'분류 전체보기'에 해당되는 글 2491건

  1. 2009.03.10 XHTML/CSS를 이용한 구조화 및 개발방법론 2
  2. 2009.03.10 X-internet
  3. 2009.03.09 jQuery로 작업하기, Part 2: 내일 나올 웹 응용을 오늘 구현해보자
  4. 2009.03.09 jsp 페이지 이동 4가지 방법
  5. 2009.03.09 가볍고 쉬운 Ajax - jQuery 시작하기
  6. 2009.03.09 Ajax로 사이트 전면 개편, Part 3: jQuery, Ajax 탭, 회전식 슬라이드쇼로 기존 사이트 개선하기
  7. 2009.03.09 jQuery로 작업하기, Part 1: 브라우저로 데스크톱 응용 옮기기
  8. 2009.03.09 jQuery and Google Maps Tutorial: #1 Basics
  9. 2009.03.09 JQuery Core 1
  10. 2009.03.09 간단한 jQuery 시작하기 자료
  11. 2009.03.09 jQuery Download...
  12. 2009.03.09 jQuery 를 사용하면서 ASP.NET 서버컨트롤와 절묘하게
  13. 2009.03.09 jquery 플러그인 링크
  14. 2009.03.09 IETester is a free WebBrowser
  15. 2009.03.07 Download: AAA Logo 2008 v2.10
  16. 2009.03.07 “Bantai Yahudi!!!! Hancurkan Amerika!!! Bantai Axe gank dan antek2nya!!! SFX harus menang!!!!]”
  17. 2009.03.04 Commons-beanutils version 1.8.0 - How to Download and Install on Mac OS X
  18. 2009.03.04 The BeanUtils Component
  19. 2009.03.04 아파치 common 오픈 소스 digester를 소개합니다.
  20. 2009.03.04 jakarta common util중 Beanutils사용법
  21. 2009.03.04 Commons-Digester
  22. 2009.03.04 Commons-DbUtils 2
  23. 2009.03.04 DbUtils 몇가지 예제
  24. 2009.03.04 jakarta Project BeanUtils 소개
  25. 2009.03.04 Commons BeanUtil
  26. 2009.03.04 BeanUtils
  27. 2009.03.04 ApacheCommon 의 BeanUtils 를 이용하는 초간단 예제..
  28. 2009.03.04 간단한 BeanUtils 사용하기
  29. 2009.03.04 JSP 엑셀다운로드
  30. 2009.03.02 컴퓨터 드라이버 자료실
반응형




XHTML/CSS를 이용한 구조화 및

개발방법론








이성노 (eouia0819@gmail.com)





1. 들어가며


이 강연은 청중들이 XHTML 및 CSS에 대 한 기본적인 선행학습이 된 상태임을 전제로 진행된다.

접근성, 웹표준화, CSS 기법 등은 다른 시간에 다루게 될 것이므로 이 시간은 실제 XHTML과 CSS를 이용한 웹사이트 개발을 위한 개발 방법론에 중점을 둘 예정이다.


Ref . 1 .


XHTML

현재 HTML은 4.01까지만 완성되어 있으며, XHTML이 HTML을 대신할 표준으로 권고되고 있다. XHTML은 1.1까지 나와있으며, 현재 2.0이 Working Draft상태에 있다.

http://www.w3.org/TR/REC-html40

http://www.w3.o rg/TR/2001/REC-xhtml11-20010531


CSS

CSS는 HTML/XHTML의 디자인 부분을 독립시켜 의미론적 웹 을 유지하는데 도움을 주고, 비전문가도 쉽게 웹문서를 디자인할 수 있도록 템플릿 개념으로 되어있다. CSS2가 현재표준안이며 CSS3가 Working Draft상태에 있다.

http://www.w3.org/TR/REC-CSS2


DOM

DOM은 Document Object Model의 약자로, 웹문서의 구성요소들을 객체화하여 접근할 수 있도록 문서의 물리적 구조를 제어할 수 있게 도와준다. 표준인 W3C DOM과 비표준인 MS DOM이 있으며, 이 때문에 JavaScript 사용시 주의해야한다.

http://www.w3.org/TR/REC-DOM-Level-1


JavaScript

JavaScript는 표준이 아니지만, 정적인 웹문서를 다양하게 활용하기 위한 기술을 제공한다. ECMA-262 3 rd Script를 기준으로 삼는다. DOM문제와 더불어, IE전용 스크립트(VBS, JS)의 문제가 있으며, 지원하지 않는 환경이 있기 때문에 반드시 규정에 맞게 사용해야 한다.

http://www. ecma-international.org/publications/standards/Ecma-262.htm



2. 구조화


CSS를 이용한 디자인의 장점은 다음과 같다.

          * 간결하고 읽고 이해하기 쉬운 코드의 생성

          * 수정, 유지보수의 용이

          * 디자인과 분리된 개발 가능

          * 트래픽 절감효과

          * 크로스 브라우저/크로스 플랫폼

          * 접근성 확보

          * 기타 (스킨시스템 구축 용이,

DHTML 기법 사용 용이, JavaScript와의 궁합,

빠른 개발-agile방법론 가능 등.. 기타등등..)


그러나 이러한 CSS의 장점은 엄밀히 말하자면 구조화된 XHTML의 장점이라고 말할 수도 있다. CSS에 대해 피상적으로 이해하고 처음 입문하는 사람들이 흔히 범하는 오류가, CSS에 대해서만 알면, 위와 같은 장점들을 맘껏 누릴 수 있다고 착각하는 점이다. 이것은 매우 흔히 일어나는 일이며 상당히 우려스러운 점으로써, 구조화된 XHTML(혹은 HTML)에 대한 이해가 없을 경우, 오히려 어렵거나 불필요한 CSS 기법을 사용함으로써 위에서 예로 들은 여러가지 장점을 십분 발휘할 수 없게 된다.

따라서, quirk모드에서 IE의 box모델 오류를 해결하기 위한 CSS hack 같은 실질적인 기법들보다 (이런 것은 검색해보면 다 알 수 있다.) 먼저 구조화된 XHTML 성방법에 대해 충분히 익혀둘 필요가 있다.


구조화 란 용어는 임의로 붙인 용어이며, 보다 널리 알려진 표현은 well-formed document 라고 할 수 있다.

well-f ormed document 의 필수조건은 다음과 같다.

  1. 의미론적이며 용도에 맞는 태그를 사용한다.

  2. 문서의 물리적/논리적 구조가 체계적이다.


2-1. XHTML의 태그 사용법

XHTML은 HTML과 크게 다르지 않지만, HTML의 XML버전이므로 XML의 규격에 준하여 몇 가지 주의 사항이 있다.

  1. 모든 태그들은 반드시 완벽하게 중첩되어야 한다.

<b><i>틀린 경우</b></i>

<b><i>맞는 경우</i></b>

  1. 모든 태그와 속성 예약어/지시어에는 소문자를 사용한다.

<A HREF= http://sample.com >틀린 경우</A>

<a href= http://sample.com >맞는 경우</a>

  1. E mpty tag들도 반드시 닫겨야 한다.

<img src= http://sample.com/wrong.jpg >

<img src= http://sample.com/right.jpg />

  1. 속성값은 반드시 겹따옴표( )를 사용해야한다.

<div class= wrong >틀린 경우</div>

<div class=wrong>틀린 경우</div>

<div class= right >맞는 경우</div>

  1. 단축형 속성값을 사용할 수 없다.

<option value= wrong selected>틀린 경우</option>

<option value= right selected= selected >맞는 경우</option>

  1. name대신 id속성을 사용한다.

<input type= text name= field1 value= 틀린 경우 />

<input type = text id= field1 value= 맞지만 문제있음 />

<input type= text id= field1 name= field1 value= 유효한 대안 />

  1. lang 속성을 사용한다.

<div lang="no" xml:lang="no">Heia Norge!</div>

  1. Doctype을 명시한다.

    Strict :
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
    "http:// www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">

    Transitional :
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

    Frameset :
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">


    이 밖에 XHTML이 HTML과 다른 점으로 다음 사항들도 주의해야한다.

    1. presentational tag들을 사용하지 않는다.

    <basefont>, <center>, <font>, <s>, <strike>, <u>

    1. 폐기된 tag들을 사용하지 않는다.

    <applet>, <dir>, <isindex>, <menu>, <xmp>

    1. DTD에 맞는 tag를 사용한다.

    <frame>, <frameset>, <iframe>


    이러한 부분들은 명확히 스펙에 명시되어 있으므로 조금만 숙달되면 준수하기 쉽다 . 그러나 이렇게 문법 지키는 코딩이 XHTML문서의 충분조건은 아니 다. 이러한 하드코딩 문법규칙이 숙달되었다면, 의미론적인 태깅을 익혀야한다. 이 과정은 딱히 정해진 가이드라인이 존재한다기보다는, 개인의 역량과 이해도에 따라 달라지므로, 여기에서는 가장 기본적인 부분들만 짚도록 한다.


    1. 잘못 사용하고 있는 태그들

    <br>은 문단 구분을 위한 태그가 아니다. (<p>를 사용)

    <quote>는 들여쓰기/박스처리를 위한 태그가 아니다.

    <table>은 웹페이지 레이아웃을 잡는데 사용하는 태그가 아니다.

    <b>는 중요한 어휘 를 표현하는데 사용하는 태그가 아니다.

    <h1>은 굵은 글씨 를 표현하는데 사용하는 태그가 아니다.

    기타등등 잘못 사용되고 있는 태그들이 많다.


    1. 의미와 목적에 맞는 태그

    중요한 어휘 를 표현하고 싶다면 <strong>또는 <em>을 사용하고 , 중요도와는 상관없는 굵은 글씨 를 표현하고 싶다면 <b>를 사용한다. 이 차이는 무엇인가?

    가장 기본적인 질문이지만, 이것이 구조화된 XHTML 을 이해하는 가장 근본적인 질문이다.

    예를 들어 텍스트 배너광고 안에 표시된 어떠한 문자열(예를 들어 가습기 총출동! 같은)이 굵은 글씨 로 표현되어 있다고 가정하자. 이것이 이 페이지 내에서 어떠한 중요도를 가지는가?

    만약 중요하지 않다면 <b>로 표현하는 것이 맞다. 그러나 이 문자열이 중요한 어휘라면, <strong>이나 <em>을 사용하도록 한다. 이것이 중요한지, 중요하지 않은지를 판단하는 것은 차후 설명할 개발방법론의 기획/분석 단계에서 이루어져야 하며, 이 판단을 올바르게 할 수 있어야 진정한 XHTML 구조화 역량을 갖출 수 있다. 실제로, 위의 이미지 같은 경우라면, <b>를 사용하는 것마저도 아깝다(?). 더 좋은 방법은 적당한 class를 부여한 후, CSS에서 font-weight:bold;를 사용하는 것이다. <b>는 문자그대로 굵은글씨 를 표현하는 것이지만, 위의 예에서는 굵은글씨 표현해야만 하는 당위성마저도 없기 때문이다.


    Q. <em>을 사용했더니 기울어진 글씨체로 나와요. 그냥 <b>로 쓸래요.

    A. <em>을 사용하고, 대신 표현은 CSS를 사용하세요. 그것이 올바른 XHTML/CSS 사용법입니다.


    이러한 판단은 지금까지 낡은 방식의 HTML 코딩 스타일에 익숙해져있던 사람들에게는 거의 모든 부분에서 걸림돌이 될 것이다.

    게시판의 게시물 리스트는 순서가 없는 리스트(<ul><li>) 인가, 순서가 있는 리스트(<ol><li>) 인가, 아니면 표의 일부분(<table><tr><td>) 인가, 이도저도 아닌 독립된 여러 줄들의 모임(<p>또는<br>) 인가?

    이미지를 이용한 테두리나 박스는 컨텐트 (<img>) 인가, 아니면 의미없는 단순한 장식요소(CSS::backgrund-image) 인가?

    입력 폼에서 사용된 비밀번호 문자열 인가, 테이블의 한 셀의 내부텍스트 인가, form control의 label 인가?

    소소한 예 몇가지만으로도 충분히 머리아플 것이다. 더 골치아픈 것은, 이러한 문제에 정답 혹은 모범답안은 없다는 점이다. 처음 예에서 가습기 총출동! 중요한 어휘 인지, 그저 굵은 글씨 인지는 문서 전체를 놓고 파악해야만 알 수 있다. 심지어 어떤 문서에서는 저 문자열이 소제목 일 수도 있다. 이런 경우에는 이것, 저런 경우에는 저것.이라고 딱 떨어지는 답이 없고 문서 전체의 문맥과 목적에 따라 그때그때 맞는 태깅을 해야한다는 점이다.


    너무 막연하게 들릴 수 있으므로 몇가지 일반적인 가이드라인을 제시해본다.

    1. 아주 특별한 경우를 제외하고, 모든 문서에는 제목 이 존재한다. 제목에는 <h1>태그를 사용한다.

    2. 아주 특별한 경우를 제외하고, 모든 문서는 한 페이지 안에서 좀더 작은 단위의 컨텐트 들로 분할될 수 있다. 이렇게 분할된 컨텐트 들은 의미상 chapter 라고 부를 수 있으며, 따라서 <h2>~<h6>까지의 태그를 사용하여 chapter단계별 소제목 을 붙일 수 있다. (chapter대신 content block, content region.. 어떤 용어를 쓰던간에. 이해가 쉽다면 자신만의 용어를 만들어 붙여도 좋다.)

    3. 어떤 컨텐트(들) 범위/영역 을 분리할 수 있다면 이를 둘러싸기 위해 <div>를 사용할 수 있다. 이것은 시각적 디자인과는 아무 관계없으며, 임의로 어떠한 컨텐트(들)과 다른 컨텐트(들)을 의미적으로 분리할 필요가 있을 때(그리고 분리 가능할 때) 사용한다.

    4. 이미지가 컨텐트 일 경우에만 <img>태그를 사용한다. 장식적인 요소일 경우에는 CSS의 background-image로 돌린다.

    어떤 이미지가 컨텐트 인지 아닌지 알아보는 가장 쉬운 방법은, 해당 이미지가 삭제되어도 정보전달 및 이용에 영향이 있는지 없는지를 살펴보는 것이다. 대체로 다음과 같은 것들은 컨텐트인 이미지 일 가능성이 높다. .

    신문기사의 사진 / 프로필에 포함된 개인사진 / 링크가 걸린 이미지 배너 / 이미지로 표현된 도표, 수식 등 / 의미를 전달하는 그림문자, 심볼 / 이미지 자체가 목적인 것들(갤러리 등)

    대체로 다음과 같은 것들은 컨텐트인 이미지 가 아닐 가능성이 높다.

    박스테두리 / 배경패턴 / 장식이미지 / 뷸릿 이미지 / 버튼(예외있음) / 정보와는 상관없는 장식성 심볼 등 / spacer 이미지

    버튼과 이미지링크는 헷갈리기 쉽다. 간단히 구별하는 방법은, 이미지링크는 URL을 이용한 GET방식을 통해 값을 전달한다. 버튼은 form의 일부분으로써, form을 컨트롤할 때 쓴다. 다음은 상당히 안 좋은 코딩 예이다.

                        <form id= formA method= post >

                       

    <im g src= img/button1.jpg onclick= doSubmit(); />

    </form>

    javascript 의존적인 코드를 만들었으므로 좋지 않고, form의 제어를 버튼이 아닌 이미지링크로 하려한다. (DOM을 무시하는 스크립트 코드는 말할 것도 없다.)

    다음과 같은 코드를 권장한다.

                        <form id= formA method= post action= logic.asp >

                       

                                  <input type= submit class= img_button1 value= ok />

                                  또는

                                  <input type= image src= img/button1.jpg alt= ok />

                        </form>

    javascript binding을 통해 별도로 javascript와 tag를 묶어주고, 되도록이면 body내의 소스에서는 javascript 사용을 자제한다. 아주 특별한 경우를 제외하고는 body내에 javascript가 사용될 필요는 없다.

    1. 비슷한 성격의 아이템이 반복되어 나열되거나 나열될 가능성이 있는 경우 리스트를 사용한다. 이때, 순서가 중요하다면 <ol>을, 순서가 중요하지 않다면 <ul>을 사용한다. 대체로 다음과 같은 것들을 리스트로 표현한다.

    메뉴 (일단 혹은 다단 메뉴) / 무언가의 목록

    게시물 목록 은 약간 애매하다. 예를 들어 인덱스 페이지에서 보이는 XX게시판의 최근 등록글 X개의 모음 같은 경우 리스트 로 표현하는 것이 적당하다. 그러나, 게시판 목록 페이지에서 보이는 게시물 목록 의 경우 , 리스트 로 볼 수도 있지만, 라고 해석할 수도 있다. 왜냐하면, 대개의 경우 목록 제목/작성자/조회수 따위의 헤더 를 포함하고, 그밖의 네비게이션링크, form버튼 들을 포함한 하나의 컨텐트 단위라고 볼 수 있기 때문이다. 따라서 <li>로 표현하는 것은 부적절할 수도 있다. 이 역시 그때그때 다른 것이므로, 문서 전체에서 해당 부분의 성격에 따라 결정해야할 문제이다.

    1. 무조건 <table>을 배척할 필요는 없다. 표현하고자 하는 것이 인지 아닌지만 판단할 수 있으면 된다.

    이런 것은 다. 공연히 <table>을 자제한다고 이런 것마저 <div>로 어떻게 해보려고 하지 말자. J

    어떤 것이 인가? 대개의 경우, 헤더 가 존재하면 다. 게시판을 예로 든다면 번호/제목/작성자/작성일/조회수/추천수 의 column header가 될 것이고,

    게시물번호 의 row header가 될 것이다. H eader는 생략될 수도 있다. 그러므로 그러한 점도 감안해야 한다. 위의 일기예보 표에서 column header는 생략되어있다.(아마도 항목 / 내용 일 것이다. 중첩된 로 표시할 경우, 3일예보/주간예보 가 가장 상위 테이블의 column header일테고, row header는 역시 생략되었다고 해석할 수 있다. )

    1. 그 결과 태그 사용량의 변화

    이렇게 의미와 용도에 맞게 태그를 사용하게 되면, 사용되는 태그 의 빈도가 이전 방식과 크게 달라지게 된다 .

    <div> 가장 많이 사용될 태그 중 한가지로, 컨텐트들의 그루핑, 분리를 위해 사용된다.

    <span> inline(줄바꿈 지 않은, 최소의 컨텐트 단위) 영역에서 컨텐트의 성격 을 부여하므로 많이 쓰인다. 이전에 <font>, <b>, <u>등등의 자리를 대체하게 될 것이다.

    <li> 생각보다 목록 이 컨텐트의 많은 부분을 차지한다는 것을 깨닫게 될 것이다.

    <h1>~<h6> 하나의 문서에서 6depth보다 더 잘게 분류된 컨텐트에는 제목이 필요할 가능성이 적다.

    <img> 그동안 생각보다 불필요한 img태그 가 많았음을 깨닫게 될 것이다.

    <table> 확실히 테이블의 개수가 준다. 하나도 없을 수도 있다. 당연히 table안에 table.. 같은 것도 없다. 코드를 알아보기 쉬우므로 개발자들은 기뻐한다.

    1. class와 id의 사용

    의미론적인 태깅을 했다면, 이제 CSS를 이용해 디자인을 표현할 수 있도록 해주어야 한다. 기껏 의미론적인 태깅을 했어도 다음과 같은 식이면 곤란하다.

    <div style= color:#FF0000;bord er-bottom:2px solid #EFEFEF;background-image: >

    <img border= 1 border-color= red onmouseover= showBorder() >

    이 경우 다음처럼 의미 를 부여한다.

    <div class= article_box >

    <img id= site_logo />

    class와 id의 용법에 대해서는 XHTML/CSS의 기본이므로 이 자리에서 따로 설명하지는 않는다. 바람직한 class/id의 selector naming에 대한 가이드라인은 이어지는 개발방법론 시간에 다루도록 한다.


    2-2. 문서의 구조화 를 시작하기 전에

    의미와 용도에 맞는 태깅이 숙달되면 이제 시야를 넓혀 문서 자체의 구조화를 생각해야 할 시간이다. 사실상, 문서의 구조화 자체는 위에 설명한 가이드라인을 제대로 준수했다면 거의 완성된 셈이다. 다만 미시적 관점에서는 간과하는 부분이 있을 수 있으므로 같은 목적을 거시적 관점에서 생각해보자.

    일반적인 웹 문서는 어떻게 생겼는가? 시간 관계상 거두절미하고 모범적인(?) XHTML 문서의 일례를 살펴보도록 하자.

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"

    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">

    <html xmlns="http://www.w3.org/1999/xhtml">

    <head>

    <!-- Meta Tags -->

    <meta http-equiv="content-t ype" content="application/xhtml+xml; charset=utf-8" />

    <meta name="robots" content="index, follow" />

    <meta name="description" content="" />

    <meta name="keywords" content="" />

    <meta name="author" content="" />

    <!-- Favicon -->

    <link rel="shortcut icon" href="" />

    <!-- CSS -->

    <link rel="stylesheet" href="" media="screen,projection" type="text/css" />

    <link rel="stylesheet" href="" media="print" type="text/css" />

    <!-- RSS -->

    <link rel="alternate" href="" title="RSS Feed" type="application/rss+xml" />

    <!-- JavaScript : Include and embedded version -->

    <script src="" type="text/javascript"></script>

    <title> 문서의 타이틀 </title>

    </head>

    <body>

    <div id="container">

    <div id="navigation">

    </div><!-- navigation -->

    <div id="primaryContent">

    </div><!-- primaryContent -- >

    <div id="secondaryContent">

    </div><!-- secondaryContent -->

    <div id="footer">

    </div><!-- footer -->

    </div><!-- container -->

    </body>

    </html>

    <body> 안쪽은 문서의 구조화에 따라 달라지므로 신경쓰지 말자. 여기에서는 전형적인 구조화된 XHTML문서의 형태를 보도록 한다.

    흔히들 <head>안쪽을 무성의하게 작성하는데, 본문만큼이나 중요하다. 절대로, 그냥 한번 만들어두고 다른 파일들에서 기계적으로 include해서 사용하지 않도록 하자. 모든 페이지마다 <head>는 각각의 페이지에 맞게 적절히 구성되어야 한다.

    되도록이면 CSS는 외부파일로 만들어두고 link해서 사용하는 것이 좋다. 필요하다면 @media같은 CSS 고급활용기법을 쓰는 것도 좋다. 가장 안 좋은 것은 <body>내의 태그에서 inline스타일로 사용하는 것이며(유연한 디자인을 불가능하게 한다.), <style>~</style>을 사용하는 것도 사이트 통일성을 유지하는 데 걸림돌이 된다.

    <script>는 가능한 한 <head>안에 위치하게 한다. 이는 자바스크립트를 해석할 수 없는 기계들을 위한 배려이다.

    잠깐 다른 주제로 빠져서, 절대로 스크립트 의존적인 기능에만 의지하지 말아야 한다. 예를 들어 주민등록번호 확인 같은 경우, javascript상에서만 체크하고 form을 submit하는 경우 javascript를 사용하지 않거나 막아둔 브라우저등에서 이용시 해당 기능을 무력화시킬 수 있다. 이러한 경우를 위해 서버사이드에서도 유효값체크를 해주어야만 한다.

    비슷한 의미로, 스크립트를 사용할 때에는 스크립트를 사용할 수 없는 경우를 위한 동등한 기능이나 정보를 fallback해주어야 한다. (<noscript> 참고)

    <title>의 경우 캐릭터셋의 영향을 받을 수 있으므로, 메타태그들이 끝난 후 선언해준다.

    <head>에 쓰이는 메타태그들은 위에 표시된 내용 외에도 더 있으므로, 문서의 목적과 필요에 따라 적절히 사용해준다.

    본격적인 <body>내에서의 구조화는 새로운 웹개발방법론과 밀접한 연관이 있으므로 이에 대해 먼저 설명 후 다시 살펴보도록 한다.


    3. 표준화를 위한 새로운 웹 개발 방법론

    실제 현장에서 웹표준화/접근성/XHTML/CSS(모두 같은 이야기라고 할 수 있다.)를 적용하는 데에는 초기에 많은 어려움이 있었다. 원인을 살펴보니, 개념 및 이해부족도 큰 문제였으며 그와 더불어 개발 공정 자체에 구조적인 문제가 있었다.

    1) 기존방식의 문제점

    화살표대로 순차 진행되며, 어느 한 단계에서 지연될 경우 병목현상이 발생하게 된다. 무엇보다도, 디자인까지 나온 후에야 코딩이 이루어지므로 인력의 효율적 관리가 어렵고, 구조화된 문서, CSS적용이 힘들다.

    이렇게 된 이유는 크게

    a) 과도한 스토리보드

    b) 디자이너의 역량부족

    을 들 수 있겠다.


    스토리보드라는 것은 계륵 같은 존재이다. 상세하면 상세한 대로, 부실하면 부실한 대로 짐이 된다.

    애초에 스토리보드란, use-case scenario의 한 표현방법 일 뿐이다. 동선의 흐름(스토리)을 기술하는 일종의 모델링 방법이다. 그런데 언제부터인가, 웹 개발의 필수 문서가 되어버렸다. 디자이너는 스토리보드가 없으면 페이지 한 장 그려낼 수 조차 없다. 개발자도 스토리보드가 없으면 비즈니스로직을 만들 수 없다.

    게다가 스토리보드는 상당히 이 많이 드는 작업이다. 그나마 쓸만한 스토리보드가 되기 위해서는 디자인 레이아웃 부터 사용자 액션에 이르기까지 모든 것을 전부 기술해야 한다. 좀 더 친절하려면 DB/프로그램설계에 도움이 될 수 있도록 내/외부에서 사용될 각종 들에 대한 정의도 포함되어야 한다. 가능한 일일까? 본인은 완벽한 스토리보드를 구경해본 적도, 만들어 본 적도 없다.( 시도는 했었으나 배보다 배꼽이 더 큰 작업이었고, 스토리보드의 구조적 한계를 깨달은 계기였다.)

    전형적인 스토리보드의 예 - 좀더 친절한(?) 기획자라면 각 영역의 사이즈, 색상, 사용되는 이미지에 대한 묘사, UI 전반에 대한 지시, 프로그래밍 지시사항 등을 꼼꼼하게 서술할 것이다.

    아울러, 불성실한(?) 디자이너, 개발자라면 스토리보드에는 그런 내용 없었는데요? 라는 발언을 달고 살 터이고, 자존심 센(?) 기획자라면 그런 발언이 안나오도록 스토리보드를 아주 상세히 작성할 것이다. 결국, 스토리보드가 완성될 사이트의 실연동영상 스틸컷 모음 수준이 되어야 완벽하다(?)는 평가를 받게 된다.

    스토리보드가 세세하면 세세할수록 작성에 들어가는 시간이 늘어나며, 세세하면 세세할수록 디자이너와 개발자는 스토리보드에 의존할 수 밖에 없으니 스토리보드가 완성되기 전까지는 하고 싶어도 별로 할 일이 없다. (물론.. 아마도 인력관리라는 명목으로 다른 프로젝트 작업을 열심히 하고 있겠지만.)

    문제는, 여러 개의 문서로 나뉘어져야 할 것을 하나의 스토리보드로 해결하려는 성향 및 기획자 절대 주의이다.


    개발자에게는 구구절절한 스토리보드보다 이런 깔끔한 비즈니스로직 프로세스 플로우 한 장이 더 필요하다. (물론, 스토리보드 를 보고 이러한 프로세스 플로우를 쳐내는 것이 skilled 개발자의 조건일 수도 있다. 그러나 애초에 이정도 기본적인 프로세스는 기획자가 작업하는 것이 더 효율적이다. 기획자의 머리에서 시나리오가 만들어지 므로.)

    디자이너 입장에서 보면 웹디자인이란 파워포인트로 된 그림 을 포토샵으로 옮긴 후, 드림위버에서 최종저장하는 것 이나 다름없다.

    천기누설을 하자면 이렇다. 기획자들은 디자이너들을 믿지 않는다. 그래서 일일이 위치나 크기나 색상이나 컨셉이나 지정해주지 않으면 제대로 된 디자인이 안나온다고 믿는다. 스토리보드대로의 디자인 이 아니면 분명히 귀찮게 굴 것이다. 왜냐하면, 기획자가 가장 사용자의 의도/접근/동선/행동 을 잘 알고 있다고 믿기 때문이다. UI 전문 기획자라면 모를까, 어불성설이다. User Interface는 디자이너가 가장 잘 안다.

    그러나, 현실적으로, 가장 잘 알아야 함에도 불구하고 현업 디자이너들은 솔직히 잘 모른다. 디자이너 일지언정 웹디자이너 는 아니기 때문이다. 별로 알고 싶어하는 것 같지도 않아보인다.

    예뻐보여서 로그인 박스를 여기에 두었어요. <- 이런 설명은 하나마나.

    이 페이지에 접속하면 사용자의 시선이 제일 처음 머무는 곳은 XX이며, 그에 따라 시선은 이러한 방향을 따라 흐르게 됩니다. 컨텐트는 컨셉과 무게에 따라 이러이러하게 배분되어 있으므로 마우스의 동선은 이러저러한 단계를 거치게 됩니다. 따라서 로그인 박스는 이 곳에 위치하는 것이 사용자 편의를 위해 바람직합니다. <- 예쁘기까지하니 금상첨화.

    반드시 이러한 잘난척하는 이론을 늘어놓으란 소리는 아니다. 적어도 웹디자이너라면 이론적인 뒷받침이 있든 없든 간에 이러한 요소가 고려된 디자인을 만들어낼 수 있어야 한다는 뜻이다. 그렇지 못한다면 5년차, 7년차등의 년차수는 의미없는 일이다. (더불어 포토샵 단축키와 타블렛으로 캐릭터그리기 스킬들도.)

    너무 이상적인 디자이너상인지도 모르겠다. 게다가 웹 UI란 단지 포토샵 작업물 이상의 것 (구조화된 문서/DHTML/기타등등) 이기 때문에 디자이너에게 너무 과중한 짐을 지우는 것일 수도 있다. 개인적인 소견으로는, 디자이너가 정말 뛰어난 디자인 만 산출해낸다면 다른 모든 짐은 덜어줘도 괜찮다고 본다.



    2) 새 방법론 제안

    그래서 새로운 방법론이 필요한 시점이다.


    앞의 도표에 비하면 상당히 복잡해보인다. 실제로는 가장 굵은 화살표만 따라보면 된다. 차례대로 따져보자.


    * 분석/기획은 공동작업

    물론, 이전에도 기획회의는 해왔다. 다른 점이 있다면, 이전에는 회의 후 결과물은 기획자가 알아서 스토리보드로 녹여내는 것이라는 암묵적 동의가 있었다면, 새로운 방법론에서는 각자 해야할 일들이 생겼다.

    우선 디자이너는 기획회의 결과 UI 스타일 가이드라인 을 산출해야한다. (위 표의 컨셉 가이드라인 은 오타입니다. -_-a) 어렵게 들리지만, 구식으로 표현하자면 시안작업 이다. 그런데 시안 이란 무엇인가? 클라이언트에게 보여줄 사지선다용 샘플 이미지 몇장?

    UI 스타일 가이드라인 이란 말 그대로 UI 디자인을 위한 스타일 및 그에 대한 가이드라인 이다. 여기에 들어갈 내용은 전체적인 레이아웃구조, 사이즈, 사용될 색상들, 텍스트 스타일, 링크 스타일, 박스 스타일, 버튼 스타일 등등을 미리 정의해 둔다. 나중에 실제 페이지 디자인 시에는 이렇게 미리 정의된 요소들을 조합/응용하기만 하면 자동으로 한 페이지가 완성될 수 있도록. 이 정의가 잘되어있다면 굳이 디자이너가 아니더라도, 이 요소들을 조합하기만 하면 별도의 디자인작업 없이도 디자인이 적용된 페이지를 만들 수 있다는 뜻이다.

    눈치 빠른 이들은 감잡았겠지만, 이 스타일 가이드라인이라는 물건이 바로 CSS다. (물론 CSS로 바로 작성해버리면 나중에 알아보기가 어려우니까 쉽게 볼 수 있는 문서형식으로 작성하는 것이 좋다.) 스타일 가이드라인만 잘 작성해도 전체 CSS 작업의 절반 이상이 완료되는 셈이다.


    기획자와 개발자는 기획회의 결과 프로세스 플로우를 먼저 뽑아낸다. 공동작업이래도 좋고, 어느 한쪽이 맡아해도 좋다. 그러나 대개의 경우 기획자 쪽이 좀더 용이할 것이다. 아무래도 전체적인 흐름을 잡고 있을 테니.

    역시 형식은 다양하겠지만 개인적으로는 UML내지 그 에 준하는 방법들을 추천한다. 잘 설계된 UML문서는 그 자체로 프로그램 코드를 대체할 수도 있다. 회의하는 동안 노트북 가져다 놓고 UML케이스툴로 슥슥 회의과정을 정리해놓으면 그자리에서 바로 프로그램 코드를 산출해주기도 한다. 개발자의 할 일이 반으로 준다.

    물론 그 정도는 아니더래도, 프로세스 플로우가 먼저 나오면, 팀 내/외부 인원들이 사이트의 흐름을 파악하기 쉽다. 또 개발자는 이를 바탕으로 비즈니스 로직을 분석해내거나, 프레임워크 에 적용 하거나, MVC모델 을 적용하는데 큰 도움이 된다. 즉, HTML코드가 직접 필요한 영역을 제외한 로직 모델링 작업을 먼저 마칠 수 있게 된다. (구조적 개발 스킬이 없는 저급개발자에게는 그림의 떡일수도..)


    * 기획자

    UML이니 하는 것들, 기획자들도 계속 공부해야 한다는 소리다.

    대신 스토리보드는 만들어도, 안만들어도 상관없다. 어차피 스타일가이드가 나오므로 굳이 모든 페이지에 시시콜콜 디자인 간섭할 이유도 없고, 프로세스 플로우에 페이지 및 로직 프로세스에 대한 선언도 되어있다. 필요한 것은 이렇게 선언된 각 페이지마다 출력되어야할 컨텐트들만 상세화하면 된다.

    예컨데, 이 페이지에는 이러저러한 메뉴가 있고, 이러저러한 내용 들이 보여야 하며 이러저러한 기능들이 있어야 한다 . 라는 것만 명확히 기술해주면 충분하다.


    * 구조화

    이 페이지에는 이러저러한 메뉴가 있고, 이러저러한 내용들이 보여야 하며, 이러저러한 기능들이 있어야 한다. 라는 컨텐트 명세서가 있다면, 이것을 깔끔하게 정리하는 과정이 구조화라 할 수 있다. 애초에, 컨텐트 명세서를 작성할 때 구조화시켜 작성한다면 별도의 구조화 과정마저도 필요없다.

    실제로 컨텐트 명세와 이에 따른 구조화를 연습해보자.


    페이지 명세서

    페이지 이름 : 영화 정보 서비스 공통 구성요소

    설명 :

    영화 정보 서비스의 모든 페이지에 대해 다음 요소들을 공통으로 포함한다.

    1. 사이트 메뉴 (메일/카페/플래닛/블로그/쇼핑/뉴스/검색/전체보기/로그인)

    2. 서비스 로고

    3. 서비스 메뉴 (영화홈/상영정보/예매/매거진/재밌는DB/커뮤니티/시사이벤트/마이무비)

    4. 검색 (영화검색, 인물검색, 통합검색), 인기검색어 4-5건, 재밌는DB 신규 등록내용 1-2건을 같이 보여준다.

    5. 영화 클릭 순위 : daily(default)/weekly 변경가능, 5건 정도 제목과 링크 제공. 상위 1건에 대해 이미지 썸네일 제공

    6. 영화기사목록 : 요즘뜨는이영화/Photo & Talk/뉴스매거진 각 5개씩 최근 등록 순서로, 상위 1건에 이미지가 있을 경우 이미지 썸네일 포함.

    7. P oll

    8. 서비스 크레딧

    9. 카피라이트

    10. 프로모션 배너 1

    11. 프로모션 배너 2

    12. 프로모션 배너 3

    13. 프로모션 배너 4

    페이지 이름 : 개별 영화 정보 보기

    URL : /movieinfo?mkey=영화id

    설명 :

    이 페이지는 개별 영화 정보 보기 페이지로 검색결과 및 개별 영화 정보의 기본 링크가 된다.

    전체 화면배치는 영화사이트 기본 레이아웃을 따른다 . ( 공통 구성요소 및 UI 스타일 가이드 참고)


    컨텐트 :

    1. 각 영화 정보 보기 페이지 및 그 서브 페이지에 공통으로

    전체보기/동영상,포토/영화지식/매거진/네티즌평

    의 서브메뉴를 제공한다.

    1. 영화 타이틀, 원제, 제작년도, 제작국가의 정보 제공

    2. 영화 정보 제공

    포스터 / 감독 / 출연 / 관람점수 / 장르 / 개봉일 / 상영시간 / 관람등급 / 관련정보 / 사이트 등

    1. 동영상 프리뷰 및 스틸컷 썸네일 4~5장 제공 -> 갤러리 페이지로 링크

    2. 평점

    3. 관람포인트 : 200자 내외의 텍스트 설명

    4. 줄거리 : 400자 내외의 텍스트로 된 줄거리

    5. 영화지식 : 해당 지식검색으로 연결되는 링크 모음

    6. 매거진 : 해당 뉴스 기사로 연결되는 링크 모음(종류, 기사제목, 출처, 날짜 등 부가 정보 필요)

    7. 네티즌리뷰 : 해당 네티즌 리뷰로 연결되는 링크 모음(제목, 작성자, 날짜등 부가 정보 필요)

    8. 400자평 보기 : 생략


간략하게 적은 것이고, 반드시 이런 형식이어야 한다는 것은 아니다. 각 회사의 사정에 따라 내부 문서 규격도 있을 터이고. 디자이너의 창의성과 퍼블리셔의 구조화를 저해하지 않는다면 기존의 스토리보드 형태의 파워포인트 문서래도 상관없다.


이제, 이 명세서를 기반으로 구조화를 해보자.

구조화의 요점은, 덩어리로 분할해서 나누어 공략한다. Divide & Conquer라는 오래된 그러나 확실한 전략이다. 효율적인 작업을 위해서 공통 레이아웃 등은 별도로 작업하는 것이 좋겠지만, 여기에서는 예시를 위해 한번에 다룬다.


명세서를 받은 퍼블리셔 (혹은 디자이너, 혹은 개발자, 혹은 기획자 ) 는 해당 페이지의 목적과 컨셉 과 스타일 에 맞게 내용을 묶어 분할하기 시작한다. 우선, 디자이너가 처음 잡은 스타일 가이드에 따르면 전형적인 2단 레이아웃 구조를 지시했으므로 크게 보아 이 문서는 다음과 같이 러프하게 구조화할 수 있을 것이다. (2단 레이아웃이 아니더라도 사실 대부분 1단계 분할은 다음 형태처럼 되기 마련이다.)

* Header 영역

* Content 영역

* Footer 영역

2단 레이아웃을 지시했으므로, Content영역은 좀 더 나눌 필요가 있겠다.

* Header

* Content

 * MainContent

 * SideContent

* Footer


퍼블리셔가 파악하기에, 위의 명세서에 들어간 내용 중 Header에 속하는 것은 다음과 같다.

* Header

          * SiteMenu

          * ServiceLogo

          * ServiceMenu

          * Search

          * Promotion_1

          * MovieRank

같은 방식으로 나머지들을 구조화한다.

* Header

          * SiteMenu

          * ServiceLogo

          * ServiceMenu

          * Search

          * Promotion_1

          * MovieRank

* Content

          * MainContent

          * SideContent

                    * Promotion_2

                    * ArticleBox

                    * Poll

                    * Promotion_3

* Footer

          * Credit

          * Copyright

이제 대략적인 공통 페이지 구조는 다 잡은 셈이다. MainContent에 들어갈 내용만 페이지 별로 상세화하면 된다.


이 페이지는 크게 제목 , 메뉴 내용 으로 나뉘어진다. 그러므로 그에 맞게 구조화하자.

* MainContent

          * ContentTitle

          * ContentMenu

          * ContentBody


ContentBody에 들어갈 내용은 다음과 같다.

* ContentBody

          * MovieInfo

                    * Poster

                    * Director

                    * Casting

                    * MovieField_1

                    * MovieField_2

                    * MovieField_3

                   

          * Score

          * Point

          * Synopsis

          * Knowhow

이 구조가 정답이라는 소리는 아니다. 이런 식으로 계층적으로 내용을 분할해 들어갈 수 있다면 다른 방식의 구조화도 가능하다.

어쨌거나, 보면 알겠지만, 결국 명세서에 써있는 내용을 잘 정리한 것 뿐이다. 애초에, 명세서에 내용을 이런 식으로 정리해놓았다면 별도의 구조화도 거의 필요없다.



이제 XHTML 코딩을 해보자.

<body>

<div id= header >

          <div id= sitemenu ></div>

          <div id= servicelogo ></div>

          <div id= servicemenu ></div>

          <div id= search ></div>

          <div id= promotion_1 ></div>

          <div id= movierank ></div>

</div>


<div id= content >

          <div id= maincontent >

                    <div id= contenttitle ></div>

                    <div id= contentmenu ></div>

                    <div id= contentbody>

                              <div id= movieinfo >

                                        <div id= poster ></div>

                                        <div id= director ></div>

                                        <div id= casting ></div>

                                        <div id= moviefield_1 ></div>

                                       

                              </div>

                              <div id= score ></div>

                              <div id= point ></div>

                              <div id= synopsis ></div>

                              <div id= knowhow ></div>

                    </div>

          </div>

          <div id= sidecontent >

                    <div id= promotion_2 ></div>

                    <div id= articlebox ></div>

                    <div id= poll ></div>

                    <div id= promotion_3 ></div>

          </div>

</div>


<div id= footer >

          <div id= credit ></div>

          <div id= copyright ></div>

</div>

</body>

</html>

이 정도 결과가 나오면 절반 이상 도달한 셈이다. 보면 알겠지만, 위에 구조화 의 결과를 그대로 HTML코드만 써서 붙인 셈이다.

이런 결과가 나올 것인데, 아예 기획자들이 파워포인트로 명세서를 쓰는 대신 처음부터 이런 HTML 코드를 짜주는 것이 바람직하겠으나 현실적으로 기획자들이 그런 수고를 해줄 것 같지 않다. 대개 기획자들의 직급이 대체로 높은 것도 한 몫 할지도. J


이제 남은 것은 아직도 비어있는 각 블록의 안쪽을 세세하게 컨텐트에 맞춰 채워넣는 것이다. 원리는 위와 상동.


예를 들어 sitemenu를 채워보자.

이 사이트 및 패밀리 사이트들이 공유하는 최상위 메뉴(메뉴라기보다는 링크모음이겠으나.)는 다음과 같다.

메일, 카페, 플래닛, 블로그, 쇼핑, 뉴스, 검색, 전체보기, 로그인


메일~뉴스까지는 패밀리사이트 링크들의 모음이므로 하나의 목록으로 묶을 수 있겠다.

검색은 form이 들어가므로 별도.

전체보기는 사이트맵으로 가는 링크이니 앞의 메일~뉴스 링크들과는 성격이 다르고,

로그인 역시 사용자 계정과 관련있는 링크이므로 별도로 분리하는 것이 낫겠다.


해서 대충 묶어보면 다음 같은 구조가 되는 것이다.

<div id= sitemenu >

<ul id= familysite>

                    <li><a href= ”” >메일</a></li>

                    <li><a href= ”” >카페</a></li>

                    <li><a href= ”” >플래닛</a></li>

                    <li><a href= ”” >블로그</a></li>

                    <li><a href= ”” >쇼핑</a></li>

                    <li><a href= ”” >뉴스</a></li>

</ul>

<form id= form_search action= ”” method= ”” >

                    <input type= text id= txt_search name= txt_search value= ”” />

                              <input type= image src= ”” alt= 검색 id= btn_search name= btn_search />

</form>

<div id= link_sitemap >

<a href= ”” >전체보기</a>

</div>

<div id= link_login >

<a href= ”” ><img src= ”” alt= 로그인 /></a>

</div>

</div>

믿기지 않겠지만, sitemenu 부분의 코딩은 이걸로 끝났다. 나머지 부분들도 이런 식으로 상세화해나가면 된다.


자, 여기까지 오는 동안 중요한 포인트가 있다면, 디자인된 화면 이 전혀 필요없다는 점이다. 즉, 디자인 과는 별개로 코드 가 완성된다. 이것이 가능한 이유는, 충분히 기획회의를 거쳤고, 대강의 디자인구조가 녹아있는 스타일가이드가 있으며, 페이지 명세서가 상세히 작성되었기 때문이다. 기획이 끝나자마자 바로 코드가 생산된다는 뜻이다.


이것은 어떤 이득을 가져다줄까?

사실, 프로그래머들에게는 코드 만 있으면 되지, 그것이 어떤 디자인 이냐는 전혀 중요하지 않다. 기획이 끝나자마자(기획자가 능숙하다면 기획단계부터 동시에 코드가 생산될 수도 있다.) 바로 생산되는 코드는 프로그래머 입장에서는 매우 반가운 일이다. 게다가 디자인 요소가 없으므로 인해 코드는 오죽 읽기 쉬운가. 또 한참 작업도중에 디자인 변경되었다고 코드 뜯어고칠 일도 없다. 웹 프로그래머, 비로소 할만한 직업이 되는 것 같다. :)

디자이너들도 코드 생산의 압박에 시달릴 필요없다. 그저 생산된 코드를 스윽 보고, 각 블록단위에 필요한 백그라운드용 이미지 들, 버튼 이미지들 만 그려내면 된다. 전반적인 건 이미 스타일가이드 작성 때 만들어두었으니까 할 일은 별로 많지도 않다. 또 디자인 변경 때문에 밤새 코드 뒤적거려가며 찾기/바꾸기 단축키를 연타할 필요도 없다. 좀 한가해졌으니 인터페이스공학 에 대해 연구 해 볼 시간이 생기리라 믿는다.

기획자들도 행복 하다. 적어도 몇백페이지짜리 스토리보드 편집은 안해도 되니까.

관리자들도 만족한다. 개발기간이 단축되었고, 유지보수,수정변경이 더 쉬워졌으며, 트래픽 절감효과로 서버 및 회선 비용을 아낄 수 있다.

사용자들도 마찬가지. 사이트가 빨리 열린다. Firefox사용자도, 매킨토시 사용자도 따로 구분할 필요없다. 심지어 핸드폰이나 PDA나 웹TV나 기타 등등 어떠한 장비-그것이 웹표준을 준수하기만 한다면 냉장고에 붙은 웹브라우저에서도 정상적인 이용이 가능하다. 시각장애인들도 불편없이 사용할 수 있다.

그밖에 검색엔진 친화성이라든가, 리팩토링, 머신피드용이 등의 부가효과들에 대해서는 더 이야기할 것도 없다.


잠깐, 중요한 것을 빼먹었다 고?

아까 만든 sitemenu, 이런 식으로 내버려두면 어쩌냐는 불안의 목소리가 있을 듯 하여 원래 강의 목표와는 상관없 지만, 완성된 XHTML에 CSS를 입히는 부분에 대한 보너스를 덧붙인다.


<div id="sitemenu">

          <ul id="familysite">

                    <li><a href="">메일</a></li>

                    <li><a href="">카페</a></li>

                    <li><a href="">플래닛</a></li>

                    <li><a href="">블로그</a></li>

                    <li><a href="">쇼핑</a></li>

                    <li><a href="">뉴스</a></li>

          </ul>

          <form id="form_search" action="" method="">

                    <input type="text" id="txt_search" name="txt_search" value="" />

                    <input type="image" src="http://image.hanmail.net/hanmail/temp/b_search.gif" alt="검색" id="btn_search" name="btn_search" />

          </form>

          <div id="link_sitemap">

                    <a href="">전체보기</a>

          </div>

          <div id="link_login">

                    <a href=""><img src="http://image.hanmail.net/hanmail/temp/b_login.gif" alt="로그인" /></a>

          </div>

</div>

아까와 같은 소스인데, 이미지 url만 붙였다. 여기에 아래의 CSS를 적용해보라.

* {

          font-family:tahoma, gulim, sans-serif;

          font-size:12px;

}

a {

          color:#333;

          text-decoration:none;

}

a:hover {text-decoration:underline;}

a img {

          border:none;

}

#sitemenu {

          float:right;

}

#familysite {

          float:left;

          margin:0px;

          padding:0px;

}

#familysite li {

          float:left;

          list-style-type:none;

          background-image:url("http://image.hanmail.net/hanmail/temp/dot.gif");

          background-position:center right;

          background-repeat:no-repeat;

          padding-left:5px;

          padding-right:5px;

          display:block;

}

#form_search {

          float:left;

          margin-left:5px;

}

#form_search input {

          float:left;

          height:11px;

          margin-right:5px;

}

#form_search input#btn_search {

          height:16px;

}

#link_sitemap {

          float:left;

}

#link_login {

          float:left;

          margin-left:5px;

}


그 결과는 놀랍게도 다음과 같을 것이다.


마무리를 해보자.

CSS가 대세 이긴 한데, CSS만 익히는 것은 아무런 소용이 없다. 구조화된 XHTML문서를 생성해낼 수 있어야만 비로소 CSS를 적용할 수 있게 된다.

웹표준화, 접근성의 확보에 CSS가 필수인 것만큼 당연히 XHTML에 대한 완벽한 이해가 선행되어야 한다.

XHTML문서의 구조화는 디자인과는 상관없이 온전히 내용만 가지고 이루어야하며, 나중에 CSS를 이용하여 디자인을 씌울 때에도 무리없이 적용되도록 잘 구조화되어야 한다.

그러나 이 과정은 기존의 개발공정으로는 제대로 담아낼 수 없어서 새 공정을 필요로 한다. 여기에서 든 내용들은 개인의 역량, 조직사정 등에 의해 유연하게 적용되어야겠지만, 중요한 것은 단지 어떤 기법을 쓸 것인가가 아니라, 왜 웹 표준화를 해야하는가, 왜 웹 접근성을 지켜야 하는가, 무엇이 의미론적인 웹을 만드는가 이러한 부분들을 확실히 이해한다면 XHTML의 구조화, CSS를 위한 디자인이 한결 현실감있게 다가올 것이다.


Ref. 2.

    1. Reference-Sites

W3C http://w3c.org

한국정보문화진흥원 http://kado.or.kr

한국소프트웨어진흥원 http://www.software.or.kr/kipahome/kipaweb


크로스 브라우징 가이드

http://www.mozilla.or.kr/docs/web-developer/standard/crossbrowsing.pdf

홈페이지 구축운영 표준 지침

http://www.mogaha.go.kr/warp/webapp/sys/dn_attach?id=1c0bdf783dc19fce1e2facc

웹접근성을 고려한 콘텐츠 제작기법

http://www.mozilla.or.kr/docs/web-developer/content_authoring_for_accessibility.pdf


웹사이트 가이드 (미국)

http://www.usability.gov/guidelines/Usability_guidelines.pdf


css ZenGarden http: //csszengarden.com

A List Apart http://alistapart.com

W3School http://www.w3school.com

C ross-browser http://www.cross-browser.com


한국 모질라 포럼 웹표준화 프로젝트 http://forums.mozilla.or.kr/viewforum.php?f=9

CSS 디자인 코리아 http://css.macple.com


신승식님 http://gregshin.pe.kr/bbs/zboard.php?id=ud

신현석님 http://hyeonseok.com

박민권 http://ani2life.egloos.com/

일모리 http://ilmol.com/wp/

Tabula Rasa http://eouia0.cafe24.com

박수만님 http://www.sumanpark.com/

윤석찬 http://channy.creation.net/blog/

김중태문화원 http://www.dal.co.kr

kukie.net http://kukie.net/resources/benefits/

hooney.net http://hooney.net/

hochan.net http://hochan.net/archives/cat_aii_aissa.html

daybreaker http://www.daybreaker.x-y.net

별주부뎐 http://blog.webservices.or.kr/hollobit/

소프트원트 http://www.softwant.com


    1. Reference-Books

Web Standard Solution : The Markup and Style Handbook / 댄 씨더홈

(8월중 번역 출간 by 박수만님)

Web Designer's Reference : An Integrated Approach to Web Design with XHTML and CSS / 크랙 그라넬

The CSS Anthology / 레이첼 앤드류

Bulletproof Web Design : Improving flexibility and protecting against worst-case scenarios with XHTML and CSS / 댄 씨더홈

Cascading Style Sheets: The Definitive Guide / 에릭 마이어 (번역판 있음)

HTML & XHTML : The Definitive Guide / 척 머스키아노 (번역판 있음)


    1. Tools

NVU

Rapid CSS Editor

Topstyle

PSPad

CSSedit

X-Edit

StyleMaster

Adobe Golive CS2

DreamWeaver 8


FF용 플러그인들. (CSS 셀렉터, 그리스몽키,  너무 많아 일일이 적을 수가 없네요. ^_^)


물론 XHTML, CSS 모두 텍스트 기반이므로 손에 익기만 하다면 아무 텍스트 에디터라도 상관없습니다. 저는 주구장창 VIM과 UltraEdit만 쓰고 있습니다.


평가/인증

W 3C HTML Validator : http://validator.w3.org/

W3C CSS Validator : http://jigsaw.w3.org/css-validator/

Webxact Accessibility Validator : http://webxact.watchfire.com

영국 시각장애인 단체 : http://www.rnib.org.uk/

미국 시각장애인 단체 : http://www.nfb.org/seal/certify.htm


Posted by 1010
98..Etc/X-internet2009. 3. 10. 10:41
반응형

(쉬프트 정보통신 - 가우스) http://member.shift.co.kr/gauce01.html

(투비 소프트 - 마이플랫폼) http://www.tobesoft.com/new/html/PID&Demo/index.html

(컴스퀘어 - 트러스트폼) http://www.comsquare.co.kr/product/feature.asp

(엘라스틱웨어 - 엔리치클라이언트) http://www.elasticware.com/kr/map.cgi/EnRichClient

_________________________________________________________________________________________________________________________________________________


1. x-internet은 예전 아키텍처에 문제가 있어서 등장했다고 한다. 그렇다면 예전 아키텍처는 어떤문제가 있었나 ?


클라이언트/서버 방식 : 개발자를 위한 개발환경과 최상의 UI기능 구현, 단 덩치가 크고 시스템적으로 유지보수가 어렵다.
WEB 방식 : 시스템 유지보수가 쉬운대신 노가다식 개발환경과 취약한 UI 및 기능성 정리하면 예전 아키텍처는 시스템 유지보수가 쉬우면 개발환경이나 기능성이 취약하고 개발환경이나 기능성이 막강하면 덩치가 커서 유지보수가 어려웠다. 등의 장단점의 차이가 분명했다.


2. 그래서 x-internet 의 등장배경은?
C/S 방식과 WEB 방식의 단점을 극복하기 위해 등장했다.


3. 그럼 명칭이 애매모호한 x-internet의 정의와 장점은 무엇인가?
정의에서 무언가 새로운 개념이 나올것 같았는데 정의는 단순했다.

"x-internet이란 Web 아키텍처와 C/S 아키텍처의 한계를 극복하고 장점만을 수용하기 위한 새로운 인터넷 아키텍처를 의미한다."

단순하게 말해 Web 과 C/S의 장점을 뽑아 새로만든 아키텍처가 x-internet 이라고 한다. 그리고 조금더 들어가면 두가지 뜻이 더 있다.


eXecutable Internet(실행 가능한 인터넷)

- 인터넷 통신과 XML 을 기반으로, 빠르고 작은 모듈들을 사용하여 C/S와 같은 UI를 사용자에서 보여줄수 있는 환경을 말한다.

eXtended Internet(확장된 인터넷)
- 개발환경의 확장성
- 외부모듈과의 인터페이스 확장성
- 멀티 OS를 지원


다시 말해 "WEB과 C/S의 장점만을 골라, C/S와 같은 기능을 인터넷과 XML을 기반으로 가볍고 빠르게 서버에서 사용자에게 제공 하고,
편리한 개발환경과 외부 모듈과의 연동을 통해 확장하기 쉬운 구조를 가진 아키텍처가 x-internet 이다."

라고 정의 할수 있다.

이렇게 x-internet 의 정의가 곧 장점이라고 보면 될것 같다.


4. x-internet은 어떻게 구현하지?
x-internet 에는 대략 3가지 요소가 있다고 정리해 보았다.
- 개발자 : 개발자는 화면과 로직을 개발하여 서버에 올려놓는다.
- 서버 : 서버에서는 각종 화면과 로직과 데이터를 저장하고 사용자의 요청을 다른 서버 로직(JSP,EJB 등) 과 연동하여 처리한다.
- 클라이언트 : 클라이언트는 x-internet 을 사용하기 위한 전용 브라우저, 자동업데이터 등의 구성요소를 설치한다.

서비스 흐름은 사용자가 전용 브라우저로 HTTP 프로토콜등을 통해 서버로 서비스를 요청하면 서버는 화면 XML, 로직 스크립, 데이터 등을 사용자의 전용브라우저로 내려보내고 전용브라우저는 화면 XML 등을 파싱하여 사용자에게 C/S스럽게(?) 보여주는 흐름이다.


5. x-internet 관련 제품 사용효과
정말 효과가 있을까?
우리 회사 : 편리한 개발환경과 확장가능한 구조로 인해 개발 생산성 향상, C/S스럽게(?) 기능을 제공해도 WEB처럼 손쉬운 유지보수 가능,
한마디로 돈은 적게 들이면서 사용자에게 고급스러운 기능 제공 가능하여 사용자의 충성도가 향상되는등의 여러 효과가 있어서
초기 도입/구입 비용이 상쇄 가능할 것이다.
사용자 : 인터넷 HTML상에 구현된 기능의 제약에서 벗어나 C/S 스러운(?) 고급스러운 기능 체험 으로 서비스 만족도가 향상될것이다


6. x-internet 도입이 성공할려면
x-internet 의 장점이 정말 장점이 되야 한다.
- 편리한 개발환경이 정말 편리해야 한다. 처음 개발환경 익힐때 오래걸리고 어느 부분은 오히려 불편하면 곤란하다.
- 확장이 쉽고 유지보수가 정말 편해야 한다. 이런 장점 설명한 제품, 프레임워크가 많지만 막상 자랑대로 되는게 별로 없었다.
- 사용자가 문구 그대로 고급스러운 기능을 써야 한다. 고급스럽다는 기능은 기능도 다양하지만 속도도 빨라야 하는것을 말한다. 기능만 좋고 속도가 느리다면 이것도 문제가 되지 않을까

이 정도로 정리해보았다. 진짜 중요한건 x-internet 을 아는것 보다는 이것을 어떻게 잘써야 x-internet 이 지향하는 효과를 볼수 있을까 를 생각해봐야 되는것 같다.

사용자 삽입 이미지

<클라이언트 기술간의 비용과 생산성 비교>

클라이언트 기술은 과거 메인프레임 시대의 터미널, 클라이언트/서버 환경의 팻 클라이언트, 웹 기반 시스템의 씬 클라이언트로 발전해 왔다.

[그림 1]에서 보듯 클라이언트/서버 환경의 팻 클라이언트, 웹 기반 시스템의 씬 클라이언트로 넘어가면서 애플리케이션에 대한 총비용은 줄어 들었으나(이 부분이 바로 배포에 소요되는 비용이다) 개발 생산성은 오히려 낮아졌다. 즉 서버 측이 더 복잡해지고 유지보수하기 어려워진 것이다. 이제 스마트 클라이언트 기술은 팻 클라이언트가 가진 장점과 씬 클라이언트가 가진 경제성을 동시에 추구할 수 있게 해준다.



[그림 1] 애플리케이션 총비용과 사용자의 생산성 (출처:almnetworks.co.kr)

[출처] X-internet |작성자 hyejin_han

Posted by 1010
반응형

난이도 : 중급

Michael Abernethy, 제품 개발 관리자, Optimal Auctions

옮긴이: 박재호 이해영 dwkorea@kr.ibm.com

2008 년 11 월 25 일

jQuery 연재물 중 두 번째 기사에서는 풍부한 RIA(Rich Internet Application)를 만들기 위해 웹 사이트에 상호 대화식 기능을 추가하는 방법을 설명하겠습니다. jQuery와 관련해 사용자가 만들어낸 사건, 웹 사이트 자체에서 얻은 정보를 결합해 활용하는 방법을 다루며, 이런 RIA를 빠르고 쉽게 만들기 위해 화면을 새로 고치지 않고도 응용 프로그램의 외형과 느낌을 변경하는 기능을 소개하겠습니다.

도입

jQuery는 지난 6개월 동안 급속도로 퍼져나가고 있으며, 웹 개발자를 위한 자바스크립트 라이브러리로 자리잡고 있다. 이런 현상은 브라우저 기반 응용으로 데스크톱 응용을 교체하기 위한 RIA(Rich Internet Application) 활용과 필요성이 급격하게 증가하는 상황과 맞물려 돌아간다. 스프레드시트부터 급여 계산에서 전자편지 응용에 이르기까지 브라우저로 데스크톱 응용을 교체해 나가는 현실이다. 이런 응용 개수가 많아지고 복잡해지면서 자바스크립트 라이브러리는 응용을 만드는 튼튼한 기초로 더욱 더 중요성이 높아질 것이다. jQuery는 개발자를 위한 필수 라이브러리가 되었다. 이 연재물에서는 jQuery를 깊숙하게 탐험하며, 개발자가 RIA를 빠르고 쉽게 만들기 위한 튼튼한 토대를 제공한다.

지난 기사에서, 개발 환경에 jQuery를 설치하는 방법과 핵심 기능, 동작 원리를 포함한 jQuery 기초를 익혔다. jQuery가 선택/필터링 메서드를 사용해 페이지에 있는 엘리먼트를 찾는 쉽게 방법은 물론이고 찾고자 하는 단일 엘리먼트나 엘리먼트 그룹을 정확하게 찾아내는 방법도 살펴보았다. 그러고 나서 jQuery가 제공하는 선택 결과를 탐색하는 다양한 메서드와 다른 프로그래밍 언어에 익숙한 개발자를 위한 유사 함수를 살펴보았다. 마지막으로 예제 위젯인 Select All/Deselect All 체크박스를 대상으로 몇 줄만 추가해서 동작하는 jQuery 프로그램 구현 방식을 살펴보았다.

이번 기사에서는 낡은 인터넷 응용에 실질적인 "풍부함"을 제공해서 예제 프로그램에 RIA라는 레이블을 붙일 수 있도록 만드는 동시에 화려한 기능을 살펴봄으로써 jQuery 지식을 확장해 나간다. 먼저 jQuery가 사건을 다루는 방법을 보여준다. 사건은 마우스 클릭, 강조, 끌기 등으로 정의된다. 사건은 단순히 버튼에만 국한되지 않으며 div, span 등에 마우스 클릭을 할 때도 일어난다. 다음으로 넘어가서 웹 페이지에 들어있는 객체에서 속성을 얻고 설정하는 방법을 설명하겠다. 이는 form 엘리먼트에서 텍스트를 얻는 방법, div에서 innerHTML을 얻는 방법, 어떤 엘리먼트에 어떤 클래스가 붙어 있는지 파악하는 방법까지도 포함한다. 마지막 절은 페이지를 새로 고치거나 외부 스타일 시트를 변경하지 않고서도 페이지 엘리먼트의 CSS 특성을 변경하는 방법을 설명한다.

예제는 웹 메일 응용을 위한 추가 위젯을 포함하며, 클라이언트 쪽에서 동작하는 RIA를 만들어 페이지 내부에서 일어나는 상호 작용에 기반을 둔 색상, 크기, 객체 위치 변경 방법을 보여준다. (이번 기사에서는 상호 작용 설명을 클라이언트 단으로 제한한다. 다음 기사에서는 서버 쪽 상호 작용도 추가할 것이다). 이 기사를 끝까지 읽으면 독자적인 RIA를 생성해 고객에게 감동을 주는 모든 jQuery 도구를 갖출 것이다.

사건

jQuery에서 사건 모듈은 웹 응용에 상호 대화 기능을 추가하는 첫 번째 단계다. 사건은 페이지에서 일어나는 모든 행위를 시작하는 단초가 되기 때문이다. 도입부에서 지적했듯이, 사건이 form 엘리먼트에서만 일어난다고 생각하면 안 된다. 실제로 어떤 엘리먼트도 사건을 일으킬 수 있으며, 이를 활용해 특정 form 엘리먼트 자체에 묶이지 않고서도 전용 위젯을 좀 더 쉽게 만들어 여기에 독자적인 상호 작용을 추가할 수 있다.

이렇게 말하긴 했지만, 대다수 사건은 form 엘리먼트와 관련이 있으며, form 엘리먼트를 사용하는 예제를 보여주는 편이 가장 손쉽다. 사용 가능한 함수로 바로 들어가기 전에, 사건 모듈이 각 함수마다 특정 패턴을 따른다는 사실에 주목하자. 각 사건 함수는 두 가지 형태로 나뉜다. 하나는 인수가 없는 형태이며, 다른 하나는 인수로 함수를 포함하는 형태다. 양쪽을 구분하는 차이점은 중요하며 함수마다 일정한 규칙을 따른다. 매개변수가 없는 함수는 실제로 사건을 일으킨다. 다시 말해 click()을 호출하면 실제 버튼을 클릭한 상태로 만든다. 버튼을 실제로 눌렀을 경우나 click() 함수를 호출했을 때 click(function)을 호출한다. 헷갈리는가? 설명만 보면 그럴지 모르겠지만 예제를 보면 좀 더 명확해질 것이다.


Listing 1. jQuery 사건 메서드
				
// "myButton" 클릭 일으키기.  이렇게 하면 버튼을 클릭하며, 버튼에 붙어 있는
// 동작을 수행한다. 예를 들어, 폼을 제출하거나 관련된 다른 jQuery 동작을 수행한다.
$("#myButton").click();

// jQuery를 활용해 "myButton"을 실제로 클릭했을 때 무슨 일이 일어날지를 설정한다.
$("#myButton").click(function(){
   $("#myDiv").toggle();
});

// 페이지에서 동작을 설정할 때 사용하는 jQuery에서 일반적인 패턴은 페이지를 읽을 때
// 초기에 발생하도록 동작을 유도하는 방식이다. 이는 서버에서 값을 가져올 경우
// Ajax 설정 과정에서도 종종 나타난다.
// 이 예제에서, myDiv는 버튼을 클릭할 때마다 시각적으로 토글된다. 페이지를 읽을 때
// click()을 바로 호출하므로, 페이지가 뜨자마자 뷰를 토글한다(실질적인 예는 아니며,
// 설계 시 디자인을 검토해야 한다.
$("#myButton").click(function(){
   $("#myDiv").toggle();
}).click();

다음에 소개하는 사건은 직전에 소개한 설계 방식에 따라 함수 두 가지가 관련되어 있다. 지면 관계상 첫 번째 형식만 정리했다.

  • blur(): form 엘리먼트가 초점을 잃어버릴 때 호출된다. 탭 키로 이동하는 바람에 텍스트 필드에서 초점이 다른 필드로 넘어갈 때가 대표적이다.
  • change(): form 엘리먼트가 초점을 잃어버렸는데 앞서 초점을 얻은 이후에 값이 바뀐 경우에 호출된다. IE와 FF는 이 이벤트를 조금 다르게 취급한다.
  • click(): 페이지 엘리먼트를 클릭했을 때 호출된다(반드시 form 엘리먼트가 되어야 할 필요는 없다).
  • dblclick(): 페이지 엘리먼트를 클릭할 때 호출된다(반드시 form 엘리먼트가 되어야 할 필요는 없다).
  • error(): 엘리먼트에 내부 오류가 발생할 때 호출된다. 브라우저마다 다르며, 사람들이 이런 이벤트를 목격하기란 쉽지 않다.
  • focus(): form 엘리먼트가 초점을 얻을 때 호출된다.
  • keydown(): 페이지 엘리먼트에서 키를 누를 경우에 호출된다.
  • keyup(): 페이지 엘리먼트에서 키를 뗄 경우에 호출된다.
  • keypress(): 연속으로 빨리 동일 엘리먼트에 keydown과 keypress가 일어날 때 호출된다.
  • select(): 텍스트 필드에서 텍스트를 선택할 때 호출된다. 콤보 박스에서 선택되는 경우에는 호출되지 않는다(이 때는 change 이벤트가 발생한다).
  • submit(): 폼을 제출할 때 호출된다.

직전에 소개한 패턴을 따르지 않으므로 호출 가능한 함수 하나만 제공하는 경우도 있다. 여기서 예외만 따로 정리한 이유는 자주 쓰이지 않기 때문이다.

  • resize(fn): 객체 크기가 바뀔 때 호출된다.
  • scroll(fn): iframe에 스크롤이 일어날 때 호출된다.
  • load(fn)/unload(fn): 객체가 페이지에 올라오고 내려올 때 발생한다.

마지막으로 몇몇 사건은 이미 예상한 바와 같이 마우스에 묶여 있다. 이를 세 번째 절에 포함한 이유는 흔히 오용되기 쉽기에, 이런 문제를 파악한 jQuery에서는 몇 가지 함수를 독자적인 함수로 대체했기 때문이다. 여기서는 기반 DOM 사건에만 직접 사상한다. 하지만 실질적인 이유 때문에 실전에서 활용할 대체 메서드도 존재한다. 엘리먼트에 대해 마우스를 누르거나 뗄 때 mousedown(fn)mouseup(fn) 함수가 호출된다. 하지만 click() 메서드를 대신 호출하면 좋다. 사건 발생은 동일하지만 기대하는 행동 양식과 조화를 이루며 오류 발생 가능성도 낮기 때문이다. 사용자가 마우스 버튼을 누른 다음에 실수를 알아차리고 마우스 버튼을 떼기 전에 마우스를 버튼에서 옮길 경우를 생각해보자. mouseup(fn)이 정의된 다른 페이지 엘리먼트로 옮겨간 다음에 버튼을 떼면 어떤 동작이 일어날까? 이상적으로는 이런 두 함수는 끌어다 놓기 인터페이스에서만 동작해야 한다. 끌어다놓기 상황에서 click은 적합한 대체물이 아니다.

사건 모듈에서 나머지 두 메서드인 mouseover(fn)mouseout(fn)은 오늘날 대다수 웹 사이트에서 흔히 사용한다. 떠다니는 도움말, 그림 출력을 위한 그림자 상자, 마우스 포인터 위치에 따른 색상 변화 등에 쓰인다. jQuery는 이런 두 함수가 상당히 자주 쓰이는 반면에 대다수 사람이 올바른 용법을 따르지 않기에 무수한 오류를 발생시킨다는 사실을 인식했다. 사람들이 고의로 코드에 버그를 심는다는 말이 아니라 복잡한 페이지와 중첩된 구성 요소를 위한 코드를 올바르게 만들기 어렵다는 말이다. 따라서 jQuery는 이벤트 모듈에 이 두 함수를 대체할 hover(fn1, fn2)라는 메서드를 추가했다.


Listing 2. jQuery hover 메서드
				
// 이 코드는 jQuery가 구현한 hover() 함수를 시연한다. 두 함수를 정의해야 한다.
// 하나는 특정 엘리먼트 위로 마우스가 지나갈 때 동작하는 함수이며,
// 다른 하나는 엘리먼트에서 마우스가 벗어날 때 동작하는 함수다.
// 이 예제에서, 테이블의 각 행은 마우스가 위로 지나갈 때 붉은색 배경으로 변하며,
// 마우스가 벗어날 때 흰색 배경으로 변한다.
$("tr").hover(function(){
   $(this).css("background","#0000ff");
},
function(){
   $(this).css("background","#ffffff");
});

속성

페이지를 상호대화식으로 만드는 요인 중에서 페이지의 특정 영역에서 정보를 얻은 다음에 다른 영역으로 옮기는 기능을 들 수 있다. 이 예는 텍스트 필드에서 정보를 얻어서 테이블에 놓거나 콤보 박스에서 정보를 가져와서 서버로 전송하고 서버에서 받은 정보를 다른 콤보 박스에 넣는 시나리오로 설명이 가능하다. 여기서 핵심은 상호 작용이 페이지에서 정보를 전달한 결과로 나타난다는 사실이다.

특히 페이지에 존재하는 개별 엘리먼트에 정보를 저장하는 다양한 방법이 있다. 아마도 단순한 <p> 태그는 텍스트 필드만큼 많은 정보를 포함하지 않으리라고(이는 참일지도 모르고 거짓일지도 모른다) 상상하기 쉽지만, 이런 정보에 접근하는 다양한 함수가 있다. 페이지 엘리먼트에서 정보를 얻는 방법이 있다면 이런 엘리먼트에 정보를 설정하는 방법도 있으리라는 결론에 이미 도달했을지도 모르겠다. 실제로 각 페이지 엘리먼트는 자료 객체이며, getter/setter 메서드로 캡슐화된 변수를 포함한다. 자바빈 모델과 jQuery에서 사용하는 모델 사이에 나타나는 차이점은 메서드 이름과 몇몇 엘리먼트가 특정 기능에 적합하지 않다는 제약점이다.

이런 메서드를 설명하기 앞서, 페이지 엘리먼트에 저장되는 정보가 무엇인지 살펴보자. <p> 같은 단순한 태그는 정보로 CLASS나 ID만 포함한다. <img> 같은 태그는 "src", "alt", "width", "height" 같은 좀 더 많은 정보를 포함할지도 모른다. 마지막으로 <input type="password"> 같은 복잡한 태그는 "defaultValue", "maxLength", "readOnly", "accessKey" 같은 정보를 포함할지도 모른다.

잠재적인 변수가 다양하기 때문에 jQuery는 이런 정보에 접근하는 일반화된 함수를 제공한다. 이 함수는 attr(name)이며, 특정 페이지 엘리먼트에서 정보에 접근하는 일반적인 방법이다. 동작 원리를 살펴보기 위해 몇 가지 예를 제시한다.


Listing 3. jQuery attr() 함수
				
<img src="/images/space.gif" id="spacer" class="a b c" alt="blank">

// attr() 함수 호출은 다음을 반환한다.
$("#spacer").attr("src"); // "/images/space.gif"를 반환한다.
$("#spacer").attr("alt"); // "blank"를 반환한다.

// 비슷하게 동일한 방식으로 ID에 접근할 수 있다.
$(img).each(function(){
   $(this).attr("id"); // "spacer"를 반환한다.
});

이 함수는 페이지에 상호대화식 기능을 추가해 깔끔한 방식으로 동작하도록 만들 때 아주 유용하다. 사실상, data() 함수가 추가되기 전에는(아래를 참조하자), 일반적으로 필요한 정보를 꽉꽉 눌러서 사용 가능한 변수 중 하나에 밀어 넣었다. 예를 들어, 프레임 두 개를 포함하는 페이지가 있을 때, 첫 번째 프레임은 탭을 보여주고, 하단 프레임은 각 탭 내용을 보여준다면 다음과 같은 방식으로 코드를 작성해야 했다.


Listing 4. attr() 구현
				
<!-- 탭으로 상단 프레임에 나타난다. CSS 파일은 탭 외형을 지정하며,
이렇게 하기 위해 HTML 코드만 필요하다. -->
<td>
   <div class="tab" id="/messages.jsp">Messages</div>
</td>

// 이 코드는 jQuery 섹션에 나타난다. 탭에서 ID를 얻는 방법과
// "messages.jsp" 페이지 내용으로 'content'라는 하단 프레임에
// 이 정보를 설정하는 방법에 주목하자. 

$(".tab").click( function() {
   window.parent.frames['content'].location = $(this).attr("id");
});

각 엘리먼트에서 속성 값을 얻는 이외에 값을 설정할 수도 있다. 이는 엘리먼트의 외형과 행동 양식을 프로그램으로 변경하는 가능성을 열어준다.


Listing 5. attr(str)로 속성 변경하기
				
// 이미지 소스를 변경해서 페이지에 표시되는 이미지 내용을 변경한다.
$("img").attr("src", "myimage.jpg");

// 페이지에 있는 모든 링크를 특정 페이지로 향하도록 변경한다.
$("a").attr("href", "mypage.html");

// 암호 필드에서 maxLength를 열 글자로 변경한다.
$(":password").attr("maxLength", "10");

페이지에 들어있는 form 엘리먼트에는 엘리먼트에 붙어 있는 값을 얻기 위해 호출할 수 있는 특수한 함수가 존재한다. 이는 폼 입력이나 유효성 검증과 관련한 작업 과정에 특히 편하다. form 엘리먼트로 상호대화식 웹 사이트를 구축할 때 이런 함수를 자주 사용할 것이다.


Listing 6. form 엘리먼트 val() 함수
				
// 텍스트 필드에 포함된 텍스트를 얻어서 공백인지 점검한다.
$(":textfield").each(function(){
   // val() 함수를 활용해 텍스트 필드 내부에 텍스트를 얻는다.
   if ($(this).val() == "")
       $(this).next().text("Error");
});

// 새로운 암호 페이지에서 양쪽이 동일한지 확인하기 위해 암호를 비교한다.
if ($("#newPassword").val() != $("#confirmPass").val())
   $("#newPassword").next().text("Error");

또한 특정 태그 내부에 포함된 정보를 얻는 함수도 있다. 이런 함수를 어떻게 활용할까? 아마도 특정 <td> 태그에 들어있는 모든 정보를 얻어 대체하거나, <p> 내부에 들어있는 모든 텍스트를 소문자로 바꾸기를 원할지도 모르겠다. 이런 속성을 얻는 두 가지 독자적인 방법이 있는데, 이런 과정에서 attr() 함수를 사용하지는 못한다. 다른 속성 함수와 마찬가지로, 이런 함수에는 대응하는 setter 함수가 존재한다. 첫 번째 함수는 html() 함수로 특정 태그의 모든 innerHTML을 반환한다. 다른 함수는 text() 태그로 특정 태그 내부에 포함된 모든 텍스트를 반환한다. html() 함수는 HTML 태그를 포함한 텍스트를 반환하는 반면에, text() 태그는 HTML 태그를 벗겨내며 내부 텍스트만 반환한다. 다음 예제를 보면 차이점을 알아챌 수 있다.


Listing 7. html() 대 text()
				
// 이 예제는 모든 <td> 태그를 검사해서 값이 공백이면 내부에 "-"를 넣는다.
$("td").each(function(){
   // 테이블 셀에 들어있는 텍스트를 점검한다.
   if ($(this).text() == "")
       $(this).text("-");
});

// 이 예제는 모든 문단의 텍스트를 소문자로 바꾼다.
$("p").each(function(){
   var oldText = $(this).text();
   var newText = oldText.toLowerCase();
   $(this).text(newText);
});

<-- 이 예제는 text()와 html() 사이에 나타나는 차이점을 보여준다. -->
<div id="sample"><b>This is the example</b></div>

$("#sample").html(); // "<b>This is the example</b>"을 반환한다.
$("#sample").text(); // "This is an example"을 반환한다.

마지막으로 최근에 속성과 관련해서 jQuery 라이브러리에 추가된 함수는 data()다. 이 함수는 jQuery UI 프로젝트에서 나왔고, 최근에 jQuery 프로젝트에 통합되었다. 원래부터 UI 프로젝트 개발자들은 특정 페이지 엘리먼트를 위해 사용 가능한 속성을 편법으로 얻기를 원하지 않았으며, 필요한 만큼 저장이 가능하도록 속성을 만들어내는 방식을 원했다. 직전에 소개하는 탭 예제로 돌아가보자. DIV의 ID 내부에 있는 링크를 편법으로 찾아내었는데 이는 이상적인 방식이 아니다. 하지만 jQuery 직전 버전의 제약 때문에 이런 방식이 유일한 대안이었다. data() 함수를 포함하면서부터 jQuery는 이런 문제를 해결하는 좀 더 우아한 해법을 제공한다. data() 함수를 각 페이지 엘리먼트에 포함된 내부 사전에 접근하는 수단으로 생각해보자. 사전은 키-값 쌍을 모아놓은 집합이다. 이는 개발자가 페이지 엘리먼트에 원하는 전용 속성을 만든 다음에 이 속성에 값을 붙이도록 허용한다. 궁극적으로, 이런 방식은 좀 더 쉬운 코드를 만들어내도록 이끌며, 프로젝트가 점점 더 커짐에 따라 유지보수도 점점 더 쉬워진다. 위에서 소개한 예를 새로운 data() 함수를 써서 다시 한번 구현해보자.


Listing 8. 새로운 data() 함수
				
// 위에서 수행했던 방식처럼 div를 생성하지만 구체적인 정보를 활용하지 않는다.
// 이런 식으로 jQuery 코드에서 일반적인 HTML 레이아웃을 만들고 수정할 수 있다.

<td>
   <div class="tab"></div>
</td>

// 이제 jQuery 코드에서 각 탭을 수정한다.

$(".tab").eq(0).text("Messages");
$(".tab").eq(0).data("link", "messages.jsp");
$(".tab").click(function(){
   window.parent.frames['content'].location = $(this).data("link");
});

// 한 걸음 더 나가서, 자바 배열을 통해 외부 속성 파일에서 이런 정보를 얻어보자.
// JSP 페이지에 있는 코드다.

<%
  // 탭 이름을 포함하는 배열
  String[] tabNames;
  // 탭 링크를 포함하는 배열
  String[] links;

  for (int i=0; i<tabNames.length; i++) {
%>
  $(".tab").eq(<%=i%>).text("<%=tabNames[i]%>");
  $(".tab").eq(<%=i%>).data("link", "<%=links[i] %>");
<% } %>

$(".tab").click(function(){
   window.parent.frames['content'].location = $(this).data("link");
});

CSS 조작

이 기사에서 소개할 마지막 내용은 스타일 시트를 조작하거나 페이지를 새로 고치지 않고서 CSS를 조작하는 방법이다. 이는 색상, 글꼴 등 단순한 화면 구성 요소를 변경하는 방법으로 페이지에 기본적인 효과를 추가하는 능력을 부여한다. jQuery에서 CSS 부문은 전반적인 라이브러리 탄생을 이끌도록 영감을 불러일으켰다. 웹 페이지에서 CSS 프로그래밍을 쉽게 만들기 위한 목표로 시작했으며, 살펴본 바와 같이 프로젝트는 무럭무럭 자라났다. 여전히 원래 프로젝트 의도를 따르기에, jQuery는 CSS로 프로그래밍 작업을 손쉽게 하는 방법을 제공한다. 하지만 jQuery가 제공하는 CSS 조작을 위한 전통적인 함수는 실제로 오늘날 웹 환경에 적합하지 않으므로, (jQuery에서) 대신 사용할 다른 함수를 제시하겠다.

웹 페이지에서 CSS를 조작하기 위한 기본적인 함수 두 가지를 사용하면 문자열로 단일 속성을 전달하거나 문자열 배열로 한번에 속성을 전달할 수 있다. 두 함수 모두 똑같은 작업을 수행하며 웹 페이지에서 CSS를 쉽게 변경할 수 있다.


Listing 9. css() 함수
				
// 모든 div 배경을 붉은색으로 변경한다.
$("div").css("backgroundColor", "#ff0000");
// 또는
$("div").css("backgroundColor", "red");
// 또는
$("div").css({backgroundColor: "#ff0000"}); // 괄호나 따옴표 쌍을 주의한다.

이런 함수는 아주 직관적이며 금방 익힐 수 있다. 하지만 웹 페이지 디자인 분야에서 현재 추세를 고려할 때 문제점도 발견할 수 있다. 일반적인 웹 페이지는 페이지에서 스타일을 제거한 다음에 코드 섹션이나 외부 파일에 스타일시트로 놓아둔다. 피할 수만 있다면 스타일 코드를 자바스크립트 코드에 넣어두기를 원하지 않을 것이다. 이렇게 하면 나중에 사이트 외형 변경이 어려워진다.

다행스럽게, 필요한 분리 기능을 제공하는 대체 함수가 존재한다. 이 함수를 사용해도 여전히 CSS 조작이 쉽고 직관적이다. 이 함수는 페이지 엘리먼트에서 클래스를 추가하고 제거하는 기능을 제공한다. 외부 스타일 시트에서 이런 클래스에 스타일을 적용하는 방법으로 스타일/자료/사건을 분리할 수 있으며, 이는 복잡한 페이지에서 아주 중요한 특성이 된다. 몇 가지 예제를 살펴보자.


Listing 10. 바람직한 CSS 조작: addClass()와 removeClass()
				
// "input_error" 클래스를 추가해서 검증에 실패한 폼 엘리먼트에 적용한다.
// "input_error" 클래스를 외부 CSS 파일에 정의해 붉은 색 테두리와 붉은 색 텍스트로
// 지정한다.

$(":textfield").each(function(){
   if ($(this).val() == "")
   {
       $(this).next().text("Error");
       // 이렇게 하면 필드 테두리와 텍스트가 붉은색으로 변한다.
       $(this).addClass("input_error");
   }
   // 텍스트 필드에 이미 클래스를 적용했는지 검사한다.
   else if ($(this).hasClass("input_error"))
   {
       $(this).next().text("");
       // 클래스를 삭제해 정상적인 테두리/텍스트로 복원한다.
       $(this).removeClass("input_error");
   }
});

예제를 보면 알겠지만, CSS 조율을 위한 외부 스타일시트에 정의된 클래스 참조 기법은 웹 페이지에서 CSS를 조작하는 바람직한 방식이다. 이렇게 하면 웹 사이트 작성자가 스타일시트 하나만 바꾸는 방법으로 전체 사이트에서 오류 메시지 처리 방법을 바꿀 수 있다. 직전에 소개한 css() 호출 방법을 사용할 경우 모든 코드 인스턴스를 쫓아다니며 변경해야 한다. css() 메서드는 사용하기도 쉽고 직관적임에도 불구하고 대규모 웹 응용에는 적합한 해법이 아니며 addClass()removeClass() 메서드를 사용함으로써 이를 회피해야 한다.

여기서 배운 모든 내용을 하나로 합치기

여기서 배운 모든 내용을 하나로 합치기 위해 다시 한번 예제로 돌아가자. RIA를 만들기 위한 상호대화식 웹 응용은 사용자에게 전자편지 기능을 제공하는 데스크톱 응용으로 작업하는 느낌을 준다. 여기서는 사건, 속성, CSS 모듈을 사용해 웹 메일 응용이 마우스 클릭과 더블 클릭을 다루는 방법을 정의한다. 아래 제시한 스크린샷에서 기대했던 행동 양식을 살펴볼 수 있다. 사용자가 테이블 행에 클릭하면 현재 선택한 행의 색상이 바뀐다. 사용자가 메시지를 더블 클릭하면 메시지를 읽는다. 하지만 또한 사용자가 새로운 메시지를 읽으면 행의 배경색을 바꿔 메시지를 읽었다고 표시해준다.


그림 1. 행을 클릭하기
행을 클릭하기

그림 2. 행을 더블 클릭하기
행을 더블 클릭하기

Listing 11. 여기서 배운 모든 내용을 하나로 합치기
				

// 먼저 테이블에 행을 추가한다. 각 행은 "messageRow" 클래스 멤버다.
// 각 행에 ID를 부여하고, 이 ID는 자바 자료 객체에서 얻은 메시지 번호로 지정한다.
// JSP 파일에서 for 루프로 이를 지정한다.

<%
   for (int i=0; i<messages.size(); i++)
   {
      MessageData message = messages.get(i);
%>
      <tr class="messageRow" id="<%=message.id %>">
      
// 테이블을 만들었다면, jQuery 코드를 사용해 마우스 클릭과 더블 클릭을 잡아내도록
// 정의한다.

// click() 함수를 사용해서 행에 클릭하는 행위를 감지한다.
// 다음에 css() 함수를 사용해 CSS를 직접 조작하는 대신에 use addClass()와
// removeClass()를 사용하는 방식에 주목하자. 이는 jQuery 코드 변경없이 스타일
// 시트를 변경하도록 허용한다.

$(".messageRow").click(function() {
     $(".messageRow").removeClass("message_selected");
     $(this).addClass("message_selected");
});

// 이제 테이블 행을 더블 클릭할 때를 잡아낸다. post() 함수의 Ajax 메서드는 무시한다.
// 다음 기사에서 다룰 계획이다. 여기서 dbclick() 함수를 사용해 더블 클릭을
// 잡아낸다. Ajax 호출에 주목하자. 더블 클릭한 테이블 행의 ID를 가져온다.
// 이렇게 가져온 ID를 서버에 날려 서버에서 메시지 정보를 얻는 데 사용한다.
// JSP 코드에서 메시지 번호를 정의했으므로 ID는 메시지 번호를 포함한다.

$(".messageRow").dblclick(function() {
     if ($(this).hasClass("mail_unread"))
     {
         $(this).removeClass("mail_unread");
     }
     $.post("<%=HtmlServlet.READ_MESSAGE%>.do", 
     {
            messageId: $(this).attr("id"),
            view: "<%=view %>"}, 
            function(data){
            // Ajax 관련 코드를 여기에 넣는다.
            });
     });
});

결론

jQuery와 같은 자바스크립트 라이브러리는 데스크톱에서 브라우저로 응용 프로그램을 이식함에 따라 점점 더 중요해진다. 이런 응용은 점점 더 복잡해지므로 웹 응용 프로젝트에서 jQuery와 같은 튼튼한 교차 브라우저 해법을 필수로 요구한다. jQuery는 다른 자바스크립트 라이브러리와 격차를 벌리기 시작했으며, 필요한 모든 작업을 수행하는 능력과 함께 손쉬운 개발 특성 때문에 많은 개발자들이 jQuery를 핵심 라이브러리로 선택하고 있다.

연재물 두 번째 기사에서 웹 페이지에서 일어나는 상호 작용을 살펴봄으로써 jQuery 지식을 확장했다. 또한 (서버에서 정보를 가져오지 않고서) 클라이언트에서 기초적인 상호 작용을 달성하는 방법을 살펴보았다. 사건 모듈을 살펴보기 시작했는데, 페이지 엘리먼트가 마우스, 키보드, 초점을 비롯한 다양한 상호 작용에 반응하는 방법을 정의했다. 이벤트는 웹 페이지에서 상호 작용을 일으키는 거대한 드라이버라는 사실을 확인했으며, 활용을 위해 반드시 form 엘리먼트일 필요가 없다는 사실도 배웠다. 그러고 나서 속성 설명으로 넘어가서, 페이지 엘리먼트에서 속성을 적절히 가져오고 설정하는 방법을 배웠다. 일반적인 attr() 함수를 사용해 모든 엘리먼트에 일반적인 접근 방법이 있다는 사실과 함께 form 엘리먼트 값을 얻기 위한 특별한 함수가 존재한다는 사실도 살펴보았다. 또한 jQuery에서 data() 함수가 새롭게 추가되었기에, 프로그래머가 원하는 어떤 속성도 만들 수 있도록 각 페이지에 속한 엘리먼트에 해시 지도를 포함할 수 있다는 사실도 배웠다. 마지막으로 페이지를 새로 고치지 않고서도 페이지 엘리먼트의 CSS를 변경하는 방법도 살펴보았다. css() 함수가 쉽고 직관적이기는 하지만, addClass()removeClass()를 사용해 이런 함수를 대체하는 편이 바람직하다는 사실도 배웠다. css() 함수는 jQuery 코드에서 페이지 스타일을 독립적으로 유지하는 기능이 없기 때문이다.

이 기사에서 마지막 부분은 세 가지 모듈을 결합해 마우스 상호 작용을 제공하는 웹 메일 응용 예제를 설명한다. 이 예제는 클릭한 행을 강조하는 행위와 더블 클릭해서 읽은 메시지를 표시하기 위한 행위를 구분하며, 메시지에 밀접한 자료를 서버에 요청하도록 Ajax 호출을 통해 프로세스에서 서버 쪽으로 메시지 번호를 전달하는 기능을 구현한다.

연재물에 속한 다음 기사에서 jQuery에서 Ajax 기능을 좀 더 깊숙히 다루며, Ajax 활용 과정에서 나타나는 복잡도를 일반적으로 사용하는 자바스크립트 호출만큼이나 단순하게 만드는 방법을 살펴보겠다. 추가로 jQuery에 들어있는 효과 모듈을 살펴볼 것이다. 이 모듈은 사용자에게 근사한 시각 효과와 추가적인 상호 작용을 강화하는 기능을 선사한다. 다음 기사에서 마지막으로 다룰 내용은 데모용 웹 메일 응용 정리와 jQuery에서 얻은 교훈이다. 여러분도 jQuery 라이브러리를 독자적인 웹 응용에 추가해서 사용하리라 믿는다.





위로


다운로드 하십시오

설명 이름 크기 다운로드 방식
예제 애플리케이션이 들어있는 Zip 파일 jquery.zip 68KB HTTP
예제 애플리케이션이 들어있는 War 파일 jquery.war 68KB HTTP
다운로드 방식에 대한 정보


참고자료

교육

토론


필자소개

Mike Abernethy

10년에 걸친 기술 경험을 통해 Michael Abernethy는 광범위한 기술을 토대로 광범위한 클라이언트와 작업해 왔다. 현재 경매 소프트웨어 회사인 Optimal Auctions에서 제품 개발 관리자로 일한다. Abernethy는 요즘 RIA(Rich Internet Applications)에 초점을 맞춰 복잡함과 단순함을 동시에 추구하고 있다. 컴퓨터 앞에 앉아 있지 않을 때는 양서를 끼고 멕시코 해변가에서 여유를 즐긴다.

Posted by 1010
05.JSP2009. 3. 9. 18:04
반응형

먼저 다음 페이지 이동 특성들을 미리 알아볼 필요가 있습니다

JSP에서는 페이지 이동시 다음 4가지 정도의 방법이 있습니다


 JavaScript를 이용

window.open, location.href, location.replace 등을 이용할수 있습니다


login_process.jsp

<% if (a == null) { %>

      <script>location.href = "admin.jsp"; </script>

<% } else { %>

      <script>alert('권한이 없어요!'); window.history.back(); </script>

<% } %>

         

특징적인부분은 브라우져의 주소창이 변경되며

(이말은 즉슨 클라이언트가 다시 admin.jsp를 서버에 요청한다는 말입니다)

login_process.jsp 에서 jsp가 다 실행되고 브라우져에 out put된 html 및
javascript들만으로

실행된 코드들이라는 것입니다


response.sendRedirect를 이용

login_process.jsp

<% if (a == null) {

          response.sendRedirect("admin.jsp");

     } else {

          response.sendRedirect("login.jsp");

     }


     a = "test 입니다";

     System.out.println(a);

%>


이 코드에서 a가 출력될까요 안될까요?

출력 됩니다.

sendRedirect가 되더라도 밑에 jsp 코드들은 모두 실행 된다는 말입니다

response.sendRedirect는 기본적으로 모든 로직들을 다 처리한 후 코드
맨 마지막 부분에사용하는 것이 올바른 방법입니다

만약 그렇지 못한 경우는 response.sendRedirect 다음 바로 return;
이라는 코드를 집어 넣어야 합니다


response.sendRedirect은 HTTP 헤더정보를 변경하여 redirect시키기
때문에 역시 브라우져의 주소창이 변경되며 sendRedirect가 실행되기전
html이나 javascript로 out put되는 코드들은 모두 실행되지 않습니다.


forward 이용

jsp 태그의 <jsp:forward> 나 servlet의 RequestDispatcher.forward 를
이용할수 있습니다


login_process.jsp

<% if (a == null) { %>

          <jsp:forward page="admin.jsp"/>

<% } else { %>

          <jsp:forward page="login.jsp"/>

<% }


     a = "test 입니다";

     System.out.println(a);

%>


그럼 위의 코드의 경우 a가 출력 될까요 안될까요?

정답은 출력 안됩니다. 바로 forward 되어 버립니다.

클라이언트로 응답주지 않고 바로 서버측에서 admin.jsp로 이동하기 때문에

주소창이 바뀌지 않고 그로인해 브라우져에 출력되는 응답속도 또한 사용자가
보기에는응답이 빠른 장점이 있습니다


하지만 forward 이후 JSP 코드들이 실행되지 않는것 사실이지만 만약 finally절이
있는경우finally절은 실행 됨으로 확실히 알아둡시다.


meta 태그 이용

마지막으로 meta 태그를 이용할 수도 있습니다.


<META http-equiv=refresh content="0;url=admin.jsp">


즉 요약하자면..

페이지 이동 방법이 여러가지가 있습니다.

그 특성들을 잘 알고 올바르게 사용하여야 합니다.


출처: http://jakartaproject.com/article/jsptip/1114929726078
Posted by 1010
반응형
Query 사용

위에서 소개한 Aptana를 설치 하였다면, 별도 jQuery설치는 필요 없다. 하지만 설치가 어려운것은 아니다. jQuery라이브러리는 55kb짜리 파일 하나로 되어 있다. 이를 HTML에 사용 선언을 하여 주면 된다.


<html>
  <head>
     <script type="text/javascript" src="path/jquery.js"></script>
     <script type="text/javascript">
       // Your code goes here
     </script>
   </head>
  <body>
    <a href="http://jquery.com/">jQuery</a>
   </body>
</html>


기존 자바 스크립트 라이브러리 사용과 차이가 없다. 단, 압축버젼과 그렇지 않은 버젼 두개의 파일을 제공하는데, 프로그래밍을 할 때는 디버깅을 위해 압축하지 않은 버젼의 파일을 사용하고, 배포할 경우 압축된 버젼을 사용하는 것이 좋다.


jQuery 의 시작 이벤트

보통의 자바스크립트 프로그래머들은 브라우져의 document가 모두 다운로드 되어진 상태에서 코드를 시작하기위해 다음과 같은 이벤트에 스크립트 코드를 넣는다.

  window.onload = function(){ ... }

그러나 이 경우 이미지 까지 다운로드가 모두 완료 된 후 이벤트가 호출되기 때문에, 큰이미지의 경우 실행속도가 늦은 것처럼 사용자에게 보일 수 있다. 따라서 jQuery는 이러한 문제를 해결하기위해 다음과 같은 이벤트를 제공한다.


$(document).ready(function(){
   // 이곳에 코드를 넣으면 된다.
});


 이 이벤트는 브라우져의 document(DOM)객체가 준비가 되면 호출이 된다. 따라서 이미지 다운로드에 의한 지연이 없다.

위 코드는 다음과 같이 생략하여 사용 가능하다.


$(function() { // run this when the HTML is done downloading }); 

사용자 이벤트 처리 - 클릭이벤트의 예

특정 태그를 클릭 했을경우 이벤트의 처리를 jQuery에서 어떻게 처리 하는지를 살펴 보자. 다음은 위 HTML예제의 앵커(a)태그 클릭 시 이벤트를 처리하는 코드 이다.


$("a").click(function(){
   alert("Thanks for visiting!");
});



jQuery에서 이벤트 처리는 콜백함수를 통해 수행된다. 이코드는 HTML에 있는 모든 앵커 태그의 클릭 시 팦업창을 통해 메시지를 출력해 준다.

코드를 보면 $()로된 문법을 볼 수 있을 것이다. 이것은 jQuery의 셀렉터 이다. $("a")가 의미하는 것은 HTML(브라우져의 DOM)에서 앵커태그 모두를 의미한다. 이후 .click()메소드는 이벤트 메소드로서 이곳에 콜백함수를 파라메타로 넣어 이벤트 처리를 수행 하는것이다. 함수는 위에서 처럼 익명(function(){...})이나 선언된 함수 모두를 사용할 수 있다.


jQuery의 셀렉터

$()로 시작하는 셀렉터를 좀더 살펴보자. jQuery는 HTML, DOM객체등을 CSS나 XPATH의 쿼리방법과 동일한 방법으로 선택 한다. 앞선 예처럼 문자열로 특정 태그를 선택하는 것은 CSS를 작성해 본 프로그래머라면 익숙할 것이다. 이와 같은 방법 외에도 다음과 같이 태그의 id를 통해 선택 할 수 있다.


$(document).ready(function() {
   $("#orderedlist").addClass("red");
});



위 코드는 id가 orderedlist인 태그에 red라는 CSS 클래스를 할당하는 코드 이다. 만약 이태그가 하위 태그를 가지고 있다면 다음과 같이 선택 할 수 있다.


$(document).ready(function() {
   $("#orderedlist > li").addClass("blue");
});


이코드는 id가 orderedlist인 태그의 하위 태그 중 <li> 태그 모두에 blue라는 CSS 클래스를 할당하는 코드 이다. 이코드는 jQuery메소드를 이용 다음과 같이 바꾸어 사용 할 수도 있다.


$(document).ready(function() {
   $("#orderedlist").find("li").each(function(i) {
      $(this).addClass("blue");
   });
});


한가지 다른 점은 모든 태그에 동일하게 CSS 클래스를 적용하는 방식이 아닌 개별 태그를 선택하여 적용할 수 있다는 것이다.

XPath를 사용하는 예를 다음과 같은 것을 들 수 있다


//절대 경로를 사용하는 경우

$("/html/body//p")
$("/*/body//p")
$("//p/../div")

//상대경로를 사용하는 경우

$("a",this)
$("p/a",this)


다음과 같이 두 방식을 혼용하여 사용 할 수도 있다.


//HTML내 모든 <p>태그중 클래스속성이 foo인 것 중 내부에 <a> 태그를 가지고 있는것

$("p.foo[a]");

//모든 <li>태그 중 Register라는 택스트가 들어있는 <a> 태그

$("li[a:contains('Register')]");

//모든 <input>태그 중 name속성이 bar인 것의 값

$("input[@name=bar]").val();


이외에도 jQuery는 CSS 3.0 표준을 따르고 있어 기존 보다 더많은 쿼리 방법을 지원하고 있다. 자세한것은 jQuery의 API 설명을 참고 하라(http://docs.jquery.com/Selectors)


Chainability

jQuery는 코드의 양을 줄이기 위해 특별한 기능을 제공한다. 다음 코드를 보자


$("a").addClass("test").show().html("foo");


<a>태그에 test라는 CSS 클래스를 할당한다. 그후 태그를 보이면서 그안에 foo라는 텍스트를 넣는다. 이런 문법이 가능한 이유는 $(), addClass(), show()함수 모두가 <a>태그에 해당하는 객체를 결과로 리턴해주면 된다. 이를 Chainability라 한다. 좀더 복잡한 경우를 보자


$("a")
.filter(".clickme")
   .click(function(){
      alert("You are now leaving the site.");
   })
.end()
.filter(".hideme")
   .click(function(){
      $(this).hide();
      return false;
})
.end();

// 대상 HTML이다

<a href="http://google.com/" class="clickme">I give a message when you leave</a>
<a href="http://yahoo.com/" class="hideme">Click me to hide!</a>
<a href="http://microsoft.com/">I'm a normal link</a>


중간에 end()함수는 filter()함수로 선택된 객체를 체인에서 끝는 역할을 한다. 위에서는 clickme클래스의 <a>태그 객체를 끊고 hideme를 선택하는 예이다. 또한 this는 선택된 태그 객체를 말한다.

이런 Chainability를 지원 하는 jQuery메소드들에는 다음과 같은 것들이 있다.


  • add()
  • children()
  • eq()
  • filter()
  • gt()
  • lt()
  • next()
  • not()
  • parent()
  • parents()
  • sibling()

Callbacks

위에서 click()이벤트를 콜백함수를 통해처리하는 코드를 살펴 보았다. 콜백함수는 기존 자바나 .NET의 그것과 같다. 다음 코드를 보자


$.get('myhtmlpage.html', myCallBack);


먼저 $.get()은 서버로 부터 HTML(또는 HTML 조각)을 가져오는 함수 이다. 여기서 myCallBack함수는 전송이 완료 되면 호출되는 콜백 함수 이다. 물론 앞선 예의

click()이벤트 콜백처럼 익명함수 function(){}을 사용 해도 된다. 그러나 이와 같이 미리 선언된 함수를 콜백으로 사용할 경우 파라메타의 전달 방법은 좀 다르다. 흔히 다음과 같이 하면 될것이라 생각할 것이다.


$.get('myhtmlpage.html', myCallBack(param1, param2));


그러나 위와 같은것은 자바스크립트의 클로져(closure)사용에 위배가 된다. 클로져는 변수화 될수 있는 코드 블록을 이야기한다. 즉 스트링변수와 같이 파라메타로 전달될 수 있지만, 실행가능한 함수 인 것이다. 일반적으로 함수를 ()제외하고 이름만을 사용하면 클로져가 된다. 위의경우 $get()함수의 파라메타로 전달된 myCallBack함수는 클로져로 전달된것이 아닌 myCallBack()를 실행한 결과 값이 전달 된다. 따라서 다음과 같이 코드를 작성하여야 한다.


$.get('myhtmlpage.html', function(){
   myCallBack(param1, param2);
});


만약 선언된 함수가 아닌 익명함수를 콜백으로 사용할경우는 다음과 같이 하면 된다.

$.get('myhtmlpage.html', function(param1, param2){
//이곳에 코드를 넣는다.
});


jQuery 의 애니메이션

HTML의 태그를 사라지고 나타내게 하거나, 늘리고 줄이고, 이동시키는 애니매이션 동작은 많이 사용하는 기는 중 하나이다. jQuery는 다양안 애니메이션 기능을 메소드를 통해 제공한다. 다음 코드를 보자


$(document).ready(function(){
   $("a").toggle(function(){
      $(".stuff").hide('slow');
   },function(){
      $(".stuff").show('fast');
   });
});

이코드는 <a>태그중 stuff클래스가 할당된것을 토글로 느리게 감추고, 빨리 보이게 하는 함수 이다.

다음은 animate()메소드를 이용하여 여러 애니메이션을 합쳐 실행하는 예이다.


$(document).ready(function(){
   $("a").toggle(function(){
      $(".stuff").animate({ height: 'hide', opacity: 'hide' }, 'slow');
   },function(){
      $(".stuff").animate({ height: 'show', opacity: 'show' }, 'slow');
   });
});


위 코드는 높이와 투명도를 동시에 천천히 사라지고, 나타나게 하는 코드 이다.


jQuery에서의 Ajax

Ajax는 서버와의 비동기 통신을 말한다. 일반적으로 Ajax하면 요즘은 자바스크립트를 이용한 브라우져의 동적 DOM의 처리, 즉 DHTML, CSS등을 포함하지만, jQuery에서는 Ajax라는 네임스페이스를 통해 비동기 서버 통신을 하는것을 말한다.먼저 다음 예를 보자


$.ajax({
   type: "GET",
   url: "test.js",
   dataType: "script"
})


이 예는 GET방식으로 서버에서 자바스크립트를 로딩하고 실행하는 코드 이다.

다음 예를 보자


$.ajax({
   type: "POST",
   url: "some.php",
   data: "name=John&location=Boston",
   success: function(msg){
      alert( "Data Saved: " + msg );
   }
});


이는 서버로 부터 POST방식으로 파라메터를 주어 데이터를 가져온 후 이를 success콜백을 이용해 처리하는 코드이다. 아마 Ajax에서 가장 많이 사용하는 코드일 것이다. success말고도 $.ajax()함수는 다양한 옵션을 제공한다. 자세한 내용은 API설명을 참조하라 (http://docs.jquery.com/Ajax)

다음 예는 이 옵션 중 async(비동기)방식을 사용할지 아닐지를 사용한 코드이다.


var html = $.ajax({
   url: "some.php",
   async: false
}).responseText;

Posted by 1010
반응형

Brian J. Dillard, VP, Ajax 개발, Pathfinder Development

옮긴이: 장동수 dwkorea@kr.ibm.com

2008 년 9 월 23 일

Ajax 기술이 대규모 상용 웹 응용 프로그램의 외관을 바꾸고 있습니다. 그러나 대다수의 소규모 웹 사이트는 모든 사용자 인터페이스를 하룻밤 사이에 재구축할 만한 자원이 없습니다. 새로운 기능은 실세계 인터페이스 문제를 해결하고 사용자 경험을 향상시켜야 가치를 증명할 수 있습니다. 이 연재 기사는 오픈 소스 클라이언트측 라이브러리를 사용하여 사용자 인터페이스를 점진적으로 현대화하는 방법을 설명합니다. 이번 회에는 느리고, 복잡하고, 짜증스런 제품 상세 정보 페이지를 DHTML과 Ajax를 사용하여 빠르고, 우아하게 바꿔 보겠습니다. 점진적 개선(progressive enhancement)의 원칙을 따르면, 모든 웹 브라우저에서 사이트에 접근할 수 있습니다.

기사 소개

이 기사는 웹 1.0 쇼핑 사이트를 Ajax 기술을 사용하여 단계적으로 개선한다. 개선 전과 후의 예제 응용 프로그램 소스를 받을 수 있고(다운로드), 필자의 웹 서버에서 두 버전이 실제로 동작하는 모습을 확인할 수 있다. Ajax 기술과 모범 사례 외에도, Ajax가 점진적 개선의 원칙과 사용성, 사용자 경험 디자인(UxD: user experience design)을 통해 사용자 경험을 향상시키는 방법을 알게 될 것이다.

이 기사는 HTML과 CSS에 익숙하고, 자바스크립트와 Ajax 프로그래밍에 대해 기본적인 지식이 있다는 전제 하에 썼다. 예제 응용 프로그램은 클라이언트측 코드만 사용하여 구축했지만, 사용된 기술은 어떤 서버측 프레임워크에도 적용할 수 있다. 예제 사이트를 실행하려면, localhost에서 동작하는 기본적인 웹 서버가 필요하지만, 소스 코드를 따라가면서 필자의 웹 서버에서 실행되는 예제 사이트를 통해 결과를 확인해도 된다.




위로


1부와 2부 복습

연재의 Part 1Part 2에 서는 Customize Me Now라는 예제 응용 프로그램을 통해 웹 1.0 버전을 Ajax를 사용한 웹 2.0 버전으로 개선하는 작업을 시작하면서, 비지니스와 사용성 관점에서 그 이유를 설명했다. jQuery 자바스크립트 프레임워크와 몇 가지 플러그인을 포함한 오픈 소스 도구를 설치하는 방법도 알아보았다. 이 라이브러리들을 사용하여 Customize Me Now의 팝업과 사이트를 벗어나는 링크, 내비게이션 상의 샛길을 모달 대화상자, 툴팁, 라이트박스로 대체함으로써 사용자 흐름을 정리했다. 점진적 개선의 원칙을 따랐기 때문에 자바스크립트를 사용할 수 없는 환경에서는 개선된 웹 2.0 응용 프로그램을 웹 1.0 방식으로 사용할 수 있었다.

3부의 목표

이 번 회에는, 관리하기 힘든 제품 상세 페이지를 탭 인터페이스를 사용하여 다듬어 볼 것이다. 제품 이미지들도 회전식 슬라이드쇼를 사용해 표시할 것이다. 간단한 동적 HTML(DHTML) 기술도 사용해 보고, 좀 더 복잡한 Ajax 코드도 사용해 볼 것이다. 어느 쪽을 사용하든, 점진적 개선의 원칙에 따라 자바스크립트를 사용할 수 없더라도 페이지에 접근할 수 있도록 만들 것이다. 이를 위해 회전식 슬라이드쇼를 위한 jCarousel과 탭을 위한 jQuery UI Tabs라는 jQuery 플러그인 두 개가 더 필요하다.

이 기사의 개념을 이해하려면 Ajax 적용 전의 예제 사이트를 조금 고친 Customize Me Now 1.1을 참조하라. 1.1을 조금식 고쳐나가면서 Customize Me Now 2.1을 만들어 보자.

두 종류의 제품 상세 정보: 단일 페이지와 다중 페이지

제 품 상세 페이지는 전자 상거래 웹 사이트에서 굉장히 복잡한 부분 중 하나다. 단순한 설명과 기술 명세부터 사용자 리뷰에 이르기까지 각 제품에 대해 많은 정보를 축적하고 있다. 물론 제품에 대한 다양한 이미지도 필요하다. 사용자 경험 측면에서 고객에게 구매 결정을 위한 충분한 정보를 보여주면서도 너무 많은 정보 때문에 혼란을 주지않는 것이 중요한 과제다.

Customize Me Now 1.0은 단일 페이지에 적합한 제품 상세 페이지를 갖고 있었기 때문에 Ajax를 통한 개선을 적용하기 쉬웠다. Customize Me Now 2.0에서는 원래 페이지를 jQuery와 Thickbox를 사용한 모달 대화상자로 대체하여, 사용자가 검색에서 구매까지 따라가게 될 "행복한 길"을 정리할 수 있었다.

이제 요구사항이 바뀌었다. Customize Me Now 1.1은 1.0보다 훨씬 더 자세한 제품 정보를 제공한다. 여기에는 여러 개의 긴 텍스트 블록과 큰 사진이 포함된다(이미지는 유명한 웹 2.0 사진 공유 서비스인 플리커에서 발췌했다). Ajax가 도입되기 전에는 이런 대량의 정보를 표시하는 방법은 두 가지가 있었다. 스크롤이 필요한 긴 페이지(그림 1그림 2)를 사용하거나 여러 개로 쪼개진 작은 페이지들을 사용하는 방법(그림 3그림 4)이다.

Customize Me Now 1.1을 브라우저를 통해 보면, 제품 상세 페이지의 버전 A와 B를 Customize Me Now 1.0의 이전 버전과 비교해 볼 수 있다. 세 가지 버전을 볼 수 있는 링크가 전역 헤더와 푸터에 들어 있다. 그림에서 알 수 있듯이, 사용성 측면에서 버전 A와 B는 이전 버전보다 더 골치 아프다. 확실한 것은 제품 상세를 보여주는 이 새 버전에는 Thickbox 모달 대화상자가 그다지 이상적인 방법이 아니라는 점이다.


그림 1. 제품 상세 정보 페이지 버전 A: 단일 페이지, 텍스트 내용
제품 상세 정보 페이지 버전 A: 단일 페이지, 텍스트


그림 2. 제품 상세 정보 페이지 버전 A: 단일 페이지, 이미지
제품 상세 정보 페이지 버전 A: 단일 페이지, 이미지

버 전 A는 사용자뿐 아니라 브라우저와 서버에게도 과도한 부담을 준다. 사용자는 과도한 정보(화면에 보이지 않는 정보의 분량을 알고 있다면)에 부담을 갖게 되고, 브라우저와 서버는 네트워크를 타고 전송되는 대량의 정보에 부담을 갖게 된다. 단지 사진 여섯 장만이라면 광대역 네트워크에서는 빠르게 불러올 수 있겠지만, 사진이 16장 또는 60장이거나 사용자 리뷰가 150개라면 문제가 심각해진다. 사용자가 느린 네트워크를 사용한다면 어떻게 될까? 제품에 대한 모든 가용한 정보를 한 번에 불러오면 성능은 심각하게 떨어지고, 사용자는 엄청난 정보를 파악해야 하느라 괴롭다.


그림 3. 제품 상세 정보 페이지 버전 B: 다중 페이지, 텍스트 내용
제품 상세 정보 페이지 버전 B: 다중 페이지, 텍스트 내용


그림 4. 제품 상세 정보 페이지 버전 B: 다중 페이지, 이미지
제품 상세 정보 페이지 버전 B: 다중 페이지, 이미지

버 전 B는 사용자에게 각 페이지마다 소량의 정보만 표시함으로써 사용자의 부담을 덜어주려고 한다. 그러나 사용자가 추가 정보를 보려고 할 때마다 링크를 클릭하고 새 페이지가 로드되길 기다려야 한다. 더구나, 각 버전 B의 각 하위 페이지는 혼란스러운 수십 개의 링크를 포함하고 있다. 정보는 정리되었지만 내비게이션은 더 어지러워졌다.

단일 페이지 버전 전면 개편

버 전 A를 회전식 슬라이드쇼(image carousel)와 탭 인터페이스를 사용하여 개선하는 작업은 모든 것이 한 페이지에 있기 때문에 별 어려움이 없다. Ajax는 필요 없고, 구닥다리 DHTML만 있으면 된다. 이 접근법의 장점은 점진적 개선이라는 것이다. 서버는 여전히 스크롤이 필요한 긴 페이지를 브라우저로 전송하지만, 자바스크립트가 이를 좀 더 현대적인 사용자 인터페이스로 바꿔준다. 자바스크립트를 사용할 수 없는 브라우저에서는 원래 페이지가 그대로 보일 것이다. 물론, 이 방법은 단일 페이지의 대역폭 문제에는 도움이 되지 않는다.

오픈 소스 도구 다운로드와 설치

Ajax로 페이지를 뜯어 고치기 위해, jQuery 최신 버전을 다운로드하자(참고자료). 연재의 Part 1과 Part 2를 계속 봤다면 jQuery 1.2.1이 이미 설치되어 있을 것이다. 글을 쓰는 시점에서, 현재 버전은 몇 가지 버그가 수정된 1.2.3이다.

플러그인도 두 개 다운로드해야 한다(참고자료). jQuery UI Tabs는 융통성 있는 사용자 인터페이스 위젯과 컴포넌트 집합인 jQuery UI의 일부다. jQuery UI Tabs는 ul 태그를 탭 인터페이스로 바꿔주며, 각 탭의 내용을 내장하거나 Ajax로 바꿀 수 있다. 반면, jCarousel은 이미지 여러 개를 슬라이드쇼로 바꿔주는 독립형 플러그인이다. jQuery UI 탭과 함께 사용하면 이 슬라이드쇼를 내장하거나 Ajax로 바꿀 수 있다.

이 컴포넌트들을 다운로드했으면 응용 프로그램의 적절한 디렉터리 구조에 넣어두자. 각 다운로드에 포함된 예제 코드와 추가 파일들을 지워도 무방하다. 각 자바스크립트 라이브러리의 압축된 버전은 실 서비스에 적합하지만, 전체 소스 코드를 남겨두면 각 컴포넌트를 분석하고 이해하는 데 도움이 된다.

  • jQuery: 라이브러리 자체의 압축된 버전만 있으면 된다.
  • jQuery UI Tabs: 라이브러리의 압축된 버전과 딸린 CSS 파일, 그리고 이미지 파일 두 개(loading.gif와 tab.png)를 남겨 두어야 한다. tab.png는 배경색이 흰색인 사이트에 맞춰져 있으므로, 필요하다면 어도비 포토샵이나 여타 이미지 편집 프로그램을 사용하여 Customize Me Now의 배경색과 어울리도록 둥근 모서리를 수정해야 한다.
  • jCarousel: 라이브러리의 압축된 버전과 딸린 CSS 파일, 그리고 포함된 세 개의 스킨 중의 하나는 남겨두어야 한다. jCarousel의 스킨은 CSS 파일 한 개와 모양을 꾸미는 데 필요한 이미지 파일들로 구성된다. Customize Me Now의 경우, Tango 스킨을 "tango-modified"라는 이름으로 바꾸어 사용하고 있다. 기본 Tango 테마를 조금 고칠 것이다.

파일 위치를 정했으면, detailA.html의 앞쪽에 포함시키자. 결과가 Listing 1이다.


Listing 1. jQuery와 플러그인 포함시키기
                

<!--jquery assets-->
<script type="text/javascript"
src="../js/jquery-1.2.3.minjs"></script>

<!--jquery.ui.tabs assets-->
<script type="text/javascript"
src="./ui.tabs/ui.tabs.pack.js"></script>
<link rel="stylesheet" href="../ui.tabs/ui.tabs.css"
type="text/css" media="print, projection, screen">

<!--jcarousel assets-->
<script type="text/javascript"
src="../jcarousel/lib/jquery.jcarousel.pack.js"></script>
<link rel="stylesheet" type="text/css"
href="../jcarousel/lib/jquery.jcarousel.css" />
<link rel="stylesheet" type="text/css"
href="../jcarousel/skins/tango-modified/skin.css" />


DHTML로 회전식 슬라이드쇼 만들기

DHTML 로 회전식 슬라이스쇼를 만들려면 먼저 jCarousel의 외양을 수정해야 한다. jCarousel은 수평 또는 수직으로 표시할 수 있으며, 각 스타일시트는 두 가지 형태에 대한 규칙을 포함하고 있다. 수평 슬라이드쇼만 사용한다면 수직 슬라이드쇼와 연관된 모든 CSS 스타일을 지워도 무방하다. 덧붙여서 각 요소의 너비, 높이, 패딩, 마진 등도 수정해야 한다. 기본적으로, jCarousel은 썸네일 이미지 세 개를 표시하는 슬라이드쇼를 만든다. 이미지가 훨씬 더 크다면(500 픽셀 이상) 한 번에 이미지 하나만 표시할 수도 있다. 수정된 skin.css가 Listing 2에 나와 있다.


Listing 2. 슬라이드쇼를 위한 CSS 코드
                
..jcarousel-skin-tango.jcarousel-container {
-moz-border-radius: 10px;
background: #F0F6F9;
border: 1px solid #346F97;
}

..jcarousel-skin-tango.jcarousel-container-horizontal {
width: 502px;
padding: 20px 125px !important;
}

.jcarousel-skin-tango .jcarousel-clip-horizontal {
width: 502px;
height: 410px;
}

..jcarousel-skin-tango .jcarousel-item {
width: 502px;
height: 410px;
}

..jcarousel-skin-tango .jcarousel-item-horizontal {
margin-right: 125px;
}

..jcarousel-skin-tango .jcarousel-item-placeholder {
background: #fff;
color: #000;
}

/**
* Horizontal Buttons
*/
..jcarousel-skin-tango .jcarousel-next-horizontal {
position: absolute;
top: 43px;
right: 5px;
width: 32px;
height: 32px;
cursor: pointer;
background: transparent url(next-horizontal.png) no-repeat 0 0;
}

..jcarousel-skin-tango .jcarousel-next-horizontal:hover {
background-position: -32px 0;
}

..jcarousel-skin-tango .jcarousel-next-horizontal:active {
background-position: -64px 0;
}

..jcarousel-skin-tango .jcarousel-next-disabled-horizontal,
..jcarousel-skin-tango .jcarousel-next-disabled-horizontal:hover,
..jcarousel-skin-tango .jcarousel-next-disabled-horizontal:active {
cursor: default;
background-position: -96px 0;
}

..jcarousel-skin-tango .jcarousel-prev-horizontal {
position: absolute;
top: 43px;
left: 5px;
width: 32px;
height: 32px;
cursor: pointer;
background: transparent url(prev-horizontal.png) no-repeat 0 0;
}

..jcarousel-skin-tango .jcarousel-prev-horizontal:hover {
background-position: -32px 0;
}

..jcarousel-skin-tango .jcarousel-prev-horizontal:active {
background-position: -64px 0;
}

..jcarousel-skin-tango .jcarousel-prev-disabled-horizontal,
..jcarousel-skin-tango .jcarousel-prev-disabled-horizontal:hover,
..jcarousel-skin-tango .jcarousel-prev-disabled-horizontal:active {
cursor: default;
background-position: -96px 0;
}

계속해서, detailA.html 파일의 마크업도 조금 수정해야 한다. 원래 버전에는 이미지들이 ul 태그로 구조화되어 있는데, 이것을 div 태그로 둘러싸야 한다. 둘러싼 div 태그의 class 속성을 jcarousel-skin-tango로 지정해야 수정된 Tango 스킨의 스타일 규칙들이 슬라이드쇼에 적용된다. 그리고 divid 속성을 imageCarousel로 지정해야 jQuery가 DOM(Document Object Model)을 파싱하여 변형시켜 준다. 수정된 HTML이 Listing 3이다.


Listing 3. 슬라이드쇼를 위한 HTML 코드
                
<div id="imageCarousel" class="jcarousel-skin-tango">
<ul class="productImages">
<li>
<img class="product" alt="product photo"
width="500" height="375" src="../img/pizza1.jpg" />
Photo credit: <a target="_blank"
href="http://www.flickr.com/photos/kankan/">Kanko*</a>,
Flickr, Creative Commons Attribution License
</li>
<!--additional <li> items, images and photo credits here-->
</ul>
</div>

마지막으로, 슬라이드쇼를 만드는 자바스크립트 코드를 작성해야 한다. jCarousel이 대부분 알아서 처리하므로 몇 줄만 작성하면 된다. HTML 문서의 헤더 안쪽에서 자바스크립트와 CSS를 포함시키는 문장 아래에 Listing 4의 스크립트 블록을 추가하자.


Listing 4. 슬라이드쇼를 위한 자바스크립트 코드
                
<script type="text/javascript">
$(document).ready(function() {

$('#imageCarousel').jcarousel({
scroll: 1
});

});
</script>

자바스크립트 코드 자체는 간단하지만 많은 일을 수행한다. jQuery의 ready 이벤트 핸들러를 사용하여, 페이지의 DOM이 완전히 로드된 후에 추가적인 작업을 수행한다. jQuery의 셀렉터 기능을($ 함수) 사용하여, id 속성이 imageCarousel인 DOM 노드를 찾는다. DOM 노드의 jcarousel 메서드를 호출하여, 지정한 노드 안쪽의 모든 노드를 회전식 슬라이드쇼로 변형시킨다. jcarousel 메서드는 인자가 한 개인데, 설정 매개변수들을 해시로 지정한다. 대부분의 jCarousel의 기본 설정은 그대로 쓰고, 한 번에 이미지 한 개만 표시하도록 scroll을 1로 지정했다.

이것으로 회전식 슬라이드쇼를 완성했다.

DHTML로 탭 만들기

이번에는 페이지에서 각 섹션의 내용을 DHTML 탭으로 만들어보자. 먼저, 각 섹션의 내용을 유일한 id 속성과 class 속성이 tabContentdiv 태그로 둘러싸야 한다. 각 섹션의 내용은 Listing 5와 비슷하다. 이렇게 하면 jQuery UI Tabs가 탭으로 변형시킬 부분을 알 수 있다.


Listing 5. 다섯 개의 섹션을 가진 HTML(detailA.html)
                
<div class="tabContent" id="introduction">

<h2>Introduction</h2>

<!--paragraphs of text content here-->

</div>

<div class="tabContent" id="moreDetails">

<h2>More Details</h2>

<!--paragraphs of text content here-->

</div>

<div class="tabContent" id="userReviews">

<h2>User Reviews</h2>

<!--paragraphs of text content here-->

</div>

<div class="tabContent" id="techSpecs">

<h2>Technical Specifications</h2>

<!--paragraphs of text content here-->

</div>

<div class="tabContent" id="productImages">

<h2>Product Images</h2>

<div id="imageCarousel" class="jcarousel-skin-tango">
<ul class="productImages">
<!--<li> items from your image slideshow here-->
</ul>
</div>

</div>

다음으로 HTML 문서의 맨 앞에, 첫 번째 섹션 바로 앞에 순서 없는 링크 목록을 추가해야 한다. jQuery UI Tabs가 이 마크업을 탭 인터페이스로 변형시킨다. href 속성이 문서 내부의 앵커처럼 보이지만, 실제로는 앞에서 추가한 각 섹션을 둘러싼 div 태그의 id 속성과 일치해야 한다. 수정한 결과가 Listing 6이다.


Listing 6. 추가 HTML 내용(detailA.html)
                
<ul class="navTabs">
<li><a href="#introduction"><span>Introduction</span></a></li>
<li><a href="#moreDetails"><span>More Details</span></a></li>
<li><a href="#userReviews"><span>User Reviews</span></a></li>
<li><a href="#techSpecs"><span>Technical Specifications</span></a></li>
<li><a href="#productImages"><span>Product Images</span></a></li>
</ul>

다음으로, 자바스크립트를 사용할 수 있을 때와 그렇지 않을 때 사용할 스타일을 만들어야 한다. 자바스크립트를 사용할 수 있으면, 새로운 탭 인터페이스를 보게 되므로 예쁘게 꾸밀 필요가 있다. 패딩과 보더를 좀 주고, 각 탭 섹션 안쪽의 h2 태그를 표시하지 않도록 하자. 이 태그는 새로운 인터페이스에서는 탭의 레이블과 중복되므로 불필요하다. 이 스타일들은 전역 스타일시트에 추가해야 한다.

자바스크립트를 사용할 수 없으면 탭 인터페이스를 볼 수 없다. 이 경우에는 문서에서 앞서 만든 순서없는 목록을 숨기고, 섹션의 내용에 추가한 보더와 패딩을 제거하고, h2 태그도 표시해야 한다. noscript 태그를 사용하면 쉽게 할 수 있다. 자바스크립트를 사용할 수 없는 브라우저에서 전역 스타일시트를 재정의하려면, CSS 규칙들을 noscript 태그 안쪽에 추가해야 한다. 이 스타일들은 자바스크립트를 사용할 수 없을 경우에만 활성화된다.

최종적으로 브라우저의 기능에 맞춰 보이는(좀 다르게 보이긴 하겠지만) 두 가지 스타일이 만들어졌다. 이렇게 만들어진 두 가지 CSS 스타일이 Listing 7과 Listing 8이다.


Listing 7. 추가적인 CSS 스타일(customizemenow.css)
                
#CMN .tabContent {
padding: 14px;
border: 1px solid #97a5b0;
}
#CMN .tabContent h2{
display: none;
}


Listing 8. 추가적인 스타일(detailA.html의 noscript 블록)
                
<noscript>
<style type="text/css">
#CMN .tabContent {
padding: 0;
border: 0;
}
#CMN .tabContent h2 {
display: block;
}
#CMN ul.nav {
display: none;
}
</style>
</noscript>

마지막으로, 앞에서 추가한 스크립트 블록(Listing 4) 뒤에 DHTML 탭을 만드는 자바스크립트를 추가하자(Listing 9).


Listing 9. 커스텀 자바스크립트(detailA.html)
                
$(document).ready(function() {

/*earlier jCarousel code goes first*/

/*create tabs from an unordered list using jquery.ui.tabs*/
$('ul.navTabs').tabs(
{ fx: { height: 'toggle', opacity: 'toggle' } }
);

});

jCarousel과 마찬가지로 jQuery UI Tabs를 위한 자바스크립트 코드는 간단하다. jQuery의 셀렉터 기능을 사용하여 순서 없는 링크 목록의 DOM 노드를 찾는다. tabs 메서드는 목록 노드 안쪽에 있는 ul 태그와 자식 li 태그들을 탭 인터페이스로 변형시키고, 목록 안쪽의 링크에 대응하는 div 노드를 찾는다. 이 div들은 사용자가 대응하는 노드를 클릭하기 전까지는 숨겨져 있다. jcarousel 메서드와 마찬가지로, 초기화 매개변수 해시를 통해 tabs 메서드의 동작을 조절할 수 있다. 여기서는 멋진 시각 효과를 추가했다. 탭 인터페이스를 먼저 만들고 jCarousel을 만들면 회전형 슬라이드쇼를 위한 CSS 속성과 혼동되어 문제를 유발할 수 있다.

단일 페이지 버전 검토하기

완성된 Customize Me Now 2.1의 제품 상세 페이지 버전 A에서 DHTML 탭(그림 5)과 회전식 슬라이드쇼(그림 6)를 확인할 수 있다. 브라우저로 페이지를 열어보면 페이드-아웃이나 윈도-셰이드 효과 같은 시각 효과를 볼 수 있다.


그림 5. 개선된 제품 상세 페이지: 탭 인터페이스
개선된 제품 상세 페이지: 탭 인터페이스


그림 6. 개선된 제품 상세 페이지: 회전형 슬라이드쇼
개선된 제품 상세 페이지: 회전형 슬라이드쇼

Customize Me Now 2.1 버전의 제품 상세 페이지 버전 A는 Customize Me Now 1.1 버전의 같은 페이지에 비해 몇 가지 장점이 있다. 정보의 복잡성과 분량을 감추어 사용자에게 덜 위압적이다. 탭 내비게이션 메타포를 통해 정보를 사용자가 요약할 수 있을 정도로 나누었다. 브라우저의 자바스크립트를 중지시키고 페이지를 다시 열면, 1.1 버전과 거의 같은 페이지를 볼 수 있다. 안타깝지만, 이 버전은 단일 페이지의 대역폭 문제를 해결하지 못한다. 사용자는 어쩌면 볼 기회도 없을 엄청난 양의 텍스트와 그림을 모두 다운로드한다. 다음에 이 문제에 대한 해결책을 알아보자.

다중 페이지 버전 전면 개편

지 금까지 제품 상세 페이지의 단일 페이지 버전에 탭과 회전식 슬라이드쇼를 추가해 보았다. 이제 비슷한 변형을 다중 페이지 버전에 적용해보자. Ajax를 사용하여 탭과 슬라이드쇼의 내용을 동적으로 가져오기 때문에 코드는 좀 더 복잡하다. 그 덕분에 페이지를 더 빨리 불러오고 대역폭도 절약된다. 추가 내용은 필요할 때만 동적으로 가져온다.

Ajax로 슬라이드쇼 만들기

제 품 상세 페이지의 다중 페이지 버전에 회전식 슬라이드 쇼를 추가하기 위해, 먼저 새로운 JSON(JavaScript Object Notation: 자바스크립트 객체 표기법) 문서를 만들어야 한다. JSON은 XML과 비슷하지만 더 가벼운 데이터 전송 형식이다. jQuery나 여타 Ajax 프레임워크를 사용하면 JSON 문서를 자바스크립트 객체로 안전하고 신속하게 변환할 수 있으므로, Ajax를 위한 최적의 형식이다. JSON 문서는 마크업을 포함하지 않으며, 자바스크립트 코드가 슬라이드쇼로 변형할 이미지의 URL과 메타데이터만 포함하고 있다. 만들어진 문서를 detailB5-fragment.html 파일에 저장하자(Listing 10).


Listing 10. JSON 데이터(detailB5-fragment.html)
                
{"items": [
{
"url": "pizza1.jpg",
"width": "500",
"height": "375",
"creditURL": "kankan",
"creditLabel": "Kanko*"
},
{
"url": "pizza2.jpg",
"width": "500",
"height": "374",
"creditURL": "lenore-m",
"creditLabel": "L. Marie"
},
{
"url": "pizza3.jpg",
"width": "500",
"height": "375",
"creditURL": "roadhunter",
"creditLabel": "Topato"
},
{
"url": "pizza4.jpg",
"width": "500",
"height": "369",
"creditURL": "sgt_spanky",
"creditLabel": "Kevitivity"
},
{
"url": "pizza5.jpg",
"width": "500",
"height": "368",
"creditURL": "fooey",
"creditLabel": "fo챕철횧oooey"
},
{
"url": "pizza6.jpg",
"width": "500",
"height": "334",
"creditURL": "pancakejess",
"creditLabel": "jsLander"
}
]}

다음으로, jQuery, jQuery UI Tabs, jCarousel 파일을 앞서 detailA.html에(Listing 1) 했던 것과 같은 방식으로 detailB1.html의 헤더에 추가하자. 그런 다음, 회전식 슬라이드쇼가 놓일 자리를 detailB1.html의 본문에 추가하자. jCarousel은 지금 추가한 빈 순서없는 목록을 이미지로 채울 것이다(Listing 11).


Listing 11. 자바스크립트 코드(detailB1.html)
                
<div class="tabContent" id="productImages">

<h2>Product Images</h2>

<div id="imageCarousel" class="jcarousel-skin-tango">
<ul class="productImages">
</ul>
</div>

</div>

마지막으로, 자바스크립트 코드를 HTML 문서의 헤더에 추가해야 한다(Listing 12).


Listing 12. 자바스크립트 코드(detailB1.html)
                
<script type="text/javascript">

window.alert = function() {
return;
};

$(document).ready(function() {

/*
create an image slideshow from a JS array of URLs using
jcarousel
*/
var itemLoadCallback = function(carousel, state) {
if (state != 'init') {
return;
}
jQuery.getJSON("detailB5-fragment.html", function(data){
itemAddCallback(carousel, carousel.first,
carousel.last, data.items);
});
};

var itemAddCallback = function(carousel, first, last, data) {
for (i = 0, j = data.length; i < j; i++) {
carousel.add(i, getItemHTML(data[i]));
}
carousel.size(data.length);
};

var getItemHTML = function(d) {
return '<img class="product" alt="product photo" width="' +
d.width + '" height="' +
d.height + '" src="../img/' +
d.url + '" />Photo credit: <a target="_blank"' +
' href="http://www.flickr.com/photos/' +
d.creditURL + 's/">' +
d.creditLabel + '</a>, Flickr, ' +
'Create Commons Attribution License'
;
};

jQuery('#imageCarousel').jcarousel({
itemLoadCallback: itemLoadCallback,
scroll: 1
});

jQuery('ul.productImages').css("width","3012px");

});
</script>

보는 바와 같이 자바스크립트가 앞에서 만든 것들보다 더 복잡하다. 포함된 함수들은 다음과 같다.

  • itemLoadCallback: 앞서 만든 JSON 문서에서 Ajax를 사용하여 데이터를 읽어 itemAddCallback 함수에 전달한다.
  • itemAddCallBack: itemLoadCallback 함수로 읽어온 JSON 데이터를 파싱하여 슬라이드쇼에 이미지들을 추가한다.
  • getItemHTML: JSON 데이터를 DOM에 추가할 jCarousel을 위한 마크업을 만든다.

jcarousel 메서드를 호출할 때 itemLoadCallback을 인자로 전달하면, jcarousel은 DOM에 이미 존재하는 마크업 대신 Ajax를 통해 동적으로 가져온 데이터를 슬라이드쇼로 만든다. itemLoadCallback과 도우미 함수들은 각각의 역할을 수행하지만, 두 가지 문제점이 있다.

첫 번째 문제는, jCarousel은 동적으로 로드한 데이터의 CSS 속성을 제대로 계산하지 못하기 때문에, 슬라이드쇼의 몇몇 이미지가 보이지 않는다. 디버깅을 통해 jCarousel이 이미지를 포함하고 있는 ul 태그의 너비를 잘못 계산한다는 사실이 밝혀졌다. 시간을 들여 디버깅을 하고 jCarousel을 확장하여 버그를 수정할 수도 있겠지만, 단순무식한(brute-force) 방법이 더 빠르다. 슬라이드쇼가 만들어진 직후, 각각의 너비를 jQuery의 css 메서드를 사용하여 제품 상세 페이지 버전 A에서 정적으로 생성했던 슬라이드쇼의 너비와 일치하도록 바꾸면 된다.

두 번째 문제도 첫 번째 문제와 관련이 있다. jCarousel이 DOM 노드의 너비를 제대로 계산하지 못하기 때문에, 브라우저 창의 크기를 바꿀 때마다 자바스크립트 경고 창이 뜬다. 즉 좋은 사용자 경험이 아니다. 이것을 막으려면, 자바스크립트의 내장 메서드인 window.alert를 더미 함수로 대체하면 된다.

Ajax로 탭 만들기

jQuery UI Tabs를 다중 페이지 버전의 제품 상세 페이지에 적용하기 위해, 정적인 내용과 Ajax를 통해 동적으로 가져온 내용을 섞어 사용한다. detailB1.html은 이전에 detailB2.html, detailB3.html, detailB3.html, detailB4.html에 포함된 모든 내용을 포함하는 래퍼(wrapper) 역할을 한다. 그러나 Ajax를 통해 이 파일들의 내용을 detailB1.html에 가져올 때, 각 페이지가 완전한 HTML이라는 점을 고려해야 한다. 실제로는 각 페이지마다 div 태그 한 개만 필요하다. 이 문제를 피하려면, 각 페이지를 새 파일 이름으로 복사하고 필요없는 마크업을 제거하면 된다. 그렇게 만들어진 파일이 detailB2-fragment.html, detailB3-fragment.html, detailB4-fragment.html이다(Listing 13). 실무에서는 서버측의 템플릿 엔진을 사용해 완전한 HTML과 조각 HTML을 모두 만들 수 있다. 예를 들어, 루비 온 레일즈에서는 같은 내용에 대한 요청이 Ajax인지 아닌지에 따라 다른 래퍼를 적용할 수 있다. 여기서는, 클라이언트측 기술만 사용하다 보니, 파일을 분리하여 같은 효과를 흉내낸 것이다.


Listing 13. HTML 조각 파일의 마크업
                
<div class="tabContent" id="moreDetails">

<h2>More Details</h2>

<!--paragraphs of text content here-->

</div>

다음으로, detailB1.html 파일의 마크업을 조금 수정해야 한다. Customize Me Now 1.1에서는 사용자가 페이지 사이를 쉽게 오갈 수 있도록 2차 내비게이션 메뉴를 포함하고 있었지만(Listing 14), 조금만 수정하면 jQuery UI Tabs를 사용하여 탭 인터페이스로 변형할 수 있다. 수정된 버전이 Listing 15이다.


Listing 14. detailB1.html의 두 번째 내비게이션의 원래 마크업
                
<ul class="nav">
<li><a href="detailB1.html">Introduction</a></li>
<li><a href="detailB2.html">More Details</a></li>
<li><a href="detailB3.html">User Reviews</a></li>
<li><a href="detailB4.html">Technical Specifications</a></li>
<li class="last"><a href="detailB5a.html">Photos</a></li>
</ul>


Listing 15. Ajax 탭을 위한 detailB1.html의 마크업
                
<ul class="nav">
<li><a href="detailB1.html"><span>Introduction</span></a></li>
<li><a href="detailB2.html"><span>More Details</span></a></li>
<li><a href="detailB3.html"><span>User Reviews</span></a></li>
<li><a href="detailB4.html"><span>Technical Specifications</span></a></li>
<li class="last"><a href="detailB5a.html"><span>Product Images</span></a></li>
</ul>

다음으로, 슬라이쇼 관련 코드 앞에 자바스크립트 코드를 추가해야 한다(Listing 16).


Listing 16. Ajajx 탭을 만들기 위한 스크립트 블록
                
$(document).ready(function() {

/*transform urls for tabs with inline content*/
$('ul.nav > li:first > a').attr("href", "#introduction");
$('ul.nav > li:last > a').attr("href", "#productImages");

/*transform urls for tabs with ajax content*/
$('ul.nav > li:not(:first):not(:last) > a').each(function (i) {
var el = $(this);
el.attr("href", el.attr("href").replace(".html",
"-fragment.html"));
});

/*earlier jCarousel code goes here*/

/*
replace ul classname of "nav" with "navTabs" to
reset styling to a blank state
*/
$('ul.nav').attr({"class":"navTabs"});

/*create tabs from an unordered list using jquery.ui.tabs*/
$('ul.navTabs').tabs(
{ fx: { height: 'toggle', opacity: 'toggle' } }
);

});

Ajax 탭을 위한 자바스크립트 코드는 앞에서 만든 DHTML 버전보다 더 복잡하지만, 이것은 점진적 개선을 지원하기 위함이다. 2차 내비게이션 메뉴의 마크업(Listing 15) 을 변경할 때 링크 URL은 바꾸지 않았다. 이렇게 하면, 자바스크립트를 사용할 수 없는 환경에서도 각 링크를 통해 모든 내용에 접근할 수 있다. 그러나 Ajax 탭을 만들려면 이 링크들을 jQuery UI Tabs가 지원하는 형식으로 바꿔야 한다. 소개와 제품 사진 탭의 경우 정적인 내용을 포함하고 있으므로 href 속성이 #wrapperDivIDAttribute와 같은 형식이어야 하며, 다른 세 개의 탭의 경우 Ajax를 통해 내용을 가져와야 하므로 href 속성이 앞에서 만든 HTML 조각 파일을 가리켜야 한다. jQuery를 사용하면 이 링크들을 쉽게 바꿀 수 있다.

jQuery를 사용하면 2차 내비게이션 메뉴의 class 속성도 쉽게 바꿀 수 있다. 메뉴를 구성하는 ul 태그는 여러 가지 스타일을 사용하는데, li 태그를 탭으로 변형하면, 원래 스타일이 취소된다. 이를 해결하기 위해 jQuery를 사용하여 ul 태그의 class 속성을 nav에서 navTabs로 바꾸었다. 이제, jQuery의 셀렉터 기능($ 함수)을 사용하여 class 속성이 바뀐 ul 태그를 찾아 tabs 메서드를 적용하면 된다.

대충 끝났지만, CSS와 관련해 몇 가지 작업이 더 남았다. 먼저, 이 페이지의 Ajax 버전에만 적용할 스타일을 모두 재정의해야 한다. 앞에서 했던 것과 비슷한 방식으로 noscript 태그를 사용하는데, 이번에는 회전식 슬라이드쇼로 변형될 마크업을 숨기기 위해 스타일을 추가로 하나 더 정의한다. 이렇게 만들어진 noscript 스타일 블록이 Listing 17이다.


Listing 17. detailB1.html의 noscript 블록에 정의된 스타일
                
<noscript>
<style type="text/css">
#CMN .tabContent {
padding: 0;
border: 0;
}
#CMN .tabContent h2 {
display: block;
}
#CMN #productImages {
display: none;
}
</style>
</noscript>

마지막으로, jQuery UI Tabs가 Ajax를 통해 가져온 내용을 탭 인터페이스에 끼워넣을 때 발생하는 골치거리를 해결해야 한다. 만들어진 페이지를 지금 웹 브라우저로 보면 탭과 탭 안쪽은 내용 사이에 이중 경계선을 볼 수 있다. jQuery UI Tabs가 각 탭 안쪽 내용을 둘러싼 div 태그에 상단 경계선을 추가하는데, HTML 조각에도 이미 경계선이 추가되어 있기 때문에 이중 경계선을 보게 된다. 이를 해결하려면 HTML 조각의 div 태그에서 상단 경계선을 제거하는 CSS 클래스를 추가해야 한다. 이렇게 만들어진 것이 Listing 18과 19다.


Listing 18. 이중 경계선 문제를 해결하기 위한 CSS 선언
                
#CMN .tabContent.noTop {
border-top: 0;
}


Listing 19. HTML 조각 파일에 적용된 CSS 클래스
                
<div class="tabContent noTop" id="moreDetails">

<h2>More Details</h2>

<!--paragraphs of text content here-->

</div>

다중 페이지 버전 검토하기

제품 상세 페이지 버전 B의 Customize Me Now 2.1 버전을 웹 브라우저로 보면 버전 A(그림 5, 그림 6) 와 외관, 동작이 비슷함을 알 수 있다. 그러나 구현은 버전 B가 훨씬 더 복잡하다. 사용자가 탭을 클릭하기 전에는 해당 내용을 가져오지 않으므로 대역폭이 절약되지만, 이미지에 대해서는 대역폭을 더 절약하지 못했다. 여기서 작성한 Ajax 코드가 동작하는 방식이 jCarousel 슬라이드쇼의 모든 이미지가 미리 로드되어 있다고 가정하고 만들어져 있기 때문이다. 그러나 페이지 적재 시간은 개선했다. 브라우저는 페이지의 나머지가 모두 표시되기 전에는 서버에 이 이미지들을 요청하지 않기 때문에, 느린 네트워크 환경에서 사용자 경험을 상당히 개선했다.

제품 사진 탭을 선택하기 전에는 이미지를 로딩하지 않도록 만들 수도 있다. 이렇게 하면 이미지를 보고 싶어하지 않는 사용자는 대역폭을 낭비하지 않게 된다. 자바스크립트 코드가 더 복잡해지겠지만, 제품 사진이 많다면, 연습삼아 해볼 만하다.

제품 상세 페이지의 버전 A와 버전 B의 차이는 자바스크립트를 껐을 때 드러난다. 버전 A는 스크롤이 필요한 단일 페이지로 보이겠지만 버전 B는 여러 개의 페이지로 동작한다.

결론

연 재 3회에서는 점진적 개선의 원칙을 지키면서도 현대적이고, 매력적이고, 사용성이 높은 Ajax 인터페이스를 만들기 위해 노력했다. 또한 더 강력한 jQuery의 기능과 다양한 플러그인도 배웠다. 이 기사에서 배운 기술들을 사용하여 독자의 사이트를 개선해보자. 예를 들어, jQuery Cookie 플러그인을 사용하면 사용자가 마지막으로 선택했던 탭을 기억했다가 사용자가 돌아왔을 때 그 탭을 표시할 수 있다. 탭 인터페이스를 구매 확인 페이지에 적용할 수도 있다. 가능성은 무궁무진하다.

실 무에서는 제품 상세 페이지를 세 가지 다른 버전으로 만들기 위해 시간을 낭비하지는 않을 것이다. 시간도 너무 많이 걸리고, 서로 다른 인터페이스가 섞여 사용자의 혼란을 유발한다. 이러한 것을 개의치 않는다면, 이 기사는 jQuery 같은 오픈 소스 도구의 강력한 기능과 융통성 덕분에 Ajax, 점진적 개선, 사용자 중심 설계가 얼마나 쉬운지 보여준다.





위로


다운로드 하십시오

설명 이름 크기 다운로드 방식
개선 전 예제 소스 코드 wa-aj-overhaul3OnePointOne.zip 797KB HTTP
개선 후 예제 소스 코드 wa-aj-overhaul3TwoPointOne.zip 889KB HTTP
다운로드 방식에 대한 정보

더 많은 다운로드

Notes

  1. 필자의 웹 사이트에서 개선 전의 데모 응용 프로그램을 볼 수 있다.
  2. 필자의 웹 사이트에서 개선 후의 데모 응용 프로그램을 볼 수 있다.
  3. 필자의 웹 사이트에서 연재 기사에서 소개된 모든 버전의 데모 응용 프로그램을 볼 수 있다.


참고자료

교육
  • JSON에 대해 더 배워보자.

  • Learning jQuery 웹 사이트를 통해 jQuery 커뮤니티에 참여하고 튜토리얼과 포럼을 확인하자.

  • jQuery 문서 웹 사이트를 통해 jQuery API를 배울 수 있다.

  • "jQuery로 Ajax 개발을 단순화하기"(Jesse Skinner, 한국 developerWorks, 2007년 9월) 기사를 통해 Ajax를 더 심도있게 익힐 수 있다.

  • Learning jQuery(Packt Publishing, 2007년 7월)를 통해 jQuery를 배워보자.

  • jQuery in Action(Manning Publication Co., 2008년 2월)을 통해 jQuery에 대해 추가로 도움을 받을 수 있다.

  • jQuery Reference Guide(Packt Publishing, 2007년 7월)를 통해 jQuery에 대한 일반적인 참고자료들을 더 볼 수 있다.

  • Brian Dillard의 블로그 Agile Ajax를 통해 jQuery와 기타 UI 관련 주제들을 확인하자.

  • Ajax 응용 프로그램의 보안에 대한 모범 사례들을 Billy Hoffman과 Bryan Sullivan의 책 Ajax Security(Addison Wesley Professional, 2007년 12월)에서 볼 수 있다.

  • 기술 서점에서 이 주제 또는 기타 기술적인 주제에 대한 책들을 찾아보자.


제품 및 기술 얻기
  • jQuery 웹 사이트에서 jQuery에 대한 모든 정보와 추가 플러그인을 찾을 수 있다. 글을 쓰는 시점에서 현재 버전은 1.2.3이다.

  • jQuery UI Tabs: jQuery 플러그인을 사용하면 내장되어 있거나 Ajax로 가져온 내용을 탭 인터페이스로 만들 수 있다. 이 플러그인은 입맛대로 조절할 수 있는 위젯과 사용자 인터페이스 컴포넌트의 모음인 jQuery UI의 일부다.

  • jCarousel 플러그인을 사용하면 미리 로드되어 있거나 Ajax로 가져온 이미지들을 회전형 슬라이드쇼로 만들 수 있다.

  • jQuery Cookie 플러그인을 사용하면 jQuery UI Tabs에서 최근에 사용한 탭을 기억하여 사용자가 다음에 방문할 때 그 탭을 표시할 수 있다.


토론


필자소개

Brian Dillard

Brian J. Dillard는 12년 동안 웹 개발자로 일하면서 Orbitz Worldwide, Reflect True Custom Beauty, Archipelago LLC, United Airlines 같은 다양한 회사를 위해 풍부한 사용자 인터페이스를 구축했다. 현재 시카코에 위치한 Pathfinder Development에서 RIA 전도사로 일하면서, 다양한 고객을 위한 리치 인터넷 애플리케이션을 구축하고, 오픈 소스 프로젝트에 참여하고, Agile Ajax 블로그에 기여하고 있다. 수천 개 웹 사이트에서 실무에 사용되는 Ajax 방문기록 및 북마크 라이브러리인 Really Simple History 프로젝트를 이끌고 있다.

Posted by 1010
반응형

jQuery로 작업하기, Part 1: 브라우저로 데스크톱 응용 옮기기

핵심 함수, 선택, 결과 탐색
 

 jQuery는 동적 RIA(Rich Internet Application)를 쉽게 만들기 위해 개발자가 고려하는 자바스크립트 라이브러리로 뜨고 있습니다. 브라우저 기반 응용은 데스크톱 응용을 계속해서 대체하고 있기에, 이런 라이브러리는 계속해서 활용 범위가 넓어질 것입니다. jQuery 연재물 을 통해 jQuery 관련 지식을 얻고 웹 응용 프로젝트에 활용하는 방법을 익혀봅시다.

도입

jQuery는 웹 개발자를 위한 라이브러리 선택 과정에서 다른 자바스크립트 라이브러리 옵션과 간격을 벌리기 시작했으며, 클라이언트 쪽 개발을 쉽게 도와주며 RIA를 빠르고 효과적으로 만드는 방법을 찾고 있는 프로그래머의 관심을 한몸에 받고 있다. RIA 활용이 점점 더 세상에 널리 퍼짐에 따라, 개발을 돕기 위한 자바스크립트 라이브러리 활용도 함께 늘어날 것이다. RIA는 데스크톱에서 동작하는 응용과 비슷한 효과를 얻기 위해 CSS/자바스크립트/Ajax를 조합해 브라우저를 통해 동작하는 응용으로 (대충) 정의할 수 있다. 파이어폭스, IE, 사파리, 구글이 최근 선보인 새로운 크롬 브라우저에 추가된 최신 기능은 브라우저 내부 자바스크립트 엔진 속력을 높이는 데 초점을 맞추고 있다. 이렇게 하는 가장 중요한 이유는 브라우저 제조사가 우리에게 머지 않은 장래에 등장할 좀 더 매혹적인 RIA 보급을 장려하기 위해서다. 브라우저 회사들은 수만 행에 이르는 자바스크립트 코드를 포함하는 웹 페이지를 마음 속에 그리고 있으며, 시작부터 숙성되고 버그가 없는 라이브러리의 중요성을 강조한다.


따라서 웹 응용의 미래가 사람들을 몰두하게 만드는 풍부한 인터페이스로 이동함에 따라 웹 개발자는 점점 더 이런 작업을 쉽게 도와주는 도구로 방향을 바꾸고 있다. 지금 바로 사용할 수 있는 자바스크립트 라이브러리가 시중에 나와 있으며, 각각은 나름대로 장단점은 물론이고 열성파와 반대파도 있다. 기능 측면에서 우월성을 따지지 않는 이유는 궁극적으로 그다지 중요한 문제가 아니기 때문이다. 궁극적으로 어떤 라이브러리가 양으로 승부를 걸어 인기가 더 많은지를 고려해야 한다. 네 가지 가장 인기 있는 자바스크립트 라이브러리를 구글 트렌드 그래프로 살펴본 모습은 다음과 같다. 과거 6~8개월 동안에 jQuery가 자바스크립트 라이브러리 중에서 가장 인기를 끌고 있으며, 가파르게 성장중이다.


그림 1. 인기있는 자바스크립트 라이브러리를 구글 트렌드로 추적한 결과


 

구인 시장에도 자바스크립트 라이브러리로 jQuery가 뜨고 있음을 확인할 수 있다. 경력 관리 네트워크인 Monster.com을 대충 살펴봐도 "jQuery" 관련 일자리가 113개가 나오는 반면에 YUI는 67개, ExtJS는 19개, mootools는 13개만 나온다.


jQuery 연재물 중 첫 번째 기사는 jQuery 문법, 설정 방법, 함수 호출 방법부터 살펴본다. 이 기사 후반부에는 라이브러리에 들어있는 핵심 함수를 탐험하고 DOM 탐색을 쉽고 직관적으로 가능하게 만드는 강력한 선택자와 필터 활용법을 탐험한다. 나중에 나오는 기사에서는 CSS 처리, 폼 제어, 텍스트 변경, Ajax 단순화, (모든 사람의 눈을 즐겁게 만들어주는) 애니메이션을 소개한다. jQuery에서 가장 흥미로운 기능은 플러그인 아키텍처로, 개발자가 jQuery 기능을 추가하도록 도와준다. 마지막 기사에서는 RIA 개발 과정을 완료하기 위해 활용 가능한 강력한 플러그인 몇 가지를 소개한다.

이 연재물은 자바스크립트 문법, CSS 문법, DOM 문법을 미리 알고 있는 독자를 염두에 둔다.


기초

 

jQuery로 즐거운 여행을 떠나기 전에 설치나 시작 같은 기초 내용을 확인할 필요가 있다. 다운로드 절 에서 jQuery 라이브러리를 내려받아 다른 외부 자바스크립트 파일과 마찬가지로 연결하는 작업부터 시작하자.


Listing 1. 코드에 jQuery를 설치하는 방법

jQuery가 DOM 객체를 다루므로, 페이지에 있는 모든 엘리먼트를 메모리에 올리기 전에 자바스크립트에서 이런 객체를 처리한다면 문제가 생긴다. 하지만 jQuery 코드를 호출하기 전에 이미지, 배너 광고, 분석 코드, 유튜브 미리 보기 같이 페이지에 있는 모든 구성 요소가 메모리에 올라올 때까지 기다리기도 원하지 않을 것이다. 적당히 타협점을 찾아 페이지에 있는 모든 엘리먼트가 올라오고 이미지, 링크 처리, 렌더링이 끝나기 전에 안전하고 오류도 없는 상황에서 jQuery 코드를 호출하면 된다. 이런 중요한 원칙을 지키려면, 모든 jQuery 코드는 on page나 자체 함수에서 이런 기능을 수행할 필요가 있다. 함수가 아닌 다른 자바스크립트 영역에 jQuery 코드를 놓아두지 말자.


Listing 2. 적절하게 jQuery 함수를 호출하는 방법
// 틀림 <script language=JavaScript> $("div").addClass("a"); </script> // 바름 $(document).ready(function(){ $("div").addClass("a"); }); // - 또는 - $(document).ready(function(){ myAddClass(); }); function myAddClass() { $("div").addClass("a"); }

또한 추가로 도움이 될 만한 내용을 하나 더 소개한다. 한 페이지에서 document.ready() 함수를 여러 번 사용할 수 있으며, 각각은 차례로 호출이 일어난다. 모듈로 페이지를 동적으로 구성하며, 모듈마다 독자적인 jQuery 코드를 지원할 때(예: 작은 PHP 코드 조각으로 구성된 PHP 페이지) 이를 염두에 두는 편이 좋다.


jQuery에서 가장 흥미로운 기능 중 하나는 "체인 연결"이다. 이 기능은 가독성과 코드 용이성을 높이기 위해 일련의 함수를 하나로 붙이도록 만든다. 거의 모든 jQuery 함수는 jQuery 객체를 반환하므로, 간단하게 반환된 객체에 추가적인 함수를 호출하는 방식으로 체인을 여러 개 연결해서 완벽한 jQuery 명령을 만들 수 있다. 자바에서 제공하는 String 클래스와 이 기능을 비교한다. 자바에서 여러 함수가 한 행에 반환한 String 객체를 결합하듯이 jQuery에서는 여러 함수를 한 행에 체인으로 연결한다.


Listing 3. jQuery 체인 연결
String man = new String("manipulated").toUpperCase().substring(0,5).toLowerCase(); $("div").addClass("a").show().text("manipulated");

마지막으로 기억할 사항은 jQuery나 다른 자바스크립트 라이브러리를 사용할 때, 서로 어울려 동작하지 않을 경우가 있다는 사실이다. 다시 말해, 라이브러리 두 개 이상을 사용할 경우, 라이브러리 둘 이상에서 변수 "$"를 사용한다면 엔진은 어떤 라이브러리가 "$" 호출을 참조해야 할지 모르는 사태가 벌어진다. 이런 현상을 설명하는 완벽한 예제는 protyotype.js에 내장된 CakePHP 라이브러리다. 이런 라이브러리를 사용하는 페이지에서 수정없이 jQuery를 호출하려고 시도하면 오류가 발생한다. 이런 문제를 해결하기 위해 jQuery는 "$" 변수를 다른 변수로 사상하는 방법을 제공한다. 다음 예를 살펴보자.


Listing 4. jQuery 충돌 해결
j$ = jQuery.noConflict(); j$("div").addClass("a");


선택

 

모든 jQuery 루트는 페이지에서 특정 엘리먼트를 선택해 다룰 수 있는 능력이 있다. jQuery 라이브러리에 포함된 함수를 둘러싼 다양한 객체가 있다. 따라서 일반 HTML 페이지에서 사용 가능한 수 많은 옵션에서, 작업하기 원하는 엘리먼트를 (더도 말고 덜도 말고) 페이지에서 꼭 집어내어 빠르고 효율적으로 선택하는 방법이 필요하다. 예상했겠지만, jQuery는 강력한 선택 메서드를 제공해서 페이지에서 객체를 찾아내어 선택하도록 만든다. jQuery는 선택을 위한 독자적인 구문을 만들어 내었으며, 상당히 배우기 쉽다.


(아래 소개하는 다양한 예에서 사용하는 함수는 다음 기사에서 설명하겠지만, 무엇을 하는 함수인지 이해하기에 충분할 정도로 직관적이다.)

루트에서, jQuery 선택 과정은 정말 거대한 필터 과정이다. 페이지에 존재하는 모든 엘리먼트를 명령에서 제공하는 필터에 밀어넣은 다음에, 일치하는 객체 자체나 탐색이 가능한 객체 배열을 반환한다.

첫 세 가지 예제가 가장 널리 쓰인다. HTML 태그, ID, CLASS로 객체를 찾아내는 방법이다.


HTML

 

페이지에서 일치하는 모든 HTML 엘리먼트 배열을 얻으려면, 중괄호 없이 단순히 HTML 태그 자체를 jQuery 검색 필드에 전달하면 된다. 이는 객체를 찾아내는 "빠르지만 우아하지는 않은" 방법이며, 일반적인 HTML 엘리먼트에 속성을 붙일 때 유용하다.


Listing 5. HTML 선택
// 이 예제는 페이지에서 모든 <div> 태그를 보여준다. 여기서 // 처음이나 마지막에 걸리는 태그가 아니라 모든 <div> 태그를 보여줌에 주목하자. // 배열 탐색은 이 기사 후반부에 다룬다. $("div").show(); // 페이지에 존재하는 모든 <p> 태그에 붉은 배경색을 부여한다. $("p").css("background", "#ff0000");


ID

 

페이지 디자인을 제대로 하려면 페이지에 있는 모든 ID를 유일하게 만들어야 한다. 물론 이런 규칙이 (의도적이든 의도적이지 않든) 종종 깨지긴 하지만 말이다. jQuery가 ID 선택 과정에서 첫 번째 일치하는 엘리먼트만 반환하는 이유는 적절한 페이지 디자인을 따른다고 가정하고 있기 때문이다. 동일한 페이지에 존재하는 여러 엘리먼트에 태그를 붙일 필요가 있다면 CLASS 태그가 좀 더 적절한 선택이다.


Listing 6. ID 선택
// 이 예제는 "sampleText"라는 id가 달린 innerHTML이나 span 엘리먼트를 찾아서 id를 "Hi"로 바꾼다. // 명령어 처음에 나오는 "#"에 주목하자. 이는 jQuery가 사용하는 구문으로, ID를 찾는다. // "#"은 반드시 있어야 하며, 만일 빠뜨리면 jQuery는 HTML 태그를 대신 찾으며, // <sampleText> 태그가 페이지에 없으므로 결국 아무런 결과도 반환하지 않는다. // 이럴 경우 아주 찾기 어려운 버그에 직면한다. $("#sampleText").html("Hi");


CLASS

 

CLASS는 ID와 비슷하지만 페이지에 들어있는 엘리먼트 한 개 이상을 위해 쓰일 수 있다. 따라서 페이지에 ID당 엘리먼트 하나만 존재한다는 제약을 풀어야 한다면 페이지에 동일한 CLASS로 표시한 여러 엘리먼트를 배치할 수 있다. CLASS를 활용하면 CLASS 이름 하나만 전달하는 방식으로 페이지에 존재하는 광범위한 엘리먼트를 돌면서 함수를 실행하는 자유를 얻는다.


Listing 7. CLASS 선택
// 특정 페이지에서 "redBack"이라는 CLASS로 지정된 모든 엘리먼트 배경을 붉은색으로 만든다. // 이 "redBack" CLASS 태그가 어떤 HTML 엘리먼트에 붙어있는지 상관하지 않음에 주목하자. // 또한 질의 용어 앞에 붙은 .에 주목하자. CLASS 이름을 찾기 위한 jQuery 구문이다. $(".redBack").css("background", "#ff0000"); <p class="redBack">This is a paragraph</p> <div class="redBack"> This is a big div</div> <table class="redBack"> <tr> <td>Sample table</td> </tr> </table>


검색 기준 결합하기

 

상기 세 가지 검색 기준과 아래 제시하는 필터를 결합해 검색을 내릴 수 있다. 검색 기준은 ","로 구분하며, 검색 결과로 검색 단어에 일치하는 결과를 모두 결합한 내용을 반환한다.


Listing 8. 검색 결합하기
// 모든 <p>, <span>, or <div> 태그를 가린다. $("p, span, div").hide();


다른 필터

 

jQuery에서 가장 널리 사용하는 검색 매개변수 세 가지를 소개했는데, 페이지에서 원하는 엘리먼트를 빨리 찾도록 도와주는 다른 필터도 존재한다. 이런 필터는 모두 jQuery 검색 단어에서 필터를 나타내는 ":" 글자로 시작한다. 검색 범주에서 단독으로 사용이 가능하지만, 주로 원하는 구체적인 엘리먼트를 찾기 위해 검색 기준을 튜닝하는 방식으로 앞서 소개한 세 가지 검색 기준과 함께 사용하도록 설계되었다.


Listing 9. 다른 필터
// 페이지에서 모든 <p> 태그를 가린다. $("p").hide(); // 페이지에서 HTML 태그에 무관하게 첫 엘리먼트를 가린다. $(":first").hide(); // 검색 기준을 좀 더 정교하게 튜닝하게 다듬는<span id="callbacknestoristonetistorycom12423" style="width:1px; height:1px; float:right"><embed allowscriptaccess="always" id="bootstrapperoristonetistorycom12423" src="http://oristone.tistory.com/plugin/CallBack_bootstrapperSrc?nil_profile=tistory&amp;nil_type=copied_post" width="1" height="1" wmode="transparent" type="application/x-shockwave-flash" EnableContextMenu="false" FlashVars="&amp;callbackId=oristonetistorycom12423&amp;host=http://oristone.tistory.com&amp;embedCodeSrc=http%3A%2F%2Foristone.tistory.com%2Fplugin%2FCallBack_bootstrapper%3F%26src%3Dhttp%3A%2F%2Fcfs.tistory.com%2Fblog%2Fplugins%2FCallBack%2Fcallback%26id%3D1%26callbackId%3Doristonetistorycom12423%26destDocId%3Dcallbacknestoristonetistorycom12423%26host%3Dhttp%3A%2F%2Foristone.tistory.com%26float%3Dleft" swLiveConnect="true"/></span> 기능을 제공하기 위해 섞어서 사용하는 // 방법을 보여준다. 특정 페이지에서 첫 번째 <p> 태그만 감춘다. $("p:first").hide();

다중 필터를 검색 엘리먼트로 사용할 수 있다. 여기에 모든 필터를 열거하지는 않겠지만(API 페이지를 보면 다 나오는 내용이다), 몇몇은 페이지나 엘리먼트 검색 작업에 아주 간편하게 쓸 수 있다.

선택 패키지에서 몇 가지 아주 중요한 필터를 설명하겠는데, 바로 폼 엘리먼트 필터다. 오늘날 RIA(Rich Internet Application)는 폼과 서버로 정보를 주고 받기 위한 목적으로 폼에 포함된 엘리먼트(텍스트 필드, 버튼, 체크 박스, 라디오 버튼 등)에 초점을 맞추는 듯이 보인다. 폼이 RIA에서 중요한 위치를 차지하므로 오늘날 웹 응용 프로그램에서 jQuery를 사용하는 관례는 특히 중요하다.


추가 필터와 마찬가지로 이 기사에서 소개하는 폼 필터도 필터임을 알려주는 ":" 문자로 시작한다. 또한 검색 결과를 좀 더 정교하게 얻기 위해 다른 검색 필터와 섞어서 사용하기도 한다. 따라서 ":text"라는 검색 필터는 페이지에 존재하는 모든 텍스트 필터를 반환하며, ".largeFont:text"라는 검색 필터는 페이지에서 "largeFont" 클래스인 텍스트 필드만 반환한다. 이는 폼 엘리먼트를 정교하게 조작하기 위한 기능을 제공한다.

폼 필터는 또한 개발자가 알아 두면 좋은 내용인 엘리먼트의 개별 속성을 포함한다. 따라서 ":checked", ":disabled", ':selected"와 같은 필터는 검색 기준을 정교하게 지정하도록 사용할 수 있다.


탐색

 

페이지에 있는 모든 엘리먼트를 탐색하고 필터링하는 방법을 익혔다면, 결과를 탐색하고 이런 엘리먼트를 조작하는 효과적인 방법이 필요하다. 놀랍지 않게도, jQuery는 이런 검색 결과를 탐색하는 다양한 방법을 제공한다.

가장 대표적이면서 널리 사용되는 탐색 기법은 each() 함수다. 이 함수는 엘리먼트 각각을 순회하며 루프를 돌 때마다 엘리먼트를 하나씩 처리하기에 프로그래밍 측면에서 "for loop"와 동일하다. 추가적으로 "this"(일반적인 자바스크립트 구문을 사용할 경우)나 $(this)(jQuery 명령어를 사용할 경우)로 루프 내에서 각 엘리먼트를 참조할 수 있다.

다음 예를 살펴보자.


Listing 10. 개별 루프
// 페이지에 있는 각 <p> 태그를 대상으로 순회한다. 여기서 인라인 함수 사용에 주목하자. // 자바에서 anonymous 클래스와 비슷한 기능이다. // 개별 함수를 호출하거나 이와 같이 인라인 함수를 작성할 수 있다. var increment = 1; $("p").each(function(){ // 이제 태그를 만날 때마다 문단 카운터를 하나씩 더한다. $(this) 변수를 사용해 // 개별 문단 엘리먼트를 참조하는 방법에 주목하자. $(this).text(increment + ". " + $(this).text()); increment++; });

검색 결과가 배열에 저장되므로 일반적인 프로그래밍 언어에서 자료 객체와 비슷하게 배열을 순회하면서 작업하는 함수를 기대할 것이다. 특정 검색 결과 길이를 알기 위해 배열에 $().length를 호출한다. Listing 11에 다른 배열 탐색 함수도 등장하는데, 다른 프로그래밍 언어와 마찬가지로 배열 탐색에 적합한 형태를 보여준다.


Listing 11. 추가적인 배열 함수
// eq() 함수는 직접 배열에 속한 구성 요소를 참조할 때 사용한다. // 이 경우 세 번째 문단(당연히 0이 시작 기준이다)을 얻은 다음에 감춘다. $("p").eq(2).hide(); // slice() 함수는 배열에서 처음과 끝 색인을 받아서 새끼 배열을 친다. // 다음은 페이지에서 세 번째부터 다섯 번째까지 문단을 감춘다. $("p").slice(2,5).hide();

이런 배열 탐색 함수 이외에, jQuery는 또한 검색 단어를 둘러싼 충첩된 엘리먼트를 찾도록 도와주는 함수도 제공한다. 이런 함수가 유용한 경우가 있을까? 글쎄, 종종 그림에 따라나오는 텍스트 라벨이나 폼 엘리먼트 다음에 오류 메시지를 포함하기를 원한다. 이런 명령어를 사용하면 특정 폼 엘리먼트를 찾아서 span 태그와 같이 다음 엘리먼트에 바로 오류 메시지를 표시하는 방법으로 사용자에게 경고 메시지를 보여줄 수 있다. Listing 12는 이런 디자인 예를 보여준다.


Listing 12. next() 함수 예
<input type=text class=validate> function validateForm() { $(".validate:text").each(function(){ if ($(this).val()=="") // 페이지에서 "validate" 클래스로 정의된 각 textfiled를 순회한다. // 비어 있다면, <span> 바로 뒤에 오류 메시지를 집어 넣는다. $(this).next().html("This field cannot be blank"); }); }


이 기사에서 배운 내용을 하나로 합치기

 

이 기사에서 배운 내용을 하나로 합친 결과를 보기 위해, 이 기사에 들어 있는 데모 용응을 살펴보자( 다운로드 절을 참조한다).

데모 응용은 여기서 간략하게 소개하는 편이 좋겠다. 이 기사 연재 전반에 걸쳐 데모 응용을 사용해서 다양한 jQuery 예제를 보여주며, 거의 모든 사람이 친숙한 RIA 웹 메일이라는 응용을 다룰 계획이기 때문이다. 이 데모 응용은 간단한 메일 클라이언트로서 jQuery를 활용해서 사용자에게 실제로 데스크톱 응용에서 볼 수 있는 전자편지 클라이언트를 사용하는 느낌을 전달한다. 최종 기사가 끝날 무렵이면 사용자를 위한 외형과 느낌을 만들어내는 과정은 물론이고 jQuery로 얼마나 손쉽게 이런 작업을 하는지 확인할 수 있을 것이다.


이 기사에서는 웹 메일 테이블에 있는 좌상단 컬럼에 보이는 "Select All"/"Deselect All" 체크 박스에 초점을 맞춘다(아래에 강조했다). 이 체크 박스를 선택하면, 컬럼에 있는 모든 체크 박스를 선택하며, 체크 박스 선택을 해제하면, 컬럼에 있는 모든 체크 박스 선택을 해제한다.


그림 2. "Select All" 체크 박스


Listing 13. 여기서 배운 모든 내용을 하나로 합치기
<!-- 1 단계는 Select All 체크박스 자체 생성이다. 페이지에서 체크박스에 유일한 ID를 부여한다. --> <input type=checkbox id=selectall> <!-- 2 단계는 체크박스에 속한 각 행을 만들어낸다. 각 행에 속한 체크박스에 'selectable' 클래스를 지정한다. 행이 여러 개며 또한 행에 속한 각 체크박스가 동일한 행동 방식을 보이기를 원하기 때문이다. --> <input type=checkbox class=selectable> <!-- 3 단계(마지막)는 jQuery 코드를 사용해서 하나로 합친다. --> // 모든 jQuery 설정 코드는 이 document.ready() 함수에 있어야 함을 기억하자. // 아니면 올바르게 동작하기 위해 자체 함수에 포함되어 있어야 한다. $(document).ready(function(){ // 페이지에서 selectall 체크박스를 찾기 위해 jQuery 선택 구문을 활용한다. // (ID를 지정하는 '#'에 주목하자) 그리고 jQuery에게 selectAll() 함수를 // 누군가 체크박스에 클릭할 때마다 호출하도록 알려준다.(이벤트는 다음 기사에서 // 다루겠다). $("#selectall").click(selectAll); }); // 이 함수는 누군가 selectall 체크박스를 누를 때마다 호출될 것이다. function selectAll() { // 이 행은 selectall 체크박스가 체크되었는지 아닌지를 판단한다. // 다음번 기사에 소개할 attr() 함수는 넘어온 객체에서 속성을 반환한다. // 여기서 체크 되었으면 true를, 그렇지 않으면 undefined를 반환한다. var checked = $("#selectall").attr("checked"); // 이제 jQuery 선택 구문을 활용해서 페이지에 속한 모든 체크박스를 찾는다. // 여기서 (각 행의 체크박스인) selectable class를 지정하는 방법을 쓴다. // 선택 결과 넘어오는 배열을 얻어서 each() 함수를 사용해서 순회한다. // 이렇게 되면 한번에 하나씩 결과 항목을 처리할 수 있다. each() 함수 내부에서 // $(this) 변수를 사용해서 개별 결과를 참조할 수 있다. 따라서 루프 내부에서 // 각 체크박스 값을 찾아서 selectall 체크박스와 일치하도록 만든다. $(".selectable").each(function(){ var subChecked = $(this).attr("checked"); if (subChecked != checked) $(this).click(); }); }


결론

 

jQuery는 웹 응용 개발 공동체에서 선호하는 자바스크립트로 자리를 잡아가는 중이며, RIA가 점점 더 널리 퍼짐에 따라 꾸준한 성장세를 보이고 있다. 여러 회사가 내부 응용을 온라인으로 이주하며, (워드 프로세서와 스프레드시트를 포함해서) 매일 사용하는 데스크톱 응용도 온라인으로 옮기는 상황에서, 개발을 쉽게 만들어주며 교차 플랫폼 지원을 약속하는 자바스크립트 라이브러리는 응용을 설계할 때 선택하는 기술의 일부가 될 것이다.


jQuery 연재물에서 첫 번째 기사는 jQuery 구문, 자바스크립트 코드에서 jQuery를 올바르게 사용하는 방법, 다른 라이브러리와 함께 사용할 때 함정을 피하는 방법을 소개한다. 또한 jQuery가 제공하는 다양한 기능 중에서 jQuery 검색과 선택 구문을 소개한다. 원하는 페이지 엘리먼트를 간편하고 빠르게 찾아내는 방법과 찾아낸 엘리먼트를 처리하는 방법을 소개한다. 또한 이 기사에서는 개별 엘리먼트를 처리하도록 이런 검색 결과를 순회하는 방법도 보여준다. 이번 기사에서 설명한 선택과 처리 방법은 연재물에서 소개할 다음 기사를 뒷받침하는 기초가 되며, 여러분이 만든 jQuery 코드를 뒷받침하는 기본 기술이 될 것이다.


마지막으로 RIA 웹 메일이라는 응용 시연을 다뤘다. 이 기사에서 배운 jQuery를 사용해서 Select All/Deselect All 체크 박스 선택 기능을 만들어 보았다. 살펴보면 알겠지만 단지 몇 줄만으로 다양한 웹 사이트에서 볼 수 있는 공통 위젯을 만든다.


이번 연재물에 이어지는 다음 기사는 예제 웹 응용에 상호대화식 기능을 추가하는 방법을 소개한다. (엘리먼트 클릭, 버튼 클릭, 콤보 박스 클릭 등) 페이지 이벤트를 다루는 방법, 페이지에 들어있는 엘리먼트에서 값을 얻는 방법, 페이지를 다시 읽지 않고서도 색상, 레이아웃 등을 바꾸기 위해 표준 CSS를 변경하는 방법을 배울 것이다.






다운로드 하십시오

 

설명
이름
크기
다운로드 방식
예제 애플리케이션이 들어 있는 Zip 파일 jquery.zip 68KB HTTP
예제 애플리케이션이 들어 있는 War 파일 jquery.war 68KB HTTP


필자소개

Michael Abernethy

10년에 걸친 기술 경험을 통해 Michael Abernethy는 광범위한 기술을 토대로 광범위한 클라이언트와 작업해 왔다. 현재 경매 소프트웨어 회사인 Optimal Auctions에서 제품 개발 관리자로 일한다. Abernethy는 요즘 RIA(Rich Internet Applications)에 초점을 맞춰 복잡함과 단순함을 동시에 추구하고 있다. 컴퓨터 앞에 앉아 있지 않을 때는 양서를 끼고 멕시코 해변가에서 여유를 즐긴다.


출처 : 한국 IBM

제공 : DB포탈사이트 DBguide.net

Posted by 1010
반응형

jQuery and Google Maps Tutorial: #1 Basics

Tags: jQuery, Google Maps | Written 4 days, 1 hour ago

There are many times I want to leverage jQuery's strengths to create a custom Google Maps mashup. In this tutorial, I will walk you through how to get started using jQuery inside the Google Maps environment. I will assume nothing, and explain each piece in detail.

View Final Demo

If you are already familiar with Google Maps API, skip to step #5, or so.

Step #1: Get API key

First, grab yourself an API key for Google Maps, you will need this in the next step.

Step #2: Load Google Maps and jQuery

We want to load up jQuery and Google Maps with the Google AJAX Libraries API.

Copy Codeblock to Clipboard

JavaScript:
  1. <script type="text/javascript" src="http://www.google.com/jsapi?key=YOUR_API_KEY_HERE">
  2. <script type="text/javascript" charset="utf-8">
  3.     google.load("maps", "2.x");
  4.     google.load("jquery", "1.3.1");
  5. </script>

Make sure to replace YOUR_API_KEY_HERE with your API key. By using the Google AJAX Libraries API, it allows you to load the JavaScript libraries you need right from Google's servers. This increases the chance that your users will be able to load the scripts faster from their browser cache, as well as shuffle the jQuery script loading off your server.

Step #3: Create the Google Map

Google Maps Basic

To create our Google Map, we need to create a container div and use CSS to give it a width and a height.

Copy Codeblock to Clipboard

HTML:
  1. <div id="map"></div>

Copy Codeblock to Clipboard

Css:
  1. <style media="screen" type="text/css">
  2.     #map { width:500px; height:500px; }
  3. </style>

Use the GMap2 function to make a map instance. Then, set the center of the map. I wrapped this code block in jQuery's document ready function so that the code is run after the page has loaded.

Copy Codeblock to Clipboard

JavaScript:
  1. $(document).ready(function(){
  2.     var map = new GMap2(document.getElementById('map'));
  3.     var burnsvilleMN = new GLatLng(44.797916,-93.278046);
  4.     map.setCenter(burnsvilleMN, 8);
  5. });

Here, I used Burnsville, MN's latitude and longitude because it is where I live right now. There are many ways to get the latitude and longitude of an address, like this simple service by iTouchMap.

The second parameter for setCenter is the zoom level, which is a number. I set the zoom level to "8" here because it is about in the middle.

At this point we should have a simple map.

Step #4: Load the Google Maps Example

To have some points to work with, let's paste in the google maps example.

Copy Codeblock to Clipboard

JavaScript:
  1. // setup 10 random points
  2. var bounds = map.getBounds();
  3. var southWest = getSouthWest();
  4. var northEast = bounds.getNorthEast();
  5. var lngSpan = northEast.lng() - southWest.lng();
  6. var latSpan = northEast.lat() - southWest.lat();
  7. var markers = [];
  8. for (var i = 0; i < 10; i++) {
  9.     var point = new GLatLng(southWest.lat() + latSpan * Math.random(),
  10.         southWest.lng() + lngSpan * Math.random());
  11.     marker = new GMarker(point);
  12.     map.addOverlay(marker);
  13.     markers[i] = marker;
  14. }

Note that I added a markers array to the example code. This will be used in the next step.

Step #5: Loop Through Markers and Add Basic Click Event to Markers

In this step, we start to use jQuery and Google Maps together. We want to be careful to use Google Map's built-in API as much as possible, leaving jQuery only for what it is best at.

Let's take that array of markers and loop through them with jQuery's each method.

Copy Codeblock to Clipboard

JavaScript:
  1. $(markers).each(function(i,marker){
  2.     GEvent.addListener(marker, "click", function(){
  3.         map.panTo(marker.getLatLng());
  4.     });
  5. });

Inside the loop, let's use Google Maps's GEvent namespace to attach a click event to each marker. Then, we will add a panTo behavior to center the map on the marker. marker.getLatLng(); returns the latitude and longitude of the marker, while map.panTo(GLatLng) allows us to center the map on that latitude and longitude.

Step #6 - Make a Clickable List of Markers

Let's add a clickable list next to the map. Insert a ul.

Copy Codeblock to Clipboard

HTML:
  1. <ul id="list"></ul>

Then let's style it up a bit by floating the map left and float our list element next to it. We also want to add a hover effect to the list items to give visual feedback to the user that they can click on each item in the list.

Copy Codeblock to Clipboard

Css:
  1. <style type="text/css" media="screen">
  2.     #map { float:left; width:500px; height:500px; }
  3.     #list { float:left; width:200px; background:#eee; }
  4.     #list li:hover { background:#000; color:#fff; cursor:pointer; cursor:hand; }
  5. </style>

In our jQuery each loop from last step, let's append the clickable list items to the list.

Copy Codeblock to Clipboard

JavaScript:
  1. $("<li />")
  2.     .html("Point "+i)
  3.     .click(function(){
  4.         map.panTo(marker.getLatLng()); 
  5.     })
  6.     .appendTo("#list");

Here I am just setting the content to "Point (the count)", adding that same panTo action from before, then appending the list item to our list.

Step #7 - Add a Custom Message

When I create a Google Maps mashup, I usually want to replace the built-in info window with something custom. With jQuery, we can add any arbitrary HTML in place of the info window. This is great when you want complete control over what the info window looks like.

Add a message div with some test text.

Copy Codeblock to Clipboard

HTML:
  1. <div id="message" style="display:none;">
  2.     Test text.
  3. </div>

Then add some basic styling to the message.

Copy Codeblock to Clipboard

Css:
  1. #message { position:absolute; padding:10px; background:#555; color:#fff; width:75px; }

We have to place the message div inside the map. To do this, we can use jQuery to append it to an object. The map view is seperated into panes. Each pane is a div layered on top of the other. To get the div object that we want to attach our message div to, we can use map.getPane(PANE). The G_MAP_FLOAT_SHADOW_PANE is the layer that I find works best for attaching custom messages.

Copy Codeblock to Clipboard

JavaScript:
  1. $("#message").appendTo(map.getPane(G_MAP_FLOAT_SHADOW_PANE));

To show the message div in place of the info window, we need to separate the click action into a separate function. Replace the map.panTo(marker.getLatLng(); with displayPoint(marker, i);, a call to the new displayPoint function shown below.

Copy Codeblock to Clipboard

JavaScript:
  1. function displayPoint(marker, i){
  2.     map.panTo(marker.getPoint());
  3.    
  4.     var markerOffset = map.fromLatLngToDivPixel(marker.getPoint());
  5.     $("#message").show().css({ top:markerOffset.y, left:markerOffset.x });
  6. }

We put the panTo action in our new function. Then the magic function here is the map.fromLatLngToDivPixel(GLatLng); which converts the latitude/longitude of the marker into a pixel on the map div.  This returns a  object containing x (amount of pixels from the left of the map) and y (amount of pixels from the top of the map).

Final Step #8 - Add Some Spice

To finish up, we will add an event when the map stops panning. We can do this by attaching the "movend" event map object. This way, after panning to the marker you've clicked on we can use jQuery's fadeIn method to add some spice.

Copy Codeblock to Clipboard

JavaScript:
  1. function displayPoint(marker, index){
  2.     $("#message").hide();
  3.    
  4.     var moveEnd = GEvent.addListener(map, "moveend", function(){
  5.         var markerOffset = map.fromLatLngToDivPixel(marker.getLatLng());
  6.         $("#message")
  7.             .fadeIn()
  8.             .css({ top:markerOffset.y, left:markerOffset.x });
  9.    
  10.         GEvent.removeListener(moveEnd);
  11.     });
  12.     map.panTo(marker.getLatLng());
  13. }

There you have it. We've come a long ways by adding our own custom click event, a clickable list and a custom info window. In the next tutorial, I'll show you how to style the Google Maps components with jQuery UI.

View Final Demo

Posted by 1010
반응형
JQuery Core

JQuery 객체 만들기

1 $( html )

    html을 JQuery Object로 만든다.
    예)
        $("<div><i>안녕하세요</i></div>").appendTo("body");

2 $(elems)
    자바스크립트 엘리먼트나 엘리먼트 배열을 JQuery 오브젝트로 만든다.

    예)
    $(document.body).html("<p>으흐흐.</p>");
    $(document.form1).html("<p>으흐흐.</p>");

3 $( expr, context )

    표현식과 context를 이용하여 JQuery객체를 만든다.
    표현식은 여기서 설명해 두었다.

4 $( fn )

    $(document).ready()의 짧은 표현이며, 일반 자바스크립트의 document.onload=function(){..}와 대응된다.

    예)
        $(function(){
            //여기에 코딩을하면된다.
            //..
            //
        });

6 eq(pos)
    선택된 JQuery오브젝트를 pos번째 JQuery Object만으로 선택.

9 gt(pos)
    선택된 JQuery 오브젝트중  pos보다 나중에있는 오브젝트들을 선택.

12 lt(pos)
    선택된 JQuery 오브젝트중  pos보다 앞에있는 오브젝트들을 선택.


10 index(subject)
    subject의 위치.

11 length
    선택된 요소의 갯수.

5 each(fn)
    선택된 모든 JQuery Object에대해 fn을 실행.


13 size()
    선택된 요소의 갯수.


7 get()
    선택된 JQuery오브젝트를 일반 HTML Element로 반환.

8 get(num)
    선택된 JQuery 오브젝트중  num번째 오브젝트를 일반 HTML Element로 반환.
    이것은 $("div").get(num) 은 $("div")[num]은 같다.


14 $.fn.extend( prop )
간단하게 플러그인을 만든다.

15 $.noConflict()
'$'가 다른 라이브러리등과 충돌하지 않게 함.
Posted by 1010
반응형
저번주에 자바 개발자 컨퍼런스에 다녀와서 느낀점은
개발환경에 밀접한 관계로 떼어놓기 힘든 자바스크립트의 활용에 대한 점이었다.
솔직히 한국에서 개발하면서 크로스 브라우징에 대한 부분은 비중있게 생각하지 않는게 현실이다.

 일부 대형 사이트 같은 경우는 웹표준을 따라서 크로스브라우징에 상당히 대처되어 있으나 대부분의 사이트는 작동되지 않는 것이 현실이다. 그래서 본인도 그런 노력을 해보고자 자바스크립트 라이브러리에 다가가고자 한다.

시작은 이렇게 남의 블로그에서 퍼다 나름으로서 하고 있지만
곧 나도 이들 사이에서 무언가 할 수 있을지 모른다는 생각을 갖고 발을 딛어보고자 한다.

 원본 출처 http://xinublog.com



jQuery: The Write Less, Do More JavaScript Library

Beginning

jQuery는 새로운 형태의 JavaScript library이다. (흔히, prototype과 비교한다. 요즘 대세는 protype에서 jQuery로 가는 것 같다.)

jQuery is a fast, concise, JavaScript Library that simplifies how you traverse HTML documents, handle events, perform animations, and add Ajax interactions to your web pages. jQuery is designed to change the way that you write JavaScript.

from http://jquery.com/

John Resig씨가 JavaScript를 어떻게 하면 단순하게 이용하게 할까 해서 만든 것이라고 한다.


Tutorials

몇가지 언어로된 Tutorials를 제공하는데, 아쉽게도 Korean Edition은 없다.

http://docs.jquery.com/Tutorials : 여기는 공식 사이트에서 제공하는 Tutorials이다.

http://jquery.bassistance.de/jquery-getting-started.html : 여기는 서핑하다가 찾은 자료인데, Example이 좀 더 낫다.

Query로 Ajax 개발을 단순화 하기 (한글) : 한국 IBM DeveloperWorks에서 Jesse Skinner씨의 글을 번역한 글이다.

jQuery Cheat Sheet이다.(from http://colorcharge.com/)
사용자 삽입 이미지


jQuery를 다루는 Blogger들

  1. JQuery를 이용한 겸손한 탭 메뉴 : Tab UI를 jQuery를 구현하는 걸 다루고 있습니다.

  2. [jQuery] jQuery 간단한 소개 및 prototype, yui와 간단 비교 : jQuery와 prototype를 간단히 비교한 글입니다.

  3. JQuery Core : 자주쓰는 jQuery의 10여가지 특징 및 기능을 간단히 설명한 글입니다.
  4. 송치형의 InnoLab - jQuery - New Wave Javascript : Prototype/Scriptaculous,  jQuery, YUI, Dojo, GWT에 대해서 각각의 특징과 설명을 한 글입니다. 한번 쯤 읽어보길 추천합니다.

기타

jQuery UI Project

jQuery UI는 Mouse Interaction과 간단한 UI로 Accordion, Datepicker, Dialog, Slider, Tabs을 제공하는데, UI는 쓸만해보이진 않는다. 다른 화려한 UI때문일지도 모르겠다.

jQuery UI Homepage(현재 비공식 오픈)

http://docs.jquery.com/UI(현재 공식 사이트)


맺 으면서, jQuery는 그 자체로는 매력이 떨어질 수 있으나, 그렇더라도 코드를 정말 단순히 줄여준다. jQuery만으로도 사용할만하다. extjs나 YUI가 jQuery로 내부적으로 사용한다고 하니 몰라도 사용할 수 있을지 모르겠다.

이제 prototype과 jQuery 같은 library를 사용하지 않고, JavaScript를 코딩한다는 것은 상상하기 어려운 것 같다.
Posted by 1010
반응형

Grab the latest version!

Choose your compression level:
jquery-1.3.2.min.js
jquery-1.3.2.js

Current Release: v.1.3.2

Posted by 1010
반응형

jQuery 를 사용하면서 ASP.NET 서버컨트롤와 절묘하게 상호작용이 되는 것을 보고

이래 저래 마음이 설레이곤 합니다.


이번에는 jQuery 를 이용하여 ASP.NET 서버컨트롤인 Panel 을 슬라이드 방법으로 보이고 감추는 방법에

대해서 구현해 볼까 합니다. 이는 ASP.NET AJAX 의 툴킷에 있는 CollapsiblePanel 컨트롤과 비슷한 효과를 보여주는데요.


http://www.asp.net/AJAX/AjaxControlToolkit/Samples/CollapsiblePanel/CollapsiblePanel.aspx


하지만 TargetControlID  속성으로 하나의 panel 로만 핸들링이 가능한데요. 두개 이상일 경우에는 어떻게 개발을 해야 할지

궁금하네요.


하지만, jQuery 는 서버단에서 개발하지 않고 클라이언트 단에서 어떻게 처리하는지 확인해 볼까 합니다. 그리고 panel 이 하나가 아니라 다수인 3개를 가지고 + 버튼을 누르면 하나씩 슬라이드로 보여주며, - 버튼을 누르면 하나씩 접혀지는 모습을 보여줍니다.


구현도 그렇게 어렵지 않고, CSS 구현과 약간의 자바스크립트 코드로 쉽게 이용할 수 있습니다.


<body>
<form id="form1" runat="server">
<div>
    <div class="cpHeader">
        <asp:ImageButton ID="btnShow" ImageUrl="~/Images/Show.gif" runat="server" 
OnClientClick="return false;" />
        <asp:ImageButton ID="btnHide" ImageUrl="~/Images/Hide.gif" runat="server" 
OnClientClick="return false;" />           
    </div>   
 
    <asp:Panel ID="Panel1" runat="server" class='cpBody'>
        <asp:Label ID="Label1" runat="server" Text="Label">심재운</asp:Label>
    </asp:Panel>
    <asp:Panel ID="Panel2" runat="server" class='cpBody'>
        <asp:Label ID="Label2" runat="server" Text="Label">권판진</asp:Label>
    </asp:Panel>
    <asp:Panel ID="Panel3" runat="server" class='cpBody'>
        <asp:Label ID="Label3" runat="server" Text="Label">이유진</asp:Label>
    </asp:Panel>
</div>
</form>

</body>


+ 버튼은 Show.gif 이미지 경로이며, - 버튼은 Hide.gif 이미지 입니다. 그리고 버튼이 위치한 div 태그에 class 을 지정하여 스타일이 적용되도록 하였습니다. class 명은 cpHeader 입니다. 그리고 + 버튼으로 인해 보여지게 될 panel 들은 asp:panel 서버컨트롤의 속성에  class 를 지정하였습니다.이는 panel 이 랜더링 되면 div 라는 html 태그가 되기 때문입니다.


CSS 설정은 아래와 같습니다.


<style type="text/css">
        .cpHeader
        {
            color: white;
            background-color: #719DDB;
            font: bold 11px auto "Trebuchet MS", Verdana;
            font-size: 12px;
            cursor: pointer;
            width:450px;
            height:18px;
            padding: 4px;          
        }
        .cpBody
        {
            background-color: #DCE4F9;
            font: normal 11px auto Verdana, Arial;
            border: 1px gray;              
            width:450px;
            padding: 4px;
            padding-top: 7px;
        }      

    </style>


이제 CSS 도 설정되었으니 자바스크립트 기술을 사용하여, 처음에는 모든 PANEL 을 감추고, + 버튼을 선택하면 +1 증가 구현으로 보이도록 하고, - 버튼은 -1 증가를 하여 감추도록 할것입니다.


$(document).ready(function() {

            var pan = $(".cpBody").hide(); //cpBody 의 css 로 정의된 div 는 초기화로 전부 감춘다.

            var showNext = 0;


            $("#btnShow").click(function() {

                if (showNext < pan.length) {

                    $(pan[showNext++]).slideDown(); //인덱스로 접근하여 슬라이드를 내린다.

                }

            });


            $("#btnHide").click(function() {

                if (showNext > 0) {

                    $(pan[showNext - 1]).slideUp(); //인덱스로 접근하여 슬라이드를 올린다.

                    showNext--; //올리면서 -1 을 감소시켜 그 이전의 panel 또한 접근할 수 있다.

                }

            });


        });


버튼에 대한 클릭이벤트를 설정하였고, btnShow 라는 버튼을 선택하면 panel 개수만큼 제한하여 오류 발생을 제어하였고,

 + 버튼을 선택할때 마다 showNext 변수값은 +1씩 증가되게 됩니다. 이 +1씩 증가되는 변수값을 응용하여 슬라이드를 제어할 수 있습니다. sildeDown() 메소드를 이용하여 해당 div 를 보이도록 합니다. sildeUp() 메소드는 panel 을 하나씩 감추는 역할을 합니다.




[그림 1 : 초기화 화면]



[그림 2 : + 버튼선택 화면]



[그림 3 : + 버튼 다시 선택시]



[그림 4 : - 버튼 선택시 화면]


위의 예제는 그리 어렵지 않을거라 생각이 들고요. 더 많은 기능이 있으니 jQuery 를 협업에서 많은 이용하시기 바랍니다.


감사합니다.



posted by 심재운(shimpark@gmail.com)

Posted by 1010
반응형
-Jquery 플러그인 모음 이거한방이면 끝..!! : http://www.seek-blog.com/41065/14090/240-plugins-jquery.html

 -light box(이미지 미리보기,pre,next) : http://leandrovieira.com/projects/jquery/lightbox/

 -이미지 스크롤 : http://benjaminsterling.com/2007/09/09/jquery-jqgalscroll-photo-gallery/

 -이미지 싸이클 : http://www.malsup.com/jquery/cycle/

 -BlockUI Plugin(processing,처리중 등등,confirm!) :  http://www.malsup.com/jquery/block/#element

 -UI Modal : http://jquery.com/demo/thickbox/

 -플래쉬 플러그인 삽입 : http://jquery.lukelutman.com/plugins/flash/#examples

 -Photo Slider Tutorial : http://opiefoto.com/articles/photoslider#example

 -jScrollPane : http://kelvinluck.com/assets/jquery/jScrollPane/jScrollPane.html

 -Accessible News Slider  : http://www.reindel.com/accessible_news_slider/#examples

 - unobtrusive tabs(탭메뉴) : http://stilbuero.de/jquery/tabs/#fragment-29
      http://www.sunsean.com/idTabs/#t3

 -jQuery Ajax Link Checker : http://troy.dyle.net/linkchecker/

 -jQuery Form Plugin  : http://malsup.com/jquery/form/#code-samples

 -jquery.suggest, an alternative jQuery based autocomplete library(자동완성)
  http://www.vulgarisoip.com/2007/06/29/jquerysuggest-an-alternative-jquery-based-autocomplete-library/
  http://nodstrum.com/2007/09/19/autocompleter/

 -jlook(폼객체 리뉴얼)  :

  http://envero.org/jlook/

 -jQuery - LinkedSelect(멀티 select) :

  http://www.msxhost.com/jquery/linked-selects/json/

 -Masked Input Plugin    : (입력포맷 확인)
  http://digitalbush.com/projects/masked-input-plugin
  http://www.appelsiini.net/projects/jeditable/default.html

 -Overlabel with JQuery(박스안에 워터마킹처리)  : http://scott.sauyet.com/xxJavascript/Demo/Overlabel/

 -Styling an input type="file"(파일찾기 이미지 처리)
   http://www.quirksmode.org/dom/inputfile.html

 -jQuery UI Datepicker v3.0 Examples(달력)
  http://marcgrabanski.com/code/ui-datepicker/

 -jQuery Validation Plugin(폼체크,포커싱)
  http://jquery.bassistance.de/validate/demo-test/ 
  http://www.texotela.co.uk/code/jquery/focusfields/
 
 -jQuery columnHover plugin(컬럼 하이라이트/컬럼 컨트롤)
  http://p.sohei.org/stuff/jquery/columnhover/demo/demo.html
  http://p.sohei.org/stuff/jquery/columnmanager/demo/demo.html

 -tablesorterDocumentation(테이블 순서)
  http://tablesorter.com/docs/index.html 

 -jQuery Accordion Demo(컨테이너 예제)
  http://jquery.bassistance.de/accordion/?p=1.1.1

 -jQPanView based in jQuery 1.1(이미지 확대보기)
  http://projects.sevir.org/storage/jpanview/index.html

 -jQuery Impromptu(confirm!,alert! 등등)
  http://trentrichardson.com/Impromptu/

 -jqGrid Examples(그리드)
  http://trirand.com/jqgrid/jqgrid.html#

 -Toggle HTML-Elements with jQuery
  http://jquery.andreaseberhard.de/toggleElements/

 -UI/Sortables(테이블 위치변경 ^^)
  http://docs.jquery.com/UI/Sortables

 -뉴스 슬라이더(부분보기,전체보기)
  http://www.reindel.com/accessible_news_slider/

 -로컬 스크롤러
  http://www.freewebs.com/flesler/jQuery.LocalScroll/

 -핫키 테스트
  http://jshotkeys.googlepages.com/test-static.html

 -슬라이더
  http://docs.jquery.com/UI/Slider/slider

 -쇼핑카트
  http://www.mimul.com/pebble/default/2007/10/30/1193753340000.html

 -테이블 소트
  http://www.mimul.com/pebble/default/2007/11/06/1194348600000.html

 -이미지 나중에 로딩시키기
  http://www.mimul.com/pebble/default/2007/11/10/1194695220000.html

 -오토탭(입력시 폼객체 자동넘김)
  http://dev.lousyllama.com/autotab/

 -실시간 폼객체 수정
  http://www.appelsiini.net/projects/jeditable/custom.html

 -프린트
  http://www.designerkamal.com/jPrintArea/#
 -차트
  http://www.reach1to1.com/sandbox/jquery/jqchart/

 -CSS Dock Menu (Jquery + CSS)
  후니넷에서 보삼
 -툴팁
  http://www.codylindley.com/blogstuff/js/jtip/

 -XML데이터 뿌리기
  http://blog.reindel.com/src/jquery_browse/
  http://www.xml.com/pub/a/2007/10/10/jquery-and-xml.html
  http://www.mimul.com/pebble/default/2006/11/05/1162710000000.html

 -Clearing Form

  http://www.learningjquery.com/2007/08/clearing-form-data

 --암호 복잡성 체크

  http://phiras.googlepages.com/PasswordStrengthMeter.html

 --Form Serialize

  http://dev.jquery.com/wiki/Plugins/FastSerialize

 --GetString 퍼라미터 가져오기
  http://www.mathias-bank.de/2006/10/28/jquery-plugin-geturlparam/

 --검색 후 콤보생성(ajax with combo) 아주 유용함
  http://extjs.com/deploy/ext/examples/form/forum-search.html
  http://extjs.com/deploy/ext/examples/grid/edit-grid.html-->그리드

 --파일 업로드(input=file) 리폼
  http://www.appelsiini.net/projects/filestyle/demo.html

Jquery Best
 http://www.spicyexpress.net/general/jquerry-at-it-best-downloadable-jquerry-plugins-and-widgets-for-you-2/

웹디자인 템플릿트(2.0)
 -http://www.templateworld.com/free_templates.html

----------------------------------------
Jquery tag cloud
http://www.ajaxrain.com/tagcloud.php
Jquery 기본설명
http://www.zzbb.kr/34
----------------------------------------

^^Star Rater(순위) --활용가능성 높음
http://www.m3nt0r.de/devel/raterDemo/

^^ AJAX CALLING --활용가능성 높음
http://cgaskell.wordpress.com/2006/11/02/jquery-ajax-call-and-result-xml-parsing/

--AJAX 아이디 중복체크 - 활용가능성 아주높음
http://www.shawngo.com/gafyd/index.html

^^ jQuery framework plugins which provide a way to sort and nest elements in web applications, using drag-and-drop(테이블드래그앤드랍) --활용가능성 중간
http://code.google.com/p/nestedsortables/

^^Simple tableSorter(리스트 정렬) 활용가능성 중간
http://motherrussia.polyester.se/docs/tablesorter/

^^Cookie 활용가능성 높음
http://www.stilbuero.de/2006/09/17/cookie-plugin-for-jquery/

^^태깅 --그냥한번보자..ㅎ
http://www.alcoholwang.cn/jquery/jTaggingDemo.htm

^^죽이는 어코디언 메뉴
http://dev.portalzine.de/index?/Horizontal_Accordion--print

^^ AJAX Indicator 이미지
http://qureyoon.blogspot.com/2006/11/make-your-own-loading-gif.html

----------------------------------------
2007.12.28 찾은것
----------------------------------------
http://rikrikrik.com/jquery/quicksearch/#usage
http://nadiaspot.com/jquery/confirm!/#examples
http://host.sonspring.com/portlets/
http://jquery.andreaseberhard.de/toggleElements/
http://www.getintothis.com/pub/projects/rb_menu/
http://icon.cat/software/iconDock/0.8b/dock.html
http://www.nuernberg.de/internet/portal/index.html
http://rikrikrik.com/jquery/shortkeys/#examples
http://rikrikrik.com/jquery/pager/#examples
http://famspam.com/facebox/ --라이트박스같은것
http://www.andreacfm.com/
http://www.vulgarisoip.com/2007/06/29/jquerysuggest-an-alternative-jquery-based-autocomplete-library/ --autocomplete
http://www.gcmingati.net/wordpress/wp-content/lab/jquery/newsticker/jq-liscroll/scrollanimate.html - 뉴스 스크롤
http://d-scribe.de/webtools/jquery-pagination/demo.htm# --페이징
http://tinymce.moxiecode.com/example_full.php?example=true --Open Source WYSWYG 웹 에디터
http://www.laptoptips.ca/projects/tinymce-advanced/ --Open Source WYSWYG 웹 에디터 advanced
http://extjs.com/ -또다른 RIA xxJAVASCRIPT 프레임워크
http://www.digital-web.com/extras/jquery_crash_course/ -jquery로 만든 비행 예약 시스템 데모(Passenger Management )
http://markc.renta.net/jquery/ --jquery 간단예제
http://www.xml.com/pub/a/2007/10/10/jquery-and-xml.html?page=2 -jquery와 XML
http://www.stilbuero.de/2006/09/17/cookie-plugin-for-jquery/  -쿠키 플러그인
http://jquery.com/files/demo/dl-done.html --간단한 어코디언 메뉴
http://mjslib.org/doc/legacy/fieldgroup.html --폼필드 컨트롤
http://extjs.com/deploy/dev/examples/tree/two-trees.html --트리
http://www.amcharts.com/column/ - 차트(바로 사용^^)
http://particletree.com/features/rediscovering-the-button-element/ - 버튼 스타일링
http://www.i-marco.nl/weblog/jquery-accordion-menu/ - 실용성 높은 어코디언 메뉴


http://www.sastgroup.com/jquery/240-plugins-jquery
http://jquery.bassistance.de/jquery-getting-started.html

Posted by 1010
반응형

:: Home Page ::

More than
750,000
download hits.

IETester is free.
To support the project
you can make a donation :

or you can translate it.

IETester is a free WebBrowser that allows you to have the rendering and javascript engines of IE8 RC1, IE7 IE 6 and IE5.5 on Vista and XP, as well as the installed IE in the same process.
New in v0.3 : IE8 RC1, zoom feature and a lot of corrections and improvements !

This is an alpha release, so feel free to post comments/bugs on the IETester forum or contact me directly.
Minimum requirement : Windows Vista or Windows XP with IE7 (Windows XP/IE6 config has some problems and IE8 instance do not work under XP without SP2)

Download IETester v0.3 (24MB)
(IETester v0.3 zipped intaller for people unable to download .exe files due to proxy limitations)

Known problems and limitations :

  • The Previous/Next buttons are not working properly
  • Focus is not working properly
  • Java applets are not working
  • Flash is not working on IE6 instance in user mode : A solution is to launch IETester as admin user and Flash will work.
  • CSS Filters are not working correctly in user mode : A solution is to launch IETester as admin user and CSS Filters will work.
Posted by 1010
반응형

Download: AAA Logo 2008 v2.10



This logo design software will help you to design professional logo image for printing or the Web in minutes! Choose one of 100+ pre-made logo templates as a start point for your logo project or create a blank logo project and make your own logo from scratch.
AAA Logo has it’s own vector-based logo objects library with more then 2000 customizable logo elements. Exactly the same technique to create logo images using pre-designed logo elements is used by almost all professional logo designers to create logos for their clients.


Download:
http://rapidshare.com/files/161495396/AAA.Logo.2008.v2.10.rar

Password:
www.sexas.us
Attention: Must have WinZix to open this file! Get WinZix here!

Full fast direct download:
Fast SharingWire download for AAA Logo 2008 v2.10
Fast UseNext download for AAA Logo 2008 v2.10
Posted by 1010
카테고리 없음2009. 3. 7. 23:37
반응형
“Bantai Yahudi!!!! Hancurkan Amerika!!! Bantai Axe gank dan antek2nya!!! SFX harus menang!!!!]”

라는 익스플러 주소창에 메세지가 나올때 조치 방법...

우선 바이러스 검사로 바이러스 잡은 다음에..


Wow kalimat apa tuh…… iseng- iseng tadi malem nyolokin flashdisk temen ke computer…lah ternyata ade pirusnye… kayaknya gak berbahaya sih…kulihat, kurasa dan kuraba komputerku masih berjalan normal…terus kubersihin deh pake ANSAV. Eh ternyata setelah kubuka Internet explorer di bagian atas ada kalimat
“Bantai Yahudi!!!! Hancurkan Amerika!!! Bantai Axe gank dan antek2nya!!! SFX harus menang!!!!]”
Ternyata kalimat itu adalah peninggalan dari sang virus tadi. Virus itu akan membuat file di setiap drive yang kite punyai dengan nama “sfx.dll.vbs” dan auto run.inf untuk menjalankan file tadi.dan letaknya juga di drive tersebut misalnya c: ya c:\ sfx.dll.vbs dan satu lagi di direktori windows...
Virus ini juga mudah menyebar karena setiap flash disk yang masuk akan langsung terinfeksi dengan kemasukan 2 pile tadi.
Sebenarnya virus ini gak berbahaya … karena dia sifatnya hanya nyampein pesen lewat internet explorer tadi…dan mungkin menonaktifkan anti virus mc Afee (ku gak tau coz aku gak pake  )
Bagaimana ngilanginya?...Pakai aja anti virus misalnya ANSAV . Kalau mau manual... masuk ke safe mode dulu lalu hapus eh 2 file tadi di setiap drive. Terus buka regedit "HKEY_CURRENT_USER\Software\Microsoft\InternetExplorer\Main\Window Title" ilangin “KALIMAT TADI” ataw malah ubah sesuka kalian. Kalo mbah dar-x sih malah mo di ubah jadi “DAR-X pria perkasa punya selera” (dasar pria yang aneh)… :)



출처 : http://dasaramutu.blogspot.com/2008/02/bantai-yahudi-hancurkan-amerika.html
Posted by 1010
반응형
 

commons-beanutils  most recent diff


version 1.8.0

    View the most recent changes for the commons-beanutils port at: commons-beanutils.darwinports.com/diff
    Scroll down toward the bottom of the page to get installation instructions for commons-beanutils.
    The raw
    portfile for commons-beanutils 1.8.0 is located here:
    http://commons-beanutils.darwinports.com/dports/java/commons-beanutils/Portfile
    Find related portfiles with the unique DarwinPorts.com search feature.
    Check for any related Fink projects here:
    pdb.finkproject.org/pdb/package.php/commons-beanutils

    The commons-beanutils Portfile 40121 2008-09-21 15:49:41Z jberry macports.org $

    PortSystem 1.0

    Name: commons-beanutils
    Version: 1.8.0

    Category: java
    Maintainers: jberry openmaintainer
    Platform: darwin

    Description: Jakarta Commons-BeanUtils
    Long Description: Commons-BeanUtils provides easy-to-use wrappers around the Java reflection and introspection APIs.
    Homepage:
    http://commons.apache.org/beanutils/

    distfiles ${distname}-src${extract.suffix}

    Master Sites: apache:commons/beanutils/source/

    Checksums: md5 1bce3cfa4ae33c94686422e78abc0792 sha1 a4af85d2cfd04a42d8de9a5bb5336a21f33e30ce

    depends_build bin:ant:apache-ant
    depends_lib bin:java:kaffe port:junit port:commons-logging port:commons-collections

    worksrcdir ${distname}-src

    use_configure no

    build.cmd ant
    build.target jar javadoc
    build.args -Dcommons-collections.jar=${prefix}/share/java/commons-collections.jar -Dcommons-logging.jar=${prefix}/share/java/commons-logging.jar -Djunit.jar=${prefix}/share/java/junit.jar

    destroot {
    xinstall -m 755 -d ${destroot}${prefix}/share/java ${destroot}${prefix}/share/doc
    xinstall -m 644 ${worksrcpath}/dist/commons-beanutils-core-${version}.jar ${destroot}${prefix}/share/java/commons-beanutils-core.jar
    xinstall -m 644 ${worksrcpath}/dist/commons-beanutils-${version}.jar ${destroot}${prefix}/share/java/commons-beanutils.jar
    file copy ${worksrcpath}/dist/docs ${destroot}${prefix}/share/doc/${name}
    }

    livecheck.check regex
    livecheck.url
    http://commons.apache.org/downloads/download_beanutils.cgi
    livecheck.regex "${name}-(\\d+\\.\\d+(\\.\\d+)?)-src.tar.gz"

If you haven't already installed Darwin Ports, you can find easy instructions for doing so at the main Darwin Ports page.

Once Darwin Ports has been installed, in a terminal window and while online, type the following and hit return:


    %  cd /opt/local/bin/portslocation/dports/commons-beanutils
    % sudo port install commons-beanutils
    Password:
You will then be prompted for your root password, which you should enter. You may have to wait for a few minutes while the software is retrieved from the network and installed for you. Y ou should see something that looks similar to:

    ---> Fetching commons-beanutils
    ---> Verifying checksum for commons-beanutils
    ---> Extracting commons-beanutils
    ---> Configuring commons-beanutils
    ---> Building commons-beanutils with target all
    ---> Staging commons-beanutils into destroot
    ---> Installing commons-beanutils
- Make sure that you do not close the terminal window while Darwin Ports is working. Once the software has been installed, you can find further information about using commons-beanutils with these commands:
    %  man commons-beanutils
    % apropos commons-beanutils
    % which commons-beanutils
    % locate commons-beanutils

 Where to find more information:

Darwin Ports
Posted by 1010
반응형
The BeanUtils Component

대부분의 자바 개발자들은 객체 속성의 getters와 setters에 대한 자바빈즈 네이밍 패턴에 따르는 자바 클래스들을 만들곤 한다. 이러한 상호대응하는 getXxx 와 setXxx 메소드들를 호출하는 방식으로 직접 이들 메소드들에 접근함은 자연스럽다. 그러나 자바 객체 속성들에 대한 동적인 접근이 필요한 경우가 종종 발생한다.(호출되는 속성 getter 와 setter 메소드들의 compiled-in 지식없이) . 다음과 같은 경우들이 이에 포함된다.

  • 자바 오브젝트 모델과 상호 작용하는 스크립팅 언어를 개발할 때 (such as the Bean Scripting Framework).
  • 웹 프레이젠테이션과 같은 템프릿 언어 프로세스를 개발할 때 (such as JSP or Velocity).
  • JSP와 XSP 환경을 위한 커스텀 태그 라이버러리를 개발할 때 (such as Jakarta Taglibs, Struts, Cocoon).
  • XML-based 구성(configuration) 자원을 활용할 때 (such as Ant build scripts, web application deployment descriptors, Tomcat's server.xml file).

자바 언어는 ReflectionIntrospection API들을 제공한다.(JDK Javadoc 안의 java.lang.reflect 과 java.beans 패키지를 보라). 그러나, 이들 API들은 이해하고 활용하기에 매우 복잡해질 수 있다. BeanUtils 콤프넌트는 이들의 능력을 쉽게 사용할 수 있도록 랩퍼들을 제공한다


문서

Release Notes 는 이 배포판에 포함되어 있는 새로운 기능과 버그 픽스를 문서로 정리하고 있다.

JavaDoc API documents 는 online상에서 가능하다. 특히, PropertyUtils 클래스 설명서에 기술된 속성 참조 신택스 옵션들에 주의해야 한다.


배포판



Commons BeanUtils

Most Java developers are used to creating Java classes that conform to the JavaBeans naming patterns for property getters and setters. It is natural to then access these methods directly, using calls to the corresponding getXxx and setXxx methods. However, there are some occasions where dynamic access to Java object properties (without compiled-in knowledge of the property getter and setter methods to be called) is needed. Example use cases include:

  • Building scripting languages that interact with the Java object model (such as the Bean Scripting Framework).
  • Building template language processors for web presentation and similar uses (such as JSP or Velocity).
  • Building custom tag libraries for JSP and XSP environments (such as Jakarta Taglibs, Struts, Cocoon).
  • Consuming XML-based configuration resources (such as Ant build scripts, web application deployment descriptors, Tomcat's server.xml file).

The Java language provides Reflection and Introspection APIs (see the java.lang.reflect and java.beans packages in the JDK Javadocs). However, these APIs can be quite complex to understand and utilize. The BeanUtils component provides easy-to-use wrappers around these capabilities.

BeanUtils Core And Modules

Since the 1.7.0 release BeanUtils has distributed three jars:

  • commons-beanutils.jar - contains everything
  • commons-beanutils-core.jar - excludes Bean Collections classes
  • commons-beanutils-bean-collections.jar - only Bean Collections classes
The main commons-beanutils.jar has an optional dependency on Commons Collections

Bean Collections

Bean collections is a library combining BeanUtils with Commons Collections to provide services for collections of beans. One class (BeanComparator) was previously released, the rest are new. This new distribution strategy should allow this sub-component to evolve naturally without the concerns about size and scope that might otherwise happen.

Bean Collections has an additional dependency on Commons Collections .

Documentation

The User Guide is part of the package JavaDocs.

The Release Notes document the new features and bug fixes that have been included in this release.

The JavaDoc API documents are available online. In particular, you should note the property reference syntax options described in the PropertyUtils class description.

Releases

1.8.0

BeanUtils 1.8.0 is binary compatible with version 1.7.0 and contains quite a few bug fixes and enhancements .

BeanUtils 1.8.0 is available to download here .

1.7.0

BeanUtils 1.7.0 is a service release which removes the dependency upon a specific commons-collection library version. It may be safely used together with either the 2.x or 3.x series of commons-collections releases. It also introduces a number of important enhancements. It is backward compatible with the 1.6 release.

This important service release is intended to help downstream applications solve dependency issues. The dependency on commons collections (which has become problematic now that there are two incompatible series of commons collections releases) has been factored into a separate optional sub-component plus a small number of stable and mature org.apache.commons.collections packaged classes (which are distributed with the BeanUtils core). This arrangement means that the BeanUtils core sub-component (which is the primary dependency for most downsteam applications) can now be safely included on the same classpath as commons collections 2.x, 3.x or indeed neither.

The distribution now contains alternative jar sets. The all-in-one jar contains all classes. The modular jar set consists of a core jar dependent only on commons logging and an optional bean collections jar (containing classes that provide easy and efficient ways to manage collections of beans) which depends on commons collections 3.

BeanUtils 1.7.0 is available to download here .

Older Releases (Not Mirrored)

Support

The commons mailing lists act as the main support forum. The user list is suitable for most library usage queries. The dev list is intended for the development discussion. Please remember that the lists are shared between all commons components, so prefix your email by [beanutils].

Issues may be reported via ASF JIRA .


Posted by 1010
반응형

1. 왜 이걸 썼을까?
   bean에서 하드코딩된 값을 property 파일에 넣고 쓸려면 "key=value" 형태 밖에 안됩니다.
   좀더 복잡한 구조를 효율적으로 관리하고자 한다면 xml을 이용해야 하는데
   xml를 쉽게 처리할 수 있는게 뭐 없을까 찾아보니 digester라는 아파치 오픈소스가
   눈에 띄었습니다.
   그냥 따라하기 식으로 환경구성하고 테스트 만들어 보니 한 30분 즘...
   정말 쉽게 구현이 되었습니다.
  
2. 필요한건 뭘까?
   url : http://jakarta.apache.org/commons/digester/
   아래 보면 digester를 이용하기 위해 관련된걸 보면 commons의 logging 1.1.x, BeanUtils 1.x, Collections 2.x/3.x 등이 필요합니다.
   다 다운받으세요^^  

사용자 삽입 이미지
  digester : http://jakarta.apache.org/site/downloads/downloads_commons-digester.cgi
  logging : http://jakarta.apache.org/site/downloads/downloads_commons-logging.cgi
  BeanUtils : http://jakarta.apache.org/site/downloads/downloads_commons-beanutils.cgi
  Collections :  http://jakarta.apache.org/site/downloads/downloads_commons-collections.cgi


3. 만들어 보기
    일단 간단한 형태로 구조를 잡았습니다.
[code]
<?xml version="1.0"?>
<config description="kkaok">
<database part="oracle">
<driverclassname>oracle.jdbc.driver.OracleDriver</driverclassname>
<url>oracle ......</url>
<maxactive>10</maxactive>
<maxwait>100</maxwait>
<defaultautocommit>true</defaultautocommit>
<defaultreadonly>false</defaultreadonly>
<defaulttransactionisolation>2</defaulttransactionisolation>
<username>oid</username>
<password>opwd</password>
</database>
<database part="cubrid">
<driverclassname>cubrid.jdbc.driver.CUBRIDDriver</driverclassname>
<url>jdbc:CUBRID:localhost:33000:demodb:::</url>
<maxactive>10</maxactive>
<maxwait>100</maxwait>
<defaultautocommit>true</defaultautocommit>
<defaultreadonly>false</defaultreadonly>
<defaulttransactionisolation>2</defaulttransactionisolation>
<username>cid</username>
<password>cpwd</password>
</database>
</config>
[/code]
config 라는 거 아래 database라는 노드가 반복이 되고
database 아래에는 part, driverclassname,url,maxactive ... 등이 있습니다.

xml을 매핑할 객체를 만들어 보겠습니다.
[code]
public class ConfigInfo {
    private List databaseinfo; // database가 반복되는 bean
    public ConfigInfo(){
        databaseinfo  = new ArrayList();
    }
    public void AddDatavaseInfo(DatabaseInfo dbinfo){ // database 빈에 추가하는 메소드
        databaseinfo.add(dbinfo);
    }
}
[/code]
반복이 되는 패턴 등은 따로 클래스로 뽑고 그냥 값은 property로 만드시면 됩니다.
[code]
public class DatabaseInfo {
    private String part;
    private String driverclassname;
    private String url;
    private int maxactive;
    private long maxwait;
    private boolean defaultautocommit;
    private boolean defaultreadonly;
    private String username;
    private String password;
    private int defaulttransactionisolation;
    public int getDefaulttransactionisolation() {
        return defaulttransactionisolation;
    }
    public void setDefaulttransactionisolation(int defaulttransactionisolation) {
        this.defaulttransactionisolation = defaulttransactionisolation;
    }
    public boolean isDefaultautocommit() {
        return defaultautocommit;
    }
    public void setDefaultautocommit(boolean defaultautocommit) {
        this.defaultautocommit = defaultautocommit;
    }
    public boolean isDefaultreadonly() {
        return defaultreadonly;
    }
    public void setDefaultreadonly(boolean defaultreadonly) {
        this.defaultreadonly = defaultreadonly;
    }
    public String getDriverclassname() {
        return driverclassname;
    }
    public void setDriverclassname(String driverclassname) {
        this.driverclassname = driverclassname;
    }
    public int getMaxactive() {
        return maxactive;
    }
    public void setMaxactive(int maxactive) {
        this.maxactive = maxactive;
    }
    public long getMaxwait() {
        return maxwait;
    }
    public void setMaxwait(long maxwait) {
        this.maxwait = maxwait;
    }
    public String getPassword() {
        return password;
    }
    public void setPassword(String password) {
        this.password = password;
    }
    public String getUrl() {
        return url;
    }
    public void setUrl(String url) {
        this.url = url;
    }
    public String getUsername() {
        return username;
    }
    public void setUsername(String username) {
        this.username = username;
    }
    public String getPart() {
        return part;
    }
    public void setPart(String part) {
        this.part = part;
    }
}
[/code]

[code]
public class ReadConfig {
    public static void main(String[] args){
        try {
            Digester digester = new Digester();
            digester.setValidating(false);
             // 최상위 노드를 생성합니다. 객체는 addObjectCreate
            // addObjectCreate(xml 경로와노드명, 객체 타입)
            digester.addObjectCreate("config", ConfigInfo.class);
            // DatabaseInfo를 생성합니다.
            digester.addObjectCreate("config/database", DatabaseInfo.class);
            // DatabaseInfo의 part 지정
            //attributes는 addSetProperties(경로, attribute name, bean 매핑할 property) 메소드로 등록합니다.
            digester.addSetProperties("config/database", "part", "part");
            // node 추가, addBeanPropertySetter(경로와이름, 객체필드명)
            digester.addBeanPropertySetter("config/database/driverclassname",
                    "driverclassname");
            digester.addBeanPropertySetter("config/database/url", "url");
            digester.addBeanPropertySetter("config/database/maxactive",
                    "maxactive");
            digester
                    .addBeanPropertySetter("config/database/maxwait", "maxwait");
            digester.addBeanPropertySetter("config/database/defaultautocommit",
                    "defaultautocommit");
            digester.addBeanPropertySetter("config/database/defaultreadonly",
            "defaultreadonly");
            digester.addBeanPropertySetter("config/database/defaulttransactionisolation",
            "defaulttransactionisolation");
            digester.addBeanPropertySetter("config/database/username",
                    "username");
            digester.addBeanPropertySetter("config/database/password",
                    "password");
            // set하기
            // addSetNext(경로와이름, 추가할 메소드명);
            digester.addSetNext("config/database", "AddDatavaseInfo");

            File input = new File(xml파일);
            config = (ConfigInfo) digester.parse(input);

            // config에 xml값이 이제 다 담겼습니다. 이제 입맛대로 사용하시면 됩니다.

        } catch (Exception exc) {
            exc.printStackTrace();
        }
    }
}
[/code]

4. 결과
    work flow
    1.  xml 구조 및 파일 생성
    2. 매핑할 객체 생성
    3. digester로 매핑하기
    4. 사용

    xml로 만들걸 객체로 생성하거나 객체로 구조 잡고 xml로 만들 수 있다면 나머진 digester가 알아서 해줍니다.
    약간의 규칙을 알아야 하는데 해당 사이트에 가보시면 문서가 잘 만들어 져 있으니 사용하는데 크게 어렵진 않을거 같습니다.
    소스를 보시면 알겠지만 기존 xml을 다루는 거와 비교해보면 월등히 간결합니다.
    기존 xml과 관련된 많은 것들이 보이지도 않습니다.
    한 30분 노력해서 앞으로 몇년을 써먹을 수 있다면 충분히 가치가 있다고 생각합니다.
    try it~~~~

Posted by 1010
반응형

jakarta common util중 Beanutils사용법

먼저.. 속성 접근 테스트에 쓰일 빈 두개.. Address , Employee

-------------------------------------------------------------

package com.joldo.test;

/**
 * @author 카악퇴 joldo@joldo.com
 */
public class Address {
 String post;
 String detail;
 
 public Address(){ 
 }
 public Address(String post, String detail){
  this();
  this.post = post;
  this.detail = detail; 
 }

 public String getDetail() {
  return detail;
 }

 /**
  * @return
  */
 public String getPost() {
  return post;
 }

 /**
  * @param string
  */
 public void setDetail(String string) {
  detail = string;
 }

 /**
  * @param string
  */
 public void setPost(String string) {
  post = string;
 }

}
-------------------------------------------------------------

-------------------------------------------------------------

package com.joldo.test;

import java.util.HashMap;
import java.util.Map;

/**
 * @author 카악퇴 joldo@joldo.com
 */
public class Employee {
 
 public Employee(){}
 
 
 // Simple Property
 private String firstName;
 public String getFirstName() {
  return firstName;
 }
 public void setFirstName(String string) {
  firstName = string;
 }

 private String lastName;
 public String getLastName() {
  return lastName;
 }
 public void setLastName(String string) {
  lastName = string;
 }
 
 private Address addr ;
 public Address getAddress(){
  return addr;
 }
 public void setAddress(Address addr){
  this.addr  = addr;
 }
 
 private int num;
 public int getNum() {
  return num;
 }
 public void setNum(int i) {
  num = i;
 }
 
 // Indexed Property
 private String[] books = {"java","tomcat","c++"};
 public String getBook(int index){
  return books[index];
 } 
 public void setBook(int index, String book){
  books[index] = book;
 }
 
 // Mapped Property
 private Map video = new HashMap();
 public String getVideo(String key){
  return (String)video.get(key);
 }
 public void setVideo(String key , String value){
  this.video.put(key,value);
 }
 
 


}
-------------------------------------------------------------



다음 두개는 속성 카피 테스트에 쓰일 빈 두개..

-------------------------------------------------------------

/*
 * Created on 2003. 11. 21.
 *
 * To change the template for this generated file go to
 * Window>Preferences>Java>Code Generation>Code and Comments
 */
package com.joldo.test;

/**
 * @author 카악퇴 joldo@joldo.com
 *
 * 
 */
public class FromClass {
 
 String fromStringToString;
 String fromStringToInt;
 String fromStringToBoolean;
 
 int fromIntToString;
 int fromIntToInt;
 int fromIntToBoolean;
 
 boolean fromBooleanToString;
 boolean fromBooleanToInt;
 boolean fromBooleanToBoolean;
 
 String fromExistToNon;
 
 public boolean isFromBooleanToBoolean() {
  return fromBooleanToBoolean;
 }

 public boolean isFromBooleanToInt() {
  return fromBooleanToInt;
 }

 public boolean isFromBooleanToString() {
  return fromBooleanToString;
 }

 public String getFromExistToNon() {
  return fromExistToNon;
 }

 public int getFromIntToBoolean() {
  return fromIntToBoolean;
 }

 public int getFromIntToInt() {
  return fromIntToInt;
 }

 public int getFromIntToString() {
  return fromIntToString;
 }

 public String getFromStringToBoolean() {
  return fromStringToBoolean;
 }

 public String getFromStringToInt() {
  return fromStringToInt;
 }

 public String getFromStringToString() {
  return fromStringToString;
 }

 public void setFromBooleanToBoolean(boolean b) {
  fromBooleanToBoolean = b;
 }

 public void setFromBooleanToInt(boolean b) {
  fromBooleanToInt = b;
 }

 public void setFromBooleanToString(boolean b) {
  fromBooleanToString = b;
 }

 public void setFromExistToNon(String string) {
  fromExistToNon = string;
 }

 public void setFromIntToBoolean(int i) {
  fromIntToBoolean = i;
 }

 public void setFromIntToInt(int i) {
  fromIntToInt = i;
 }

 public void setFromIntToString(int i) {
  fromIntToString = i;
 }

 public void setFromStringToBoolean(String string) {
  fromStringToBoolean = string;
 }

 public void setFromStringToInt(String string) {
  fromStringToInt = string;
 }

 public void setFromStringToString(String string) {
  fromStringToString = string;
 }

}
-------------------------------------------------------------

-------------------------------------------------------------

/*
 * Created on 2003. 11. 21.
 *
 * To change the template for this generated file go to
 * Window>Preferences>Java>Code Generation>Code and Comments
 */
package com.joldo.test;

/**
 * @author 카악퇴 joldo@joldo.com
 *
 * 
 */
public class ToClass {

 String fromStringToString;
 int fromStringToInt;
 boolean fromStringToBoolean;
 
 String fromIntToString;
 int fromIntToInt;
 boolean fromIntToBoolean;
 
 String fromBooleanToString;
 int fromBooleanToInt;
 boolean fromBooleanToBoolean;
 
 String fromNonToExist;

 public boolean isFromBooleanToBoolean() {
  return fromBooleanToBoolean;
 }

 public int isFromBooleanToInt() {
  return fromBooleanToInt;
 }

 public String isFromBooleanToString() {
  return fromBooleanToString;
 }


 public boolean getFromIntToBoolean() {
  return fromIntToBoolean;
 }

 public int getFromIntToInt() {
  return fromIntToInt;
 }

 public String getFromIntToString() {
  return fromIntToString;
 }

 public boolean getFromStringToBoolean() {
  return fromStringToBoolean;
 }

 public int getFromStringToInt() {
  return fromStringToInt;
 }

 public String getFromStringToString() {
  return fromStringToString;
 }

 public void setFromBooleanToBoolean(boolean b) {
  fromBooleanToBoolean = b;
 }

 public void setFromBooleanToInt(int b) {
  fromBooleanToInt = b;
 }

 public void setFromBooleanToString(String b) {
  fromBooleanToString = b;
 }


 public void setFromIntToBoolean(boolean i) {
  fromIntToBoolean = i;
 }

 public void setFromIntToInt(int i) {
  fromIntToInt = i;
 }

 public void setFromIntToString(String i) {
  fromIntToString = i;
 }

 public void setFromStringToBoolean(boolean string) {
  fromStringToBoolean = string;
 }

 public void setFromStringToInt(int string) {
  fromStringToInt = string;
 }

 public void setFromStringToString(String string) {
  fromStringToString = string;
 }

 public String getFromNonToExist() {
  return fromNonToExist;
 }

 public void setFromNonToExist(String string) {
  fromNonToExist = string;
 }

}
------------------------------------------------------




------------------------------------------------------

다음 main 을 가지고 있는 테스트 클래스.

------------------------------------------------------

package com.joldo.test;

import java.util.HashMap;

import org.apache.commons.beanutils.BasicDynaClass;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.beanutils.DynaBean;
import org.apache.commons.beanutils.DynaProperty;
import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.beanutils.WrapDynaBean;

/**
 * @author 카악퇴 joldo@joldo.com
 *
 * 외부 의존 패키지
 * commons-beanutils
 * commons-collections
 * commons-logging
 */
public class SimpleTest {

 public static void main(String[] args) {
 
  Employee emp = new Employee();
 
  /**
   * < PropertyUtils >
   * simple property
   */  
  emp.setFirstName("yoon");
  emp.setLastName("sangjin");
  emp.setNum( 10 );
 
  String firstName = null;
  String lastName = null;
  int num = -1;
 
  try{
   firstName = (String)(PropertyUtils.getSimpleProperty(emp,"firstName"));
   lastName =  (String)(PropertyUtils.getSimpleProperty(emp,"lastName"));
   num = ( (Integer)(PropertyUtils.getSimpleProperty(emp,"num")) ).intValue();


   System.out.println( firstName + ":" + lastName + ":" + num );
  }catch( Exception e){
   System.out.println( e );
  }
 
 
 
 
  /**
   * < PropertyUtils >
   * Indexed property
   */ 
  int index = 0;
  String name = "book";
  String value1 = null;
  String value2 = null;
  try{
   value1 = (String)(PropertyUtils.getIndexedProperty(emp , name , index ));  
   value2 = (String)(PropertyUtils.getIndexedProperty(emp , name + "[" + index + "]"  ));
 
   System.out.println( value1 + ":" + value2 );
  }catch( Exception e){
   System.out.println( e );
  }
 
 
 
  /**
   * < PropertyUtils >
   * Mapped property
   */  
  name = "video";
  String key = "oracle";
  String setValue = "this is oracle's value";
 

  try{
  
   PropertyUtils.setMappedProperty( emp , name + "(" + key + ")" , setValue);  
   String getValue = (String)( PropertyUtils.getMappedProperty(emp , name + "(" + key + ")" ) );
   System.out.println( getValue );
  }catch( Exception e){
   System.out.println( e );
  } 
  /** OR **/
  try{
   PropertyUtils.setMappedProperty( emp , name , key  , setValue);  
   String getValue = (String)( PropertyUtils.getMappedProperty(emp , name , key ) );
   System.out.println( getValue );
  }catch( Exception e){
   System.out.println( e );
  }
 
  /**
   * < PropertyUtils >
   * Nested Property
   */
  name = "address.post";
  Address address = new Address("152-093","seoul korea");
  emp.setAddress( address );
 
  try {
   String post = (String)PropertyUtils.getNestedProperty(emp , "address.post");
   System.out.println( post );  
  
  }catch( Exception e){
   System.out.println( e);
  }
 
  /**
   * < DynaBean , DynaClass >
   *
   * 동적 클래스 정의 및 인스턴스 생성
   */
  DynaProperty[] props = new DynaProperty[]{
   new DynaProperty("firstName", String.class),
   new DynaProperty("lastName",  String.class),
   new DynaProperty("num", Integer.class),
   new DynaProperty("addr", Address.class),
   new DynaProperty("books", String[].class )
  };
 
  BasicDynaClass dynaClass = new BasicDynaClass("employee", null, props);
 
  DynaBean employee;
  try {
   employee = dynaClass.newInstance();

   employee.set("firstName", "lee");
   employee.set("lastName", "sun sin");  
   employee.set("num", new Integer(10));
   employee.set("addr", new Address() );  
   employee.set("books",new String[10]);
   employee.set("books",0,"javabook");
  
   // error code
   //employee.set("books[0]","javabook");
 
   System.out.println( employee.get("firstName") );
   System.out.println( employee.get("lastName"));
   System.out.println( employee.get("num"));
   System.out.println( employee.get("books",0));

  } catch (Exception e) {
   System.out.println( e );
  }
 
 
 
 
 
 
  /**
   * WrapDynaBean
   *
   * 이미 제작되어진 빈즈를 래핑하여
   * DynaBean 인터페이스로서 접근
   *
   */
  DynaBean wrapper = new WrapDynaBean(emp);
  name = "firstName";
  firstName = null;
  firstName = (String) wrapper.get(name);
  System.out.println( name );
 
 
  /**
   * BeanUtils (property converting)
   *
   * 스트링 값을 이용
   * 특정빈의 int,boolean등 primitive 타입으로
   * 변환하여 빈즈 세팅
   */
  Employee empLim = new Employee();
  HashMap map = new HashMap();
  map.put("firstName","lim");
  map.put("lastName","kkuk jung");
  map.put("num","24");
 
  try {
   BeanUtils.populate(empLim,map);
  }catch (Exception e) {
   e.printStackTrace();
  }
 
  System.out.println(empLim.getFirstName());
  System.out.println(empLim.getLastName());
  System.out.println(empLim.getNum());
 
  /**
   * BeanUtils (property copy)
   *
   * from 객체의 getter 메소드와
   * to 객체의 setter 메소드를 추출하여
   * from 객체의 속성값으로부터 to 객체 속성값 세팅
   * 스트러츠의 ActionForm 과 모델측의 VO간의 매핑시
   * 유용하게 쓰일 듯..
   */
 
  FromClass from = new FromClass();
  ToClass to = new ToClass();
 
  from.setFromStringToString("string");
  from.setFromStringToInt("10");
  from.setFromStringToBoolean("true");
  from.setFromIntToString(10);
  from.setFromIntToInt(20);
  from.setFromIntToBoolean(30);
  from.setFromBooleanToString(true);
  from.setFromBooleanToInt(true);
  from.setFromBooleanToBoolean(true);
  from.setFromExistToNon("string"); 
 
 
  try {
   System.out.println("-- before copy --");
   System.out.println( to.getFromStringToBoolean() );
   System.out.println( to.getFromStringToInt()     );
   System.out.println( to.getFromStringToString()  );
   System.out.println( to.getFromIntToBoolean()    );
   System.out.println( to.getFromIntToInt()        );
   System.out.println( to.getFromIntToString()     );
   System.out.println( to.isFromBooleanToBoolean() );
   System.out.println( to.isFromBooleanToInt()     );
   System.out.println( to.isFromBooleanToString()  );
   System.out.println( to.getFromNonToExist()      );
  
   // clone 이 아닌
   // 다른 클래스의 인스턴스간 속성값 copy
   BeanUtils.copyProperties( to, from);

   System.out.println("-- after copy --");
   System.out.println( to.getFromStringToBoolean() );
   System.out.println( to.getFromStringToInt()     );
   System.out.println( to.getFromStringToString()  );
   System.out.println( to.getFromIntToBoolean()    );
   System.out.println( to.getFromIntToInt()        );
   System.out.println( to.getFromIntToString()     );
   System.out.println( to.isFromBooleanToBoolean() );
   System.out.println( to.isFromBooleanToInt()     );
   System.out.println( to.isFromBooleanToString()  );
   System.out.println( to.getFromNonToExist()      );


  }catch (Exception e) {
   e.printStackTrace();
  }
 }
}


********************************************************************************
http://blog.naver.com/rosekingdom/60001408076
스타일시트나 다른 HTML 자원을 참조할 시에는 <html:link rewrite>를 사용하라

html:link rewrite 태그는 컨텍스트 상대적인 URL을 사용할 수 있다. 또한, 필요하다면 URL을 인코딩하는 것도 가능하기 때문에 컨테이너가 보안을 관리하는 자원의 경우 이용할 수 있다.
예제)
<LINK REL=’stylesheet’ HREF=”’<html:rewrite page=’/path/to/CSS”/>’ TYPE=’text/css’>

정적인 페이지 접근을 위한 액션(Action)은 어찌 할 것인가?

모델 2 환경에서 페이지는 깔끔하지만, 처리한 내용만 보여주기 때문에 단순하다고 할 수 있다. 모델 2 환경에서는 액션을 먼저 거친 후, 페이지로 이동을 해야 한다(바로 이 부분이 모델 1에서 웹 프로그래밍 하던 사람들이 고생하는 것으로 역자 또한 그러하다). 그리고 액션은 페이지가 request, session context에 담아서 보낸 정보를 조합하여 나타낼 뷰를 결정해야 한다. 그러나 액션은 뷰의 주소를 모른다(ActionMapping이 알고 있음). 모든 페이지는 하나의 액션 핸들러를 가지고 있어야 한다. 하나의 액션은 다수의 다른 뷰 페이지를 핸들링 할 수 있으며 하나의 페이지는 여러 다른 액션에 의해서 핸들링 될 수 있다. 그러나 하나의 페이지는 적어도 하나의 액션 핸들러를 반드시 가지고 있어야 한다. 여기서 한가지 질문! 그렇다면 static한 페이지에 접근하는 경우, 모두 액션을 가지고 있어야 하는가? 100개의 페이지가 있다면 그것들 모두 액션을 가지고 있어야 하는가? 이런 경우를 대비해서 스트럿츠에는 ForwardAction이라는 액션을 제공한다. 액션 맵핑에 아래와 같이 입력해주면 된다.
예제)
<action    name="boardForm"
            parameter="/boardcreate.jsp"
            path="/boardcreateform"
            type="org.apache.struts.actions.ForwardAction"
            validate="false"/>

다른 건 다 무시하고, parameter와 type만 살펴보자. Boardcreate.jsp은 새로운 글을 입력하는 페이지로, 실제로 액션이 있어도 아무런 역할을 하지 못한다. 이 경우 org.apache.struts.actions.ForwardAction를 액션 타입으로 지정하고, parameter란에 이동하고자 하는 페이지 주소를 적는다. 이 경우는 /boardcreateform.do를 (*.do으로 서블릿 매핑이 되었다고 가정) 주소창에 치면 우선은 ForwardAction을 통과해서 boardcreate.jsp로 포워드 된다. 이렇게 함으로써 모델 2에서 Action을 거쳐서 페이지로 이동해야 한다는 조건을 만족하게 된다.

액션이 하는 일이 매우 작다면 하나의 액션을 가지고 여러 일을 처리하는 방법을 간구하라

간단히 게시판을 만드는 중이었다. 게시물 삭제, 수정, 수정하는 폼, 등록, 답글 달기, 주석 달기 등 이렇게 하나하나 액션을 만들다 보니 관리해야 할 액션 클래스만 몇 십 개가 되어버렸다. 이렇게 하나의 단위로 묶을 만한 액션들 다수가 존재한다면, DispatchAction의 사용을 고려해봐야 한다. 곧바로 예제를 보겠다.
예제)
<action
            input="/board/bullcreateform.jsp"
            name="bullForm"
            parameter="method"
            path="/bull"
            scope="request"
            type="webfx.board.struts.action.BullAction"
            validate="false"/>

다른 액션 맵칭하는 방법과 거의 다르지 않다. 여기서 특이하게 볼 내용은 parameter이다. 그리고 BullAction의 코드는 아래와 같다.

public BullAction extends org.apache.struts.actions.DispatchAction {
 public ActionForward create(
  ActionMapping mapping,
  ActionForm form,
  HttpServletRequest request,
  HttpServletResponse response)
  throws Exception {
 …
};
 public ActionForward remove(
  ActionMapping mapping,
  ActionForm form,
  HttpServletRequest request,
  HttpServletResponse response)
  throws Exception {
 ….
 }

 등등…
}

이 소스에는 execute라는 메소드가 없다. 단지 Method Signature가 이름을 제외하고 execute와 다른 create, remove 함수가 있다. 그리고 이 액션은 org.apache.struts.actions.DispatchAction을 상속했다. 우리가 parameter로 지정한 값은 request로 넘어올 파라미터 이름이다. 그리고 이 파라미터에 우리가 실행시키고자 하는 메소드 명을 지정하면 그 메소드가 실행된다. 예를 들어 /bull.do?method=create라고 실행시켰다면 BullAction에서 create 함수를 실행시키게 된다. 이처럼 관리하기 힘들 정도로 액션이 늘어날 경우에는 DispatchAction을 사용하는 것도 고려해봐야 한다.

데이터와 Value Objects을 교환하기 위해서 populate 유틸리티를 사용하라

Jakarta.apache.org 사이트에 있는 Commons에 속한 BeanUtils를 말하는 겁니다. BeanUtils.populate()와 describe() 메소드는 beans와 임의 타입 간의 데이터 전송 사용을 위해서 사용될 수 있다. 요구되는 것은 대응되는 public 형의 프로퍼티 이름들과 네이티브 타입이다. Wrapping Object 즉, Integer 같은 것들은 지원이 되지만 그 외의 타입은 지원되지 않는다(예, Date).

describe 메소드는 소스 빈을 스트링 값을 가지고 있는 맵으로 만든다. populate 메소드는 그러한 맵을 읽어서 타겟이 되는 빈에 프로퍼티를 적용하는 역할을 한다. 만약 타겟 타입이 스티링이 아니라면 필요한 타입으로 변경이 된다. 만약 변환이 실패하면 예외가 발생한다. 다음이 두 빈(Bean) 간의 데이터 이동하는 소스이다.
BeanUtils.populate(target, BeanUtils.describe(source));

몇몇 경우에 필요한 만큼만 조절한 ActionBean에서 또한 조절된 값을 가지고 있는 Map을 만들기 위해서 커스터마이징한 describe 메소드가 필요할 때가 있다. 다른 값들 (즉, 지원되지 않는 타입)은 직접 세팅해 줄 수 있다.
BeanUtils.populate(target, source.describe());
tartget.setDate(source.getDateBinary());

BeanUtils을 직접 이용하는 것보다 유틸클래스를 하나 만들어, 그곳에서 BeanUtils 클래스를 이용하도록 만드는 게 더 좋을 것이다. 예를 들면 아래와 같다.
valueUtils.populate(ValueObject target, ActionForm source);
valueUtils.populate(ActionForm target, ValuteObject source);

공통적으로 사용되는 속성들에 대한 객체를 만들어 사용하라

웹 애플리케이션 안에서 애플리케이션에 전체적으로 걸쳐서 사용되어야 하는 속성이 있다. 사용자 정보, 북마크, 메뉴 세팅, 다른 프로퍼티 등등이 이것에 속할 수 있다. 이때에는 이런 것들을 각각 따로 관리하는 것보다 이런 것들을 한꺼번에 래핑하는 객체를 만들어서 사용하자.

애플리케이션을 위한 Base Action을 사용하라

애플리케이션 액션들은 몇몇 기본적인 작업을 처리한다. 이러한 태스크를 일관적으로 구현하기 위해, 애플리케이션의 다른 액션들이 서브클래싱하여 사용할 수 있는 Base 클래스를 이용하자! 만약 개개 액션의 execute 메소드 안에서 중요하게 처리되어야 할 일이 있다면 서브 클래스들이 execute() 메소드를 통해서 실행시킬 추상 메소드를 사용하는 것이 좋은 접근 방법이다. Base 클래스는 기본적으로 자신이 해야 할 일을 하고 그것이 모두 잘 처리 되었을 때, 새로운 메소드를 실행시키고 그 결과를 리턴한다. 이 부분 때문에 새로운 메소드의 서명을 변경할 수 있다.
Public ActionForward execute(ActionMapping mapping,
    ActionForm form,
    HttpServletRequest request,
    HttpServletResponse response)
   throws IOException, ServletException {

  // Application specific bevaviour
  // if evevrything is kosher call subclass

  return (executeAction(mapping, form, request, response, myParameter))
}

executeAction은 Base 클래스의 추상 클래스이다.

이중으로 submit이 일어나는 것을 막기 위해서 Action Token을 사용하라

스트럿츠의 액션에 one-use tokens을 만들어 담는 메소드들이 있다. 폼이 작성될 때 세션에 토큰이 위치하게 되고, 이 토큰은 HTML 폼에 hidden 프로퍼티로 포함된다. 그 폼이 리턴 될 때 토큰은 validation이 된다. 만약 validation에 실패하면 벌써 그 폼은 submit이 된 것이므로 사용자는 그에 대한 알림 메시지를 받을 수 있다.
saveToken(request)
on the return trip
isTokenValid(request)
resetToken(request)
스트럿츠를 이용한 웹 애플리케이션 개발 사이클
화면 설계서를 개발한다. (화면 요구사항 설계)
화면 요구사항을 만족하기 위한 Helper 클래스를 개발한다. (value object)
워크플로우 요구사항을 개발한다. (화면 이동 등의 스토리보드 작성)
워크플로우 요구사항을 만족시키기 위한 ActionMapping을 개발한다.
ActionMapping을 사용하여 워크 플로우를 검증하기 위한 Stub JSP 페이지를 작성한다.
ActionMapping에 의해 호출 되는 액션을 개발한다.
Stub Jsp를 작성한다.


Posted by 1010
반응형

Commons-Digester


I. Digester ?

digester는 XML파일로 저장된 정보를 java 객체에 매핑해 주는 API입니다. 하지만 그 반대기능은 되지 않습니다.

보통은 XML파일을 parsing할때 SAX와 DOM을 이용하였지만 DOM은 성능이 좀 느리고, SAX는 DOM보다는 빠르지만 코드가 난잡해 집니다. digester는 SAX기반이지만 pattern 매칭으로 아주 쉽고 빠르게 XML 파일을 parsing합니다


원래 digester는 struts의 struts-config.xml 정보를 로딩하기위해 개발되어 struts의 한 부분이었는데 독립적으로 commons project로 분리되었습니다.


II. 준비물

digester를 사용하기 위해서는 다음 4가지 준비물이 필요합니다

commons-digester http://jakarta.apache.org/site/downloads/downloads_commons-digester.cgi

commons-beanutils http://jakarta.apache.org/site/downloads/downloads_commons-beanutils.cgi

commons-collections http://jakarta.apache.org/site/downloads/downloads_commons-collections.cgi

commons-logging http://jakarta.apache.org/site/downloads/downloads_commons-logging.cgi

만약 digester는 SAX API를 사용하기 때문에 jdk1.4 이상의 버젼이 아니라면 Xerces같은 XML parser가 필요합니다


아래예제를 실행하려면 dbcp 관련 파일도 필요하며 이는 Commons-dbcp 편을 참조하세요~


참고 사이트

commons-digester API http://jakarta.apache.org/commons/digester/commons-digester-1.6/docs/api/index.html

commons-digester Guide http://jakarta.apache.org/commons/digester/apidocs/org/apache/commons/digester/package-summary.html


III. 시작하기 전에..

보통 digester는 여러 설정값들을 xml에 정의해 놓고 이를 어플리케이션에서 로드하는 방식으로 많이 이용됩니다. 이 강좌에서도 데이터베이스 정보를 xml에 정의해 놓고 이를 로딩하여 데이터베이스에 연결하는 예제를 강의할 것입니다.

시작하기 전에 XML에 대한 어느정도 기본 지식이 필요합니다.


주요함수

다른 함수들도 많이만 가장 많이 사용되는 다음 4가지만 딱 눈으로 익히고 갑시다


-. addObjectCreate(element 경로, 자바빈크래스) : 어떤 element 경로를 자바빈클래스로 매칭?

-. addSetProperties(element 속성명, 자바빈프로퍼티명) : 어떤 element 속성을 자바빈 변수에 설정?

-. addBeanPropertySetter(element 경로, 자바빈프로퍼티명) : 어떤 element 경로를 자바빈 변수에 설정?

-. addSetNext(element 경로, 자바빈함수) : 어떤 element 경로를 자바빈 함수에?

대강은 이런 뜻으로 알고 넘어 갑쉬다~!

그럼 element 경로가 먼가요? 다음에 나옵니다


Element Matching Pattern

XML에 element들의 path를 pattern으로 인식하는 방법만 익힙시다.


<a>                 -- Matches pattern "a"
    <b>             -- Matches pattern "a/b"
        <c/>        -- Matches pattern "a/b/c"
        <c/>        -- Matches pattern "a/b/c"
    </b>
    <b>             -- Matches pattern "a/b"
        <c/>        -- Matches pattern "a/b/c"
        <c/>        -- Matches pattern "a/b/c"
        <c/>        -- Matches pattern "a/b/c"
    </b>
</a>


위 XML을 보면 element a가 최상위 루트 element 입니다.

이것은 "a"로 매칭되며 그다음 a의 서브 element b는 "a/b" 로 매칭합니다

그다음은 .. "a/b/c".. 

쉽죠?

즉 최상위만 "/"가 붙지 않으며 그 이하는 트리구조처럼 "/"를 붙여주면 됩니다


자 그럼 좀전에 보았던 함수들과 연관지어 보면..

...

digester.addObjectCreate("a/b", B.class);

digester.addBeanPropertySetter("a/b/c", "c");

...

요렇게 쓰입니다.


IV. Digester를 이용하여 데이터베이스 커넥션 정보를 DBCP로 멀티 설정하여 웹에서 사용해 보자!

자 이제 실질적인 예제를 봅시다~


무엇을 하려고 하려면 mysql과 oracle jdbc정보를 xml 파일에 기록해 두고 이를 딱 한번만 읽어서 이정보를 데이터베이스 커넥션풀인 dbcp에 설정할 것입니다


 XML 파일

다음과 같은 XML 파일이 있습니다. 이 파일은 mysql과 oracle을 연결하는 커넥션 정보를 가지고 있습니다

이 파일이름은 C:\Tomcat 5.0\webapps\ROOT\WEB-INF\classes\config.xml입니다

mysql과 oracle 두개의 jdbc pool을 dbcp로 설정할 것입니다.

이 파일은 제가 임의로 정해서 만든겁니다.


<?xml version="1.0" encoding="EUC-KR"?>


<connection-sources>
   <description>This script is a connection description</description>


   <JDBCConnectionPool name="mysql">
      <description>Mysql Connection Source</description>
      <defaultAutoCommit>true</defaultAutoCommit>
      <defaultReadOnly>false</defaultReadOnly>
      <driverClassName>org.gjt.mm.mysql.Driver</driverClassName>
      <maxActive>10</maxActive>
      <maxIdle>10</maxIdle>
      <maxWait>10000</maxWait>
      <username>unicorn</username>
      <password>iloveyou</password>
      <url>jdbc:mysql://localhost/unicorn</url>
   </JDBCConnectionPool>


   <JDBCConnectionPool name="oracle">
      <description>Oracle Connection Source</description>
      <defaultAutoCommit>true</defaultAutoCommit>
      <defaultReadOnly>false</defaultReadOnly>
      <driverClassName>oracle.jdbc.driver.OracleDriver</driverClassName>
      <maxActive>10</maxActive>
      <maxIdle>10</maxIdle>
      <maxWait>10000</maxWait>
      <username>unicorn</username>
      <password>iloveyoutoo</password>
      <url>jdbc:oracle:thin:@localhost:1521:unicorn</url>
   </JDBCConnectionPool>


</connection-sources>


web.xml

웹 배치파일에 의해 db.ConnectionInitialize.java를 초기 서블릿 컨테이너 로딩시 실행하여 XML정보를 DBCP로 세팅할 것입니다

config.xml 파일경로를 config 파라미터에 설정합니다


<?xml version="1.0" encoding="ISO-8859-1"?>

<!DOCTYPE web-app
    PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
    "http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app>

  <servlet>
     <servlet-name>connectioninitialize</servlet-name>
     <servlet-class>db.ConnectionInitialize</servlet-class>
     <init-param>
        <param-name>config</param-name>
        <param-value>C:\Tomcat 5.0\webapps\ROOT\WEB-INF\classes\config.xml</param-value>
     </init-param>
     <load-on-startup>1</load-on-startup>
  </servlet>
   
</web-app>


db.jdbc.ConnectionSources.java

XML 파일에서도 보면 알수있듯이 connection-sources 서브요소인 description과 JDBCConnectionPool을 저장하는 객체입니다. 변수를 눈여겨 봅시다


package db.jdbc;

import java.util.HashMap;

public class ConnectionSources {

	private String description;
	private HashMap source = new HashMap();
	
	public void setDescription(String description) {
		this.description = description;
	}
	
	public void addSource(JDBCConnectionPool source) {
		this.source.put(source.getName(), source);
	}
	
	public String getDescription() {
		return this.description;
	}
	
	public JDBCConnectionPool getSource(String name) {
		return (JDBCConnectionPool)this.source.get(name);
	}
}

addSource 함수는 여러 JDBCConnectionPool 정보를 ConnectionSourcec 의 source에 저장합니다. 이 함수는 밑에서 다시 나오니 눈여겨 봅시다


db.jdbc.JDBCConnectionPool.java

변수에 대해 단순히 setter, getter로 이루어져 있습니다.

XML 파일을 보면JDBCConnectionPool 의 서브 element들을 저장하는 객체이며 XML파일과  이 java 변수명들과 매칭되는것을 알수 있을겁니다.

대강 감이 오나요? ㅡ.ㅡ? 감좌봐쓰~?


package db.jdbc;

public class JDBCConnectionPool {

	private String name;
	private String description;
	private boolean defaultAutoCommit;
	private boolean defaultReadOnly;
	private String driverClassName;
	private int maxActive;
	private int maxIdle;
	private int maxWait;
	private String username;
	private String password;
	private String url;
	
//for debug public void print() { String toString = "name : "+name+"\n"+ "description : "+description+"\n"+ "defaultAutoCommit : "+defaultAutoCommit+"\n"+ "defaultReadOnly : "+defaultReadOnly+"\n"+ "driverClassName : "+driverClassName+"\n"+ "maxActive : "+maxActive+"\n"+ "maxIdle : "+maxIdle+"\n"+ "maxWait : "+maxWait+"\n"+ "username : "+username+"\n"+ "password : "+password+"\n"+ "url : "+url; System.out.println(toString); } public void setName(String name) { this.name = name; } public void setDescription(String description) { this.description = description; } public void setDefaultAutoCommit(boolean defaultAutoCommit) { this.defaultAutoCommit = defaultAutoCommit; } public void setDefaultReadOnly(boolean defaultReadOnly) { this.defaultReadOnly = defaultReadOnly; } public void setDriverClassName(String driverClassName) { this.driverClassName = driverClassName; } public void setMaxActive(int maxActive) { this.maxActive = maxActive; } public void setMaxIdle(int maxIdle) { this.maxIdle = maxIdle; } public void setMaxWait(int maxWait) { this.maxWait = maxWait; } public void setUsername(String username) { this.username = username; } public void setPassword(String password) { this.password = password; } public void setUrl(String url) { this.url = url; } public String getName() { return this.name; } public String getDescription() { return this.description; } public boolean getDefaultAutoCommit() { return this.defaultAutoCommit; } public boolean getDefaultReadOnly() { return this.defaultReadOnly; } public String getDriverClassName() { return this.driverClassName; } public int getMaxActive() { return this.maxActive; } public int getMaxIdle() { return this.maxIdle; } public int getMaxWait() { return this.maxWait; } public String getUsername() { return this.username; } public String getPassword() { return this.password; } public String getUrl() { return this.url; } }

db.ConnectionInitialize.java

자 이제 여기가 핵심 클래스입니다. 위부분이 모두이해가 되었으면 다음 소스코드를 살펴봅시다

web.xml에서 정의한 config.xml을 로딩하여 파싱하고 그 정보를 DBCP에 설정합니다

mysql과 oracle 두개의 jdbc를 설정하도록 xml에 정의하였었습니다.


package db;

import java.sql.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;

import org.apache.commons.pool.ObjectPool;
import org.apache.commons.pool.impl.GenericObjectPool;
import org.apache.commons.dbcp.ConnectionFactory;
import org.apache.commons.dbcp.PoolingDriver;
import org.apache.commons.dbcp.PoolableConnectionFactory;
import org.apache.commons.dbcp.DriverManagerConnectionFactory;

import org.apache.commons.digester.Digester;
import java.io.*;
import org.xml.sax.SAXException;

import db.jdbc.ConnectionSources;
import db.jdbc.JDBCConnectionPool;

public class ConnectionInitialize extends HttpServlet {

	public void init() throws ServletException {

		String config = null;
		// web.xml의 배치스크립트의 파라미터이름을 가져온다
		Enumeration names = getServletConfig().getInitParameterNames();
		do {
			if(!names.hasMoreElements())
			break;
			String name = (String)names.nextElement();
			String value = getServletConfig().getInitParameter(name).trim();

			System.out.println(name+" : "+value);

			// config에 정의된 XML 파일을 가져온다
			if (name.startsWith("config"))
 				config = value;
		} while(true);

		//Digester를 생성하고
		Digester digester = new Digester();
		//XML 유효성을 검증할것인가?
		digester.setValidating(false);

		//connection-sources 요소를 ConnectionSources.class 객체에 저장하겠다
		digester.addObjectCreate("connection-sources",
		                          ConnectionSources.class);

		//connection-sources/description 요소를
		//ConnectionSources.class의 description 변수에 저장하겠다
		digester.addBeanPropertySetter("connection-sources/description",
						"description");

		//connection-sources/JDBCConnectionPool 요소를 JDBCConnectionPool.class 객체에저장하겠다
		digester.addObjectCreate("connection-sources/JDBCConnectionPool",
		                         db.jdbc.JDBCConnectionPool.class);

		//connection-sources/JDBCConnectionPool 요소의 name 속성을
		//JDBCConnectionPool.class의 name 변수에 저장하겠다
		digester.addSetProperties("connection-sources/JDBCConnectionPool", "name", "name");

		//connection-sources/JDBCConnectionPool/description 을
		//JDBCConnectionPool.class 객에의 description 변수에 저장하겠다
		digester.addBeanPropertySetter("connection-sources/JDBCConnectionPool/description",
                                       "description");

		digester.addBeanPropertySetter("connection-sources/JDBCConnectionPool/defaultAutoCommit",
                                       "defaultAutoCommit");
		digester.addBeanPropertySetter("connection-sources/JDBCConnectionPool/defaultReadOnly",
                                       "defaultReadOnly");
		digester.addBeanPropertySetter("connection-sources/JDBCConnectionPool/driverClassName",
                                       "driverClassName");
		digester.addBeanPropertySetter("connection-sources/JDBCConnectionPool/maxActive",
                                       "maxActive");
		digester.addBeanPropertySetter("connection-sources/JDBCConnectionPool/maxIdle",
                                       "maxIdle");
		digester.addBeanPropertySetter("connection-sources/JDBCConnectionPool/maxWait",
                                       "maxWait");
		digester.addBeanPropertySetter("connection-sources/JDBCConnectionPool/username",
                                       "username");
		digester.addBeanPropertySetter("connection-sources/JDBCConnectionPool/password",
                                       "password");
		digester.addBeanPropertySetter("connection-sources/JDBCConnectionPool/url", "url");

		//connection-sources/JDBCConnectionPool을
		//ConnectionResources.class의 addSource를 이용하여 반복적으로 추가하겠다
		digester.addSetNext("connection-sources/JDBCConnectionPool", "addSource");

		try {
			File file = new File(config);
			//digester 파싱 및 결과 리턴
			ConnectionSources connectionSources =
                                                             (ConnectionSources)digester.parse(file);

			System.out.println(connectionSources.getDescription());
			JDBCConnectionPool mysql = connectionSources.getSource("mysql");
			mysql.print(); //디버깅
			JDBCConnectionPool oracle = connectionSources.getSource("oracle");
			oracle.print(); //디버깅

			//이부분 이하는 Commons-dbcp 부분을 참조하기 바란다
			setupDriver(mysql.getName(),
				mysql.getDriverClassName(),
				mysql.getUrl(),
				mysql.getUsername(),
				mysql.getPassword(),
				mysql.getDefaultAutoCommit(),
				mysql.getDefaultReadOnly(),
				mysql.getMaxActive(),
				mysql.getMaxIdle(),
				mysql.getMaxWait());

			setupDriver(oracle.getName(),
				oracle.getDriverClassName(),
				oracle.getUrl(),
				oracle.getUsername(),
				oracle.getPassword(),
				oracle.getDefaultAutoCommit(),
				oracle.getDefaultReadOnly(),
				oracle.getMaxActive(),
				oracle.getMaxIdle(),
				oracle.getMaxWait());

		} catch (FileNotFoundException filenotfoundexception) {
			System.out.println("Config file not found");
			filenotfoundexception.printStackTrace();
		} catch (Exception exception) {
			exception.printStackTrace();
		}
	}

	public void setupDriver(String poolName,
			String driverClassName,
			String url,
			String username,
			String password,
			boolean defaultAutoCommit,
			boolean defaultReadOnly,
			int maxActive,
			int maxIdle,
			long maxWait) throws Exception {
		try {
		    Class.forName(driverClassName);
		} catch (ClassNotFoundException classnotfoundexception) {
			System.out.println(driverClassName+" is not found");
		    classnotfoundexception.printStackTrace();
		    throw classnotfoundexception;
		}
		
		GenericObjectPool connectionPool = new GenericObjectPool(null);
		connectionPool.setMaxActive(maxActive);
		connectionPool.setMaxIdle(maxIdle);
		connectionPool.setMaxWait(maxWait);
		
		ConnectionFactory connectionFactory
			= new DriverManagerConnectionFactory(url, username, password);
		
		PoolableConnectionFactory poolableConnectionFactory
			= new PoolableConnectionFactory(connectionFactory,
		                                   connectionPool,
		                                   null,
		                                   null,
		                                   defaultReadOnly,
		                                   defaultAutoCommit);
		
		Class.forName("org.apache.commons.dbcp.PoolingDriver");
		PoolingDriver driver = (PoolingDriver) DriverManager.getDriver("jdbc:apache:commons:dbcp:");
		
		driver.registerPool(poolName,connectionPool);    
	}
}

그럼 이렇게 설정한 DBCP는 어떻게 사용할까요?

이전강좌에서 보았던 ConnectionContext.java와 ConnectionResource.java를 다시 사용해봅시다

package db;

public interface ConnectionContext {
	public java.sql.Connection getConnection();
	public void rollback();
	public void commit();
	public void release();
}

ConectionContext를 구현한 ConnectionResource는 풀이름을 받아 처리하는것으로 수정하였습니다

package db;

import java.sql.Connection;
import java.sql.DriverManager;
import javax.sql.DataSource;

public class ConnectionResource implements ConnectionContext {
	private Connection connection = null;
	private boolean transaction = false;

    public ConnectionResource(String poolName) throws Exception {
    	init(false, poolName);	
    }
    
    public ConnectionResource(boolean transaction, String poolName) throws Exception {
    	init(transaction, poolName);
    }
    
    public void init(boolean transaction, String poolName) throws Exception {
    	this.transaction = transaction;
    	connection = DriverManager.getConnection("jdbc:apache:commons:dbcp:"+poolName);
if (transaction) connection.setAutoCommit(false); if (connection == null) throw new Exception("fail to get connection"); } public Connection getConnection() { return connection; } public void rollback() { if (transaction) { if (connection != null) try { connection.rollback(); } catch (Exception e) {} } } public void commit() { if (transaction) { if (connection != null) try { connection.commit(); } catch (Exception e) {} } } public void release() { { if (connection != null) { if (transaction) { try { connection.setAutoCommit(true); } catch (Exception e) {} } try { connection.close(); } catch (Exception e) {} }    } }

jsp 파일

마지막으로 ConnectionContext는 풀이름과 함께 사용하면 되겠네요

<%@ page contentType="text/html;charset=EUC_KR" %>
<%@ page import="java.sql.*,db.*" %>

<%

ConnectionContext mysqlContext = new ConnectionResource("mysql");
// 혹은 ConnectionContext oracleContext = new ConnectionResource("oracle");


Connection connection = null;


try {


    connection = mysqlContext.getConnection();
    out.println(connection);


} catch (Exception exception) {
    exception.printStackTrace();
} finally {
    if (connection != null) try { mysqlContext.release(); } catch (Exception exception) {}
}

%>


DBCP 부분은 이전 강좌인 Commons-dbcp (http://www.jakartaproject.com/article/jakarta/1111890409958 )부분을 참조하세요~


ps. 휴 =3 다썼당 ^_^

     흠.. 쉽게쓴다고 이리 저리 하루종일 썼는데 설명이 많이 부족한것 같네요

     그밖에 사항은 알아서 찾아보기~!

     digester II탄에서는 xml 설정파일로부터 규칙을 읽어 xml을 파싱하는 방법을 알아보겠습니다~


=============================================

본문서는 자유롭게 배포/복사 할수 있지만

이문서의 저자에 대한 언급을 삭제하시면 안됩니다

저자 : GoodBug (unicorn@jakartaproject.com)

최초 : http://www.jakartaproject.com 

=============================================

 
Posted by 1010
반응형

Commons-DbUtils

 

I. 어디서 다운을 받나요?


http://jakarta.apache.org/site/downloads/downloads_commons-dbutils.cgi

http://jakarta.apache.org/commons/dbutils/apidocs/index.html


II. 설치는 어떻게 하나요?

다운 받은 commons-beanutils.jar는 자신의 /WEB-INF/lib/ 에 복사합니다


III. DbUtils란 무엇인가요?

DbUtils는 JDBC 작업을 좀더 쉽게 할수있도록 해주는 작은 클래스 집합입니다.


IV. 왜 DbUtils를 사용해야 하는가요?

① resource 누출에 대한 어떠한 가능성도 없습니다
  JDBC코딩을 하는데 있어서 쉽지않고 양도 만만치 않으며 지루해 지기 쉽습니다
  이러다 보면 자기도 모르게 Connection 누수를 발생시킬수 있는데 이러한 가능성을 배재해 줍니다


② 코드의 가독성이 높아집니다
  데이터베이스 처리하는데 필요한 코드의 양을 절대적으로 줄여야 합니다.
  남아있는 코드로 당신의 의도를 정확하게 나타내어야 합니다.


③ ResultSet으로 부터 JavaBean property로 세팅을 해줍니다!
  더이상 setter메소드를 이용하여 ResultSet으로부터 컬럼값을 가져오는 코딩을 하지 않아도 됩니다
  ResultSet 각각의 row는 bean instance의 에 완벽하게 전달해 줍니다


V. 어떻게 사용하나요?

  Connection, Statement, ResultSet 의 close를 간단하게!

    이럴때는 org.apache.commons.dbutils.DbUtils 클래스를 이용하자!

    이 클래스는 모두 static 메소드들로 구성되어있습니다

   

    사용예)

    DbUtils.close(conn);  
    DbUtils.close(stmt);
    DbUtils.close(rs);
    DbUtils.closeQuietly(conn);
    DbUtils.closeQuietly(stmt);
    DbUtils.closeQuietly(rs);
    DbUtils.closeQuietly(conn, stmt, rs);
    DbUtils.commitAndClose(conn);

    DbUtils.commitAndCloseQuietly(conn);
    DbUtils.loadDriver("com.mysql.jdbc.Driver");
    DbUtils.rollback(conn);


    closeQuietly 메소드처럼 뒤에 Quietly라고 붙어 있는 메소드는 익셉션 처리는 자체적으로 처리합니다,

    즉 자신을 call한곳으로 throw 하지 않습니다

    commitAndCloses는 connection을 commit 후 close 하며 rollback는 connection을 rollback 합니다

    loadDriver 는 JDBC 드라이버를 로딩 합니다


 파일로 저장된 SQL을 사용하자!

    이럴 때는 org.apache.commons.dbutils.QueryLoader 클래스를 이용합니다

    이 클래스는 SingleTone 패턴의 클래스입니다

    즉 파일로 저장된 SQL을 읽어 HashMap으로 로드하는 클래스 입니다

   

    사용예)

    QueryLoader queryloader = QueryLoader.getInstance();   //싱글톤
    HashMap hashmap = queryloader.load("sql");      

    queryloader.unload("sql");


    queryloader는 싱클톤이므로 위와같이 객체를 얻어옵니다

    load 함수는 Properties 클래스를 이용하여 sql.properties 파일을 읽어

    HashMap으로 저장하여 리턴하여 줍니다

    unload는 load시 따로 메모리에 저장해 놓았던 sql 정보를 해제합니다


  Setter함수로 더이상 머리 아프지 말자!

   이럴때는 org.apache.commons.dbutils.QueryRunner 클래스를 이용합니다


   사용예)

    ...  

    BoardVO boardVO = null;

    ArrayList arraylist = new ArrayList();

 

    resultset = statement.executeQuery("SELECT * FROM board_t");

    while (resultset.next()) {

        boardVO = new BoardVO();

        boardVO.setTitle("title");

        boardVO.setContent("content");

        boardVO.setWriter("writer");

        arraylist.add(boardVO);       

    }

    ..

   와 같은 코드는 다음과 같이 간략화 됩니다


   ResultSetHandler rsh= new BeanListHandler(BoardVO.class);
   QueryRunner queryRunner = new QueryRunner();

   List list = (List)queryRunner.query(conn, "SELECT * FROM board_t", rsh);


   정말 간단해 집니다 만약 테이블에 컬럼이 30~40개가 된다면..

   select 한문장 할려면 코드수가 몇십줄 입니다. 더이상 노가다 하지 맙시다~


   QueryRunner는 다음과 같은 함수를 지원합니다

   사용예)

  QueryRunner queryrunner = new QueryRunner();
   QueryRunner queryrunner = new QueryRunner(DataSource ds);
//datasource를 바로 이용할 수 있다

  queryRunner.query(Connection conn, String sql, ResultSetHandler rsh)
   queryRunner.query(Connection conn, String sql, Object param, ResultSetHandler rsh)
   queryRunner.query(Connection conn, String sql, Object[] params, ResultSetHandler rsh)


   여기서 말하는 Object param은 파라미터 전달시 사용됩니다

   ArrayList params = new ArrayList();
   params.add("100");

   params.add("200");

   ResultSetHandler rsh = new BeanListHandler(BoardVO.class);
   QueryRunner queryRunner = new QueryRunner();

   List list = (List)queryRunner.query(conn, "SELECT * FROM board_t WHERE boardNo > ? and boardNo < ?", params.toArray(), rsh);


   select 뿐만 아니라 update, delete역시 가능합니다

   사용예)

   QueryRunner queryRunner = new QueryRunner();

   queryRunner.update(Connection conn, String sql)
   queryRunner.update(Connection conn, String sql, Object param)
   queryRunner.update(Connection conn, String sql, Object params[])

  

   ArrayList params = new ArrayList();
   params.add(boardId);
   queryRunner.update(connection, "UPDATE board_t SET read = read + 1 WHERE boardNo = ?", params.toArray());

   와 같이 사용할 수 있습니다



VI. 샘플코드

public class DbUtilsExample() {

   public static void main(String[] args) {

       HashMap map = QueryLoader.getInstance().load("sql");  // (주의) load함수는 실행할때마다 파일을 읽습니다


       Connection conn = null;

       try {

           DbUtils.loadDriver("com.mysql.jdbc.Driver");

           conn = DriverManager.getConnection("jdbc:mysql://localhost/mysql", "root", "");


           ArrayList params = new ArrayList();
           params.add(args[0]);


           ResultSetHandler rsh = new BeanListHandler(BoardVO.class);

           QueryRunner qr = new QueryRunner();

           List list = (List)qr.query(conn, (String)map.get("select"), params.toArray(), rsh);


           for (int i = 0; i < list.size(); i++) {

               BoardVO board = (BoardVO)list.get(i);

               System.out.println(board.getTitle());

               System.out.println(board.getContent());

               System.out.println(board.getWriter());

          }

       } catch (Exception e) {

           System.out.println(e);

       } finally {

           DbUtils.closeQuietly(conn);

       }

   }

}



=============================================

본문서는 자유롭게 배포/복사 할수 있지만

이문서의 저자에 대한 언급을 삭제하시면 안됩니다

저자 : GoodBug (unicorn@jakartaproject.com)

최초 : http://www.jakartaproject.com 

=============================================

Posted by 1010
반응형

DbUtils 몇가지 예제


DBUtils 기본은 다음 링크를 참조하세요

http://www.jakartaproject.com/article/jakarta/1108193481660


설정방법

   DB유틸 설정 방법은 특별히 없습니다  그냥 다운받은 클래스 패스 잡으시면 됩니다

   Application에서 사용시에는 환경변수나 실행시 클래스 패스를 잡으면 되고요,

   웹에서 사용한다면 해당 어플리케이션의 /WEB-INF/lib/ 에 commons-beanutils.jar 를 복사하면 됩니다


   기본적인 문서는 http://www.jakartaproject.com/article/jakarta/1108193481660 를 보세요


   Download http://jakarta.apache.org/site/downloads/downloads_commons-dbutils.cgi

   API http://jakarta.apache.org/commons/dbutils/apidocs/index.html


SELECT 예제 (여러건)


<%@ page contentType="text/html;charset=EUC_KR" %>
<%@ page import="com.jakartaproject.board.vo.*,org.apache.commons.dbutils.*,java.sql.*,java.util.*, org.apache.commons.dbutils.handlers.*" %>

<%
       Connection conn = null;

       try {

           DbUtils.loadDriver("com.mysql.jdbc.Driver");

           conn = DriverManager.getConnection("jdbc:mysql://localhost/test", "test", "1111");


           ArrayList params = new ArrayList();
           params.add("1%");


           ResultSetHandler rsh = new BeanListHandler(BoardVO.class);

           QueryRunner qr = new QueryRunner();

           List list = (List)qr.query(conn, "SELECT boardTitle, boardContent, userNick FROM board_test_t WHERE userIp like ?", params.toArray(), rsh);


           for (int i = 0; i < list.size(); i++) {

               BoardVO board = (BoardVO)list.get(i);

               System.out.println(board.getBoardTitle());

               System.out.println(board.getBoardContent());

               System.out.println(board.getUserNick());

          }

       } catch (Exception e) {

           System.out.println(e);

       } finally {

           DbUtils.closeQuietly(conn);

       }
%>


SELECT 예제 (한건)

select 처리 건수가 1건일 경우에는 MapHandler를 사용하면 됩니다

<%@ page contentType="text/html;charset=EUC_KR" %>
<%@ page import="org.apache.commons.dbutils.*,java.sql.*,java.util.*, org.apache.commons.dbutils.handlers.*" %>

<%
       Connection conn = null;

       try {

           DbUtils.loadDriver("com.mysql.jdbc.Driver");

           conn = DriverManager.getConnection("jdbc:mysql://localhost/test", "test", "1111");


           ResultSetHandler rsh = new MapHandler();

           QueryRunner qr = new QueryRunner();

           Map map = (Map)qr.query(conn, "SELECT count(*) cnt FROM board_test_t", rsh);

           System.out.println(map.get("cnt"));

           

       } catch (Exception e) {

           System.out.println(e);

       } finally {

           DbUtils.closeQuietly(conn);

       }
%>

핸들러에는 아래와 같이 여러 종류의 핸들러 들이 있으며,

ArrayHandler, ArrayListHandler, BeanHandler, BeanListHandler, ColumnListHandler, KeyedHandler, MapHandler, MapListHandler, ScalarHandler

그때그때 맞춰 사용하면 됩니다



UPDATE 예제

이 예제는 Unicorn 소스에 있는 예입니다

Unicorn 소스를 다운받아 /src/com/jakartaproject/admin/dao/AdminMySqlDAO.java 를 열어 보시면 Update, Insert 예제를 볼수 있습니다


public void setBoardCommonSecurity(ConnectionContext connectioncontext,

                                                  AdminForm adminForm) throws BaseException {


        String updateQuery = "UPDATE board_common_t SET badIp=?, badId=?, badNick=?, badContent=?, inputPerMin=?, tryLogin=?";

        try {
            ArrayList params = new ArrayList();
            params.add(encode(adminForm.getBadIp()));
            params.add(encode(adminForm.getBadId()));
            params.add(encode(adminForm.getBadNick()));
            params.add(encode(adminForm.getBadContent()));
            params.add(String.valueOf(adminForm.getInputPerMin()));
            params.add(String.valueOf(adminForm.getTryLogin()));

            QueryRunner queryRunner = new QueryRunner();
            queryRunner.update(connectioncontext.getConnection(), encode(updateQuery), params.toArray());

        } catch (Exception e) {
            logger.error("Error at AdminDAO.setBoardCommonSecurity",e);
            BaseException baseException = new BaseException("errors.sql.problem");
            throw baseException;
        }

        logger.info("AdminDAO.setBoardCommonSecurity was executed");
    }

Posted by 1010
반응형

1. jakarta Project BeanUtils 소개

요즘 자주 사용하는 스트럿츠, 스프링 등의 프레임워크를 보면 BeanUtils를 자주 사용하는 걸 볼 수 있습니다.
자바 객체의 속성들을 동적으로 파악해서 필요한 처리를 해야 하는 경우가 점차 증가하고 있는것입니다.

기존의 Reflection와 Introspection API를 이용해서 구현할 수 있지만  API를 이해하고 활용하기가 매우 복잡하고 까다롭기도 합니다. 좀 더 쉽고 편하게
이용할 수 없을까 하는 needs에 의해 만들어 진게 BeanUtils 컴포넌트입니다.

이 컴포넌트를 처음 본다고 하시는 분도 계시겠지만 실제 우리 소스에서 검색해보면 사용 페이지가 제법 나오기도 하며 스프링 프레임워크가 적용되지 않은 페이지에서 쉽고 편하게 사용할 수 있는 모듈이기도 합니다.
이클립스에서 BeanConverterUtils, BeanUtils로 검색해보세요^^

일단 우리가 현재 이 모듈을 어떻게 사용하고 있는지 보겠습니다.
검색을 보면 request로 넘기는 무수히 많을 값을 ReqPrdSearch 객체에 값을 자동으로
넣어 주는 걸 볼 수 있습니다.

검색을 하면 get 방식으로 다음처럼 값이 넘어 갑니다.
fld=&so=0&mfr=&attr_1=&attr_2=&attr_3=&attr_4=&attr_5=&attr_6=&am1=&am2=&am3=&am4=&am5=&am6=&mfm=&cm=&sn=41&pg=20&rq=&price1=-1&price2=-1&searchColl=ALL&tq=mp3&cat0=&cat1=&cat2=&catdepth=-1&searchListType=A

이 값들을 어떻게 처리할까요?
그냥 useBean 쓰면 안되나요? 안됩니다.^^
jsp가 아닌 bean에서 처리할려 하니 쓸 수가 없습니다.
물론 스프링에서 아래처럼하면 됩니다.
bind(request, ReqPrdSearch);

하지만 스프링를 사용하지 않은 경우 어떻게 하나요?
beanUtils를 이용하시면 됩니다.

request를 객체에 담는거 이외에 또 어디에서 사용하고 있을까요?

DB로 부터 가져온 값을 처리할 때 이용하고 있습니다.
spring jdbc를 이용해서 DB로부터 값을 가져와서 Map(GSData)에 넣고 이용하는데
그 값을 객체에 넣고 사용할 때가 있습니다. 소스를 추적하다 보면 값넣어 주는
부분이 안보이는데 잘 작동이 되는 걸 볼 수 있습니다.

알게 모르게 이미 우리 안에서 사용되고 있으며
알면 편하고 쉽게 쓸 수 있어서 간단하게 BeanUtils를 소개합니다.

2. reference
http://jakarta.apache.org/commons/beanutils/
이곳에 가시면 각종 guide문서와 최신 jar파일을 다운 받으실수 있습니다.

http://jakarta.apache-korea.org/commons/beanutils.html
영어에 거부 반응이 있으신분은 여기 가보시면 번역된 내용을 접하실 수 있습니다.

http://www.jakartaproject.com/
자카르타프로젝트라는 책을 쓴 최범균씨 홈페이지인데 좋은 정보를 얻을 수 있습니다.

3. 주요 클래스
3-1. BeanUtils
     객제의 정보, 속성 값 읽기, 속성 값 쓰기 등의 기능을 제공하고 있으며
     PropertyUtils와 차이점은 value를 convert 해준다는 겁니다.
3-2. PropertyUtils
     BeanUtils와 기능은 거의 흡사합니다.
3-3. ConvertUtils
     타입에 따라 convet 하는 기능을 수행합니다.
    
4. 상세 내용
   BeanUtils를 사용하기 위해서는 몇가지 규칙이 있습니다.
   관련 부분은 필요시 사이트나 책을 찾아 보시면 나옵니다.

   BeanUtils, PropertyUtils, ConvertUtils는 모두 static한 메소드를 가지고 있으며
   BeanUtilsBean, PropertyUtilsBeanUtilsBean, ConvertUtilsBeanUtilsBean 이라는
   싱글톤으로 구현된 객체와 wrapping되어 있는걸 볼 수 있습니다.
   ...
   public static Object cloneBean(Object bean)
           throws IllegalAccessException, InstantiationException,
           InvocationTargetException, NoSuchMethodException {
       return BeanUtilsBean.getInstance().cloneBean(bean);
   }
   ...
   나름대로 참고할 만한 구조 같습니다. ^^
  
4-1. BeanUtils

// bean 복제
public static Object cloneBean(Object bean)

// orig에서 dest로 복제, 동일 속성명이 존재해야합니다.
public static void copyProperties(Object dest, Object orig)

// orig의 property를 dest로 복제
public static void copyProperty(Object bean, String name, Object value)

// Return the value of the specified array property of the specified bean, as a String array.
public static String[] getArrayProperty(Object bean, String name)

// 배열값 가져오기, property name을 'name[0]' 이런 식으로 주어야 한다. 규칙임
public static String getIndexedProperty(Object bean, String name)

// 배열값 가져오기 index는 몇번째
public static String getIndexedProperty(Object bean, String name, int index)

// mapped property 가져오기, property name을 'name(0)' 이런 식으로 주어야 한다.
public static String getMappedProperty(Object bean, String name)
// mapped property 가져오기
public static String getMappedProperty(Object bean, String name, String key)

public static String getNestedProperty(Object bean, String name)

// bean에서 값 가져오기
public static String getProperty(Object bean, String name)

public static String getSimpleProperty(Object bean, String name)

// bean에서 해당 name의 property에 value를 convert해서 넣는다.
public static void setProperty(Object bean, String name, Object value)

/*
 * bean 있는 값을 key, value로 map에 넣어 줍니다. 가장 많이 쓰이는 메소드 중 하나
 */
public static Map describe(Object bean)

/*
 * map에 있는 값을 bean에 넣어 줍니다. 가장 많이 쓰이는 메소드 중 하나
 */
public static void populate(Object bean, Map properties)

describe메소드를 이용해서 객체의 property와 value, type등을 쉽게 알아낼 수 있습니다.

Map map = BeanUtils.describe(bean);
Set set = map.keySet();
Iterator it = set.iterator();
while (it.hasNext()) {
    String key = (String)it.next();

    if ("class".equals(key)) {
        continue;
    }
    Object value = map.get(key);
    properties.put(key, value);
}
            
4-2. PropertyUtils
     BeanUtils와 거의 동일합니다.
     BeanUtils가 bean를 다루기위한거라면 PropertyUtils는 map을 처리한다고 보시면
     됩니다.

4-3. ConvertUtils
     이 utils은 싱글톤으로 구현된 ConvertUtilsBean에서 맵에 각종 컨버터를
     등록해 놓고 lookup(Class clazz)해서 converter 얻고 그걸로 값을 처리하고
     있습니다.
     조금만 수정하면 아주 잘 써먹을 수 있는 util이길래 언급합니다.

5. example & test

배열, 기본형, 객체를 property로 하는 dto들을 만들어 잘 처리가 되는지 확인합니다.

Person.java : dto안에 또 다른 dto를 넣어서 잘 처리되는지 확인하기 위해 만들었습니다.

public class Person {
    private String name;
    private int age;

    public Person(String name, int age)
    {
        this.name = name;
        this.age = age;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }

    public String toString()
    {
        final String TAB = "    ";

        String retValue = "";

        retValue = "Person ( "
            + "name = " + this.name + TAB
            + "age = " + this.age + TAB
            + " )";

        return retValue;
    }
}


Employee.java : 기본형, 배열, 객체 등을 property로 생성합니다.

public class Employee {
    private String address;
    private String firstName;
    private String lastName;
    private int age;
    private Person person;
    private Date credate;
    private List personList;
    private String[] fld1;
    private int[] fld2;

    public String[] getFld1() {
        return fld1;
    }
    public void setFld1(String[] fld1) {
        this.fld1 = fld1;
    }
    public int[] getFld2() {
        return fld2;
    }
    public void setFld2(int[] fld2) {
        this.fld2 = fld2;
    }
    public List getPersonList() {
        return personList;
    }
    public void setPersonList(List personList) {
        this.personList = personList;
    }
    public String getAddress() {
        return address;
    }
    public void setAddress(String address) {
        this.address = address;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public Date getCredate() {
        return credate;
    }
    public void setCredate(Date credate) {
        this.credate = credate;
    }
    public String getFirstName() {
        return firstName;
    }
    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }
    public String getLastName() {
        return lastName;
    }
    public void setLastName(String lastName) {
        this.lastName = lastName;
    }
    public Person getPerson() {
        return person;
    }
    public void setPerson(Person person) {
        this.person = person;
    }
    public String toString()
    {
        final String TAB = "    ";

        String retValue = "";

        retValue = "Employee ( "
            + "address = " + this.address + TAB
            + "firstName = " + this.firstName + TAB
            + "lastName = " + this.lastName + TAB
            + "age = " + this.age + TAB
            + "person = " + this.person + TAB
            + "credate = " + this.credate + TAB
            + "personList = " + this.personList + TAB
            + " )";

        return retValue;
    }

}


BeanConvertUtils.java

public class BeanConvertUtils {
    //map의 value을 bean에 넣어주는 메소드
 public static void mapToBean(java.util.Map properties, java.lang.Object bean)
   throws IllegalAccessException, InvocationTargetException,
   NoSuchMethodException {

  if (properties == null) {
   return;
  }

  BeanUtils.populate(bean, properties);

 }

    //bean의 value을 map에 넣어주는 메소드
 public static void beanToMap(java.lang.Object bean, java.util.Map properties)
   throws IllegalAccessException, InvocationTargetException,
   NoSuchMethodException {

  Map map = PropertyUtils.describe(bean);

  map.remove("class");
  properties.putAll(map);

 }
}


TestBeanConvertUtils : 테스트 케이스

public class TestBeanConvertUtils extends TestCase {
    Employee emp;
    GSData map;
    HttpServletRequestMock request;
    public void setUp() {
        List list = new ArrayList();
        list.add(new Person("kkaok1", 23));
        list.add(new Person("kkaok2", 22));

        emp = new Employee();
        emp.setAddress("경기도");
        emp.setFirstName("kim");
        emp.setLastName("hyun");
        emp.setPerson(new Person("kkaok", 22));
        emp.setPersonList(list);
        emp.setAge(22);
        emp.setFld1(new String[]{"0","1","2"});

        map = new GSData();
        //map = new HashMap();
        map.put("address", "경기도");
        map.put("firstName", "kim");
        map.put("lastName", "hyun");
        map.put("person", new Person("kkaok", 22));
        map.put("personList", list);
        map.put("age", new Integer(22));
        map.put("fld1", new String[]{"0","1","2"});
    }

    public void testmapToBean() throws IllegalAccessException, InvocationTargetException, NoSuchMethodException{
        Integer i1= new Integer(0);

        new ConvertUtilsBean().convert("0", i1.getClass());

        Employee emptest = new Employee();
        BeanConvertUtils.mapToBean(map, emptest);
        //System.out.println(emp.toString());
        //System.out.println(emptest.toString());
        assertEquals(emp.toString(), emptest.toString());
    }

    public void testbeanToMap() throws IllegalAccessException, InvocationTargetException, NoSuchMethodException{
        Map maptest = new HashMap();
        BeanConvertUtils.beanToMap(emp, maptest);
        //System.out.println(map.toString());
        //System.out.println("maptest : "+maptest.toString());

        assertEquals(map.get("address"), maptest.get("address"));
        assertEquals(map.get("age"), maptest.get("age"));
    }

}



6. 마무리

   map에 있는 값을 bean에 넣거나
   bean에 있는 값을 map에 넣거나
   request에 있는 값을 bean에 넣거나
   동적으로 변하는 bean을 분석해야 할때 정말 편하고 쉽게 쓸 수 있는 모듈입니다.

   한번 소스를 뜯어 보시면 좋은 내용을 접하실 기회가 되실 거라 믿습니다.

7. 참고

   eclipse에서 테스트 하실때는 commons-beanutils.jar, commons-beanutils-bean-collections.jar,  commons-beanutils-core.jar, commons-logging-1.1.jar, junit-3.8.1.jar 등을 빌드패스에 넣어 주세요

  * request의 값을 빈에 넣는 예)
  BeanUtils.populate(객체, request.getParameterMap());
  * request.getParameterMap() 이렇게 하면 map에 key, value로 값이 return 됩니다.

출처 : Tong - avcom18님의 spring통

Posted by 1010
반응형

Commons BeanUtil 에는 참 유용한게 많다..


그중 RowSet 과 비스무리(?) 한 것이 하나 있는데..


바로 RowSetDynaClass 이다...



일반적 JDBC 프로그래밍에서는 아래와 같은 순서를 가진다.


pstmt = t.prepareStatemet(query);

pstmt.setXXX(....);

rs = pstmt.executeQuery();


while(rs.next()){

 ... 작업들.....

}


rs.close();


이런 구조를 가지고 가게 되며, 커넥션이 닫히기 전에 resultSet 을 순차적으로 내려가면서 작업을 한다.


Commons BeanUtils 안에는 RowSetDynaClass 라는 클래스가 있다.

이 클래스는 JDBC 연결이 끊어진 이후에도 사용가능한 CachedRowSet 과 비슷한 기능을 제공해준다.


이것의 사용법은 상당히 간단하다. 참조 코드는 다음과 같다.


   pstmt = t.prepareStatement(query);
   rs = pstmt.executeQuery();
   rsdc = new RowSetDynaClass(rs);
  
   rs.close();

....


finally{   ... conn.close(); }


요기까지가 끝이다.. 이후에는 rsdc 라는 녀석을 이용해서 데이터를 취득하면 된다.

이렇게 데이터를 가져오게 되면

rsdc 에서는 getRows() 메소드를 이용하여 List 객체를 취득할 수 있다.


List arr = rsdc.getRows();


또한 검색해 온 컬럼의 이름들을 Property 로 얻을 수도 있다.


 DynaProperty[] dynas = rsdc.getDynaProperties();
 
 for(int i = 0; i < dynas.length; i++){
  System.out.println(dynas[i].getName());
 }


DynaProperty 클래스는 일반적인 Property 개념으로 보면 된다.


또한 이렇게 얻어온 DynaProperty 배열객체를 이용하여 RowSetDynaClass 를 순환하며 내용을 참조할 수 있다.


List 형태로 반납된 rows 들을 순환하게 되면 DynaBean 이라는 객체를 반납하게 된다.

이 형태를 이용하여 rs.next() 작업과 동일한 결과를 얻을 수 있다.


 Iterator iter = arr.iterator();
 
 while(iter.hasNext()){
  DynaBean bean = (DynaBean)iter.next();
 
  for(int i = 0; i < dynas.length; i++){
   System.out.println(bean.get(dynas[i].getName()));
  }
 }



사용하다 주의할 것은 테이블 컬럼명이 regiDate 와 같이 중간에 대문자가 끼어있을 경우이다

이렇게 중간에 대문자가 끼어있는 것들은 RowSetDynaClass 를 생성하는 과정에서

전부 소문자로 바뀌게 된다.

SQL 상에서는 대소문자를 가리지 않지만 객체에서 값을 얻어올때는 가린다는것에 주의!!!


그렇기 때문에 bean.get("regidate") 와 같이 프라퍼티를 전부 소문자로 적어야

제대로 출력될 것이다.



DAO Layer 측에서 해야할 일들이 DAO를 이용하는 Layer 에 종속적인 작업이 진행될 경우

RowSetDynaClass 를 이용하여 Action 측에서 사용하면 각 레이어의 할일이 조금더 분명해 질수 있다.



 

Posted by 1010
반응형

BeanUtils
map에 있는 값을 bean에 넣거나  , bean에 있는 값을 map에 넣거나   , request에 있는 값을 bean에 넣거나
동적으로 변하는 bean을 분석해야 할때 정말 편하고 쉽게 쓸 수 있는 모듈.
객제의 정보, 속성 값 읽기, 속성 값 쓰기 등의 기능을 제공.

eclipse에서 테스트 할 땐 commons-beanutils.jar, commons-beanutils-bean-collections.jar,  commons-beanutils-core.jar, commons-logging-1.1.jar, junit-3.8.1.jar 등을 빌드패스에 넣어야 함.





// bean 복제
public static Object cloneBean(Object bean)

// orig에서 dest로 복제, 동일 속성명이 존재해야합니다.
public static void copyProperties(Object dest, Object orig)

// orig의 property를 dest로 복제
public static void copyProperty(Object bean, String name, Object value)

// Return the value of the specified array property of the specified bean, as a String array.
public static String[] getArrayProperty(Object bean, String name)

// 배열값 가져오기, property name을 'name[0]' 이런 식으로 주어야 한다. 규칙임
public static String getIndexedProperty(Object bean, String name)

// 배열값 가져오기 index는 몇번째
public static String getIndexedProperty(Object bean, String name, int index)

// mapped property 가져오기, property name을 'name(0)' 이런 식으로 주어야 한다.
public static String getMappedProperty(Object bean, String name)
// mapped property 가져오기
public static String getMappedProperty(Object bean, String name, String key)

public static String getNestedProperty(Object bean, String name)

// bean에서 값 가져오기
public static String getProperty(Object bean, String name)

public static String getSimpleProperty(Object bean, String name)

// bean에서 해당 name의 property에 value를 convert해서 넣는다.
public static void setProperty(Object bean, String name, Object value)

/*
 * bean 있는 값을 key, value로 map에 넣어 줍니다. 가장 많이 쓰이는 메소드 중 하나
 */
public static Map describe(Object bean)

/*
 * map에 있는 값을 bean에 넣어 줍니다. 가장 많이 쓰이는 메소드 중 하나
 */
public static void populate(Object bean, Map properties)



describe메소드를 이용해서 객체의 property와 value, type등을 쉽게 알아낼 수 있음.

Map map = BeanUtils.describe(bean);
Set set = map.keySet();
Iterator it = set.iterator();
while (it.hasNext()) {
    String key = (String)it.next();

    if ("class".equals(key)) {
        continue;
    }
    Object value = map.get(key);
    properties.put(key, value);
}

Posted by 1010
반응형
먼저 아래사이트에서 필요 라이브러리를 다운로드 받는다...

http://commons.apache.org/beanutils/
http://commons.apache.org/logging/

필요라이브러리는 아래와 같다.
/*현재 최신버전은 1.8.0 이다.*/
commons-beanutils. jar
commons-beanutils-bean-collections.jar
commons-beanutils-core.jar

/*현재 최신버전은 1.1.1 이다.*/
commons-logging.jar

테스트 Person 객체.. 코드

public class Person {
 private String firstName;
 private String lastName;
 private Address address;
 private String phoneNumber;
 private long personId;
 public Person() {
  address = new Address();
 }
 public String getFirstName() {
  return firstName;
 }
 public void setFirstName(String firstName) {
  this.firstName = firstName;
 }
 public String getLastName() {
  return lastName;
 }
 public void setLastName(String lastName) {
  this.lastName = lastName;
 }
 public Address getAddress() {
  return address;
 }
 public void setAddress(Address address) {
  this.address = address;
 }
 public String getPhoneNumber() {
  return phoneNumber;
 }
 public void setPhoneNumber(String phoneNumber) {
  this.phoneNumber = phoneNumber;
 }
 public long getPersonId() {
  return personId;
 }
 public void setPersonId(long personId) {
  this.personId = personId;
 }
 
 
 public String toString() {
  StringBuffer buffer = new StringBuffer();
  buffer.append("firstName-->").append(this.getFirstName()).append("\n");
  buffer.append("lastName-->").append(this.getLastName()).append("\n");
  buffer.append("phoneNumber-->").append(this.getPhoneNumber()).append("\n");
  buffer.append("personId-->").append(this.getPersonId()).append("\n");
  buffer.append("address-->").append(this.getAddress().toString()).append("\n");
  return buffer.toString();
 }
}


public class Address {
 private String address;
 private String detailAddress;
 private String zipCode;
 private String countryCode;
 public String getAddress() {
  return address;
 }
 public void setAddress(String address) {
  this.address = address;
 }
 public String getDetailAddress() {
  return detailAddress;
 }
 public void setDetailAddress(String detailAddress) {
  this.detailAddress = detailAddress;
 }
 public String getZipCode() {
  return zipCode;
 }
 public void setZipCode(String zipCode) {
  this.zipCode = zipCode;
 }
 public String getCountryCode() {
  return countryCode;
 }
 public void setCountryCode(String countryCode) {
  this.countryCode = countryCode;
 }
 public String toString() {
  StringBuffer buffer = new StringBuffer();
  buffer.append("  address-->").append(this.getAddress()).append("\n");
  buffer.append("  detailAddress-->").append(this.getDetailAddress()).append("\n");
  buffer.append("  zipCode-->").append(this.getZipCode()).append("\n");
  buffer.append("  countryCode-->").append(this.getCountryCode()).append("\n");
 
  return buffer.toString();
 }
}


import java.util.HashMap;
import java.util.Map;
import org.apache.commons.beanutils.BeanUtils;
public class Test {
 public static void main(String[] args) throws Exception{
  Map map = new HashMap();
  map.put("firstName", new String("블라블라"));
  map.put("lastName", new String("블랍블라블라블라"));
  map.put("phoneNumber", new String("0000-0000-0000"));
  map.put("personId", new Long(10011001));
  map.put("address.address", new String("경기도 블라블라블라블라") );
  map.put("address.detailAddress",new String("불라불라??? ???동 ????호") );
  map.put("address.zipCode",new String("000-000") );
  map.put("address.countryCode",new String("국가코드 000"));
 
  Object oClass = Person.class.newInstance();
  BeanUtils.populate(oClass, map);
  System.out.println(oClass.toString());
 }
}


위의 예제는 스트러츠같은곳에서 form 값을 세팅할때 사용하는것을 한번 작성해봤다.

스트러츠에도 위와같이 폼빈을 세팅해주지 않을까 생각한다.

리플렉션으로 만들어도 되겠지만 이왕 누군가가 만들어둔게 있으면 가져다 쓰는게 시간도 절약되고 주절주절 ㅋㅋㅋ

오늘은 여기까지. ^^
Posted by 1010
반응형

간단한 BeanUtils 사용하기


자료형게시판을 만들다가 불현듯 잔머리가 생각이 났다.


자료형나름대로의 컨트롤로를 두면 어떨까 ? 하는 생각이다.

그래서 요기저기 머리를 굴리다가 MultipartRequest와 BeanUtils 를 이용해서 하기로 마음을 먹었다.


순서는  업로드해서 Bean에 담기까지의 과정이다.

1. 다중 파일을 을 업로드 시킨다.

2. MultipartRequest를 이용해서 받는다.

3. request.setAttribute 를 이용해서 request에 MultipartRequest의 내용을 담다.

4. request.getRequestDispatcher 를 이용해서 bean에 담을 페이지(또는 서블릿)으로 이동시킨다.

5. BeanUtils을 이용해 bean에 모든내용을 담는다.


1~4번까지는 그리 무리없이 진행이 되었지만 역시 5번에 막힌다.

모양이 심플해야 하기에 (본인은 복잡스러운거 싫어함) 그래서 머리를 굴리다가

BeanUtils를 써보면 어떨까 생각이 들었다.



일단 BeanUtils을 사용하기 위해서는 자카르타 프로젝트 홈페이지에서 다운을 받는다.

최신버전은 commons-beanutils-1.7.0 일듯..

그리고 이것을 사용하기 위해서 다른 패키지가 하나더 필요하다

commons-logging-1.0.4 요놈 도 자카르타에서 다운을 받자


위의 두개 패키지를 압축을 풀고 lib폴더에 보면 jar파일 있을거시다. 이것을 내 프로젝트의 lib로 이동시키자

그럼 일단 준비끝이라 할 수 있다.


그럼 소스부분을 보면..

1번 소스는 볼필요가 없기에 패스


2~4번 소스


 String repository  = "D://upload//";  // 저장할곳을 넣는다.

 MultipartRequest multi = new MultipartRequest(request, repository, 1024*1024*1024, "euc-kr", new DefaultFileRenamePolicy());

 java.util.Enumeration params = multi.getParameterNames();
 while( params.hasMoreElements() ) {
  String name = (String)params.nextElement();
  String value = multi.getParameter(name);
  request.setAttribute(name, value); // 요부분은 3번 부분
 }

 java.util.Enumeration files = multi.getFileNames();
 while ( files.hasMoreElements() ) {
  String name = (String)files.nextElement();
  String filename = multi.getFilesystemName(name);
  String type = multi.getContentType(name);
 }
 request.getRequestDispatcher("step2.jsp").forward(request, response); // 요부분이 4번부분


본인은 원래 저기서 파일관련 테이블에 참조키를 이용하여 파일을 관리하기로 했다. 그부분은 각자 알아서 처리하면될듯하다.


5번 소스


 BoardBean board = new BoardBean();
 BeanUtils beanutils = new BeanUtils();
 java.util.Enumeration params = request.getAttributeNames();

 while ( params.hasMoreElements() ) {
  String name = (String)params.nextElement();
  Object value = request.getAttribute(name);
  BeanUtils.setProperty(board , name, value ); // 이부분이 5번부분이라고 할수있다.

}


확인은

out.println("<hr>board.getContent() = " + board.getContent() );
out.println( beanutils.getProperty(board, "content") );

이런식으로 확인을 해보면 알 수 있다.



setProperty 를 보면

setProperty(java.lang.Object bean, java.lang.String name, java.lang.Object value)

이런식으로 bean과 각 bean의 멤버, 그리고 해당 value를 각각 담을 수 있게 해준다.

 

반대로

getProperty는

getProperty(java.lang.Object bean, java.lang.String name)

bean과 멤버를 이용해서 value를 얻을 수있다.


생각보다 모양이 간단하다(?) 서블릿으로 만드는 건 각자가 하면 될 듯하다.

근데 위에 보면

commons-logging-1.0.4  이놈은 사용을 하지 않는 것 처럼 보이지만

commons-logging-1.0.4 이 없으면 BeanUtils 가 에러가 나기에 꼭 lib에 추가하도록 한다.




일반적인 request 받아서 빈에 넣기


 BeanUtils beanutils = new BeanUtils();
 GroupBean group = new GroupBean();

 java.util.Enumeration params = request.getParameterNames();

 while ( params.hasMoreElements() ) {
  String name = (String)params.nextElement();
  String value = CommonUtil.htmlNko2nulltrim(request.getParameter(name), "");
  //out.println(name+" = " + value + "<br>");
  if (!value.equals("")){
   BeanUtils.setProperty(group , name, value );
  }
 }

Posted by 1010
05.JSP2009. 3. 4. 09:33
반응형

JSP 페이지 head에 두줄만 적어주세요.


1. 엑셀파일로 다운받고 싶은 페이지(list.jsp)에 엑셀파일 버튼을 만들고

   excel.jsp(파일명은 각자) 파일을 링크한다.


2. excel.jsp는 엑셀파일로 다운받고 싶은 페이지(list.jsp)와 동일하게 생성한다.


3. excel.jsp 파일에 <head>에 아래 두줄을 작성한다.

<head>
 <title>엑셀 다운로드</title>
<%
 //모든 HTML은 Excel 파일형식으로 변환됨 (편하지 않나요?)
 // 엑셀 파일 다운로드 처리
 response.setHeader("Content-Disposition", "attachment; filename=myexcel.xls");
 response.setHeader("Content-Description", "JSP Generated Data");
%>
</head>


4. list.jsp 페이지에서 excel.jsp로 파라미터 값을 넘겨야 겠죠?
히든값으로 해서 넘겨줘요

list.jsp 파일 form 안에 적어주세요.
<!-- 엑셀 다운로드를 위한 프레임 시작-->
 <iframe name="excel" width="0" height="0" frameborder="0"></iframe>
<!-- 엑셀 다운로드를 위한 프레임 끝-->


5. excel.jsp에서 각자 상황에 맞게 테이블 모양을 변경해주세요

Posted by 1010
반응형
본 자료실은 다운로드만 가능합니다.
요청하실 자료가 있으시면 드라이버 자료요청으로 요청하시면 됩니다.
만약 드라이버 CD가 미배송 되었을 경우 각 칩셋에 맞는 드라이버를 설치해 주시면 됩니다.
혹시 궁금하신 점이나 설치 시의 문의가 있을 때에는 고객지원센터 1588-5915 번으로 문의해 주시기 바랍니다.

※ 다운로드가 안될 경우 : 인터넷 옵션 > 고급탭 > URL을 항상 UTF-8로 보냄을 체크해제해 주세요.
오디오/사운드 메인보드 비디오/그래픽 공유기(유.무)/랜카드
프린터/스캐너/모니터 시디롬/HDD 노트북 기타
유틸리티 백신/보안 윈도우 패치/시스템 최적화
무료소프트웨어 기타
Posted by 1010