하지만, 개인적으로 prototype이나 jQuery 보다는 dojo를 선호하기 때문에(대부분의 경우엔 별 차이가 없지만, 마이너리티 체질의 똥고집이랄까...) dojo로 Lightbox를 만들기로 했는데... 웬걸 -.-; dojox.image.Lightbox라는 녀석이 이미 있더라. 그냥 쓸까 하다가... 이 녀석이 dijit(dojo의 위젯 시스템; jQuery UI 쯤에 해당하는 모듈이다)에 의존하는 관계로 버림받고 있길래 간단히 하나 맹그러봤다.
솔직히... 만만하게 보고 시작했는데... 생각보다는 많이 복잡하더라. -,.-;;;
이름은 Lightbox와 비슷하면서도 dojo로 만들었다는 느낌이 들도록 Delightbox라고 지었다 ^^;
덧. 1.2 릴리즈 소식을 듣고 다운받으러 갔다가 무심결에 맨 위에 있는 링크를 눌렀더니... dojo.js -.-;;;;
prototype과 jquery 덕분에(?) 크기에 대한 압박이 심했나 보다. 아무튼 prototype.js나 jquery.js 대용이라면 dojo.js 파일 하나면 충분하고... dijit을 포함한 dojo 모든 것을 맛보려면 역시 풀버전~ ^^; 그 전에 눈요기가 필요하다면 Dojo Feature Explorer~ =3=333
dojo.declare(className, superClass, { members... }) : 주어진 이름을 가진 클래스를 정의한다. 클래스 이름은 문자열로, 수퍼클래스는 클래스 객체로 지정해야 한다. 수퍼클래스가 없으면 null 또는 [ ] (빈 배열)을 지정하면 된다. 하나 이상의 수퍼클래스(정확히는 하나의 수퍼클래스와 여러 개의 믹스인을 배열로 지정할 수도 있다.
위의 예는 Point라는 이름의 클래스를 정의한다. 이 클래스는 수퍼클래스가 없으며, 하나의 생성자(constructor)와 x, y 두 개의 속성(멤버 변수)을 가지며, moveTo, equals 세 개의 메소드(멤버함수)를 가진다. 키워드는 declare(선언)이지만, 실제로 하는 일은 선언이 아니라 정의(define)다. 오히려 dojo.provide가 선언에 가깝다. 이 글의 주제에서 벗어나는 얘기니 궁금하면 구글신께 물어보시라... 나중에 시간나면 따로 쓰기로 하자~
이 클래스를 사용하는 예를 보면:
var p1 = new Point(1, 2); var p2 = new Point(); p2.moveTo(3, 4); alert(p1.equals(p2)); // false
위의 예는 Rect라는 이름의 클래스를 정의한다. 이 클래스는 Point를 상속받으며(x, y 두 개의 속성과 moveTo, equals 두 개의 메소드), 추가로 w, h 두 개의 속성과 getRight, getBottom, resize 세개의 메소드를 정의하며, equals를 오버라이딩(재정의)하고 있다. equals에서 this.inherited(arguments)라는 생소한 구문이 보이는데 부모클래스에서 정의한 메소드를 호출하는 것이다. 자바로치면 super(rect)와 비슷하다고 보면 되겠다. 생성자에서 super를 호출하지 않는다는 점도 주의해야한다. 아무말 안해도 무조건 super를 호출한다. 자바스크립트는 함수 오버로딩(중복정의)을 지원하지 않기 때문에 생성자를 하나밖에 가질 수 없다.
객체 관련 유틸리티 함수들이 여러가지 있지만, 개인적으로 한 번이라도 써 본적이 있는 녀석들만 간단히 알아보면:
dojo.delegate(obj, { members...}) : 주어진 객체를 상속받은 객체를 만든다. dojo.declare와 달리, 클래스가 아니라 인스턴스 객체를 만든다. 간단하게 몇 가지 속성이나 메소드만 재정의한 객체를 만들때 편리하다.
dojo.clone(obj) : 주어진 객체의 deep-copy 객체를 만든다.
dojo는 하나 이상의 클래스를 하나의 .js 파일에 넣어두고, 이를 동적으로 로딩하는 기능을 제공하는데, dijit 설명할 때 자세히 알아보기로 하고... 여기서는 dojo 관련 소스들을 보다가 자주 보게될 함수 몇가지만 알아보자:
dojo.require(className) : 현재 모듈(나중에 설명한다)에서 주어진 이름의 클래스를 참조(사용)한다. 자바의 import와 비슷하지만, 자바와 달리 와일드카드(*)를 사용할 수 없다(dojo 0.4.x 쓰시던 분들도 주의!). 일반적으로 dojo.require('dijit.form.TextBox')라고 하면 dojo디렉토리/dijit/form/TextBox.js를 로딩한다고 이해해도 무방하다.
dojo.provide(className) : 주어진 이름의 클래스를 선언한다. 즉, 현재 모듈(나중에 설명한다)에서 주어진 이름의 클래스를 정의할 것임을 미리 알린다. 실제로는 빈 객체를 정의하기 때문에 네임스페이스 선언을 위해서 사용한다.
위의 두 함수는 dojo custom build에서도 매우 중요한 역할은 한다.
오늘은 여기까지~
다음에는 아기다리고기다리던 dijit(dojo widget)이다.
덧. 자바스크립트로 진지한 코딩을 해 본 개발자라면 IE의 trailing comma error의 공포를 알고 있을 것이다. dojo.delcare도 조심해서 쓰지 않으면 좌절을 맛보게 된다. 조만간 동의하실 것이라개인적으로 즐겨쓰는 방법은:
자바스크립트로 코딩 좀 했다는 분들도 IE(참고자료)와 불여우, 그리고 W3C의 표준 DOM 이벤트 모델간의 차이에 대해서 제대로 이해하기보다는 까이꺼~ 대충~ 통밥으로~ 처리하는 경우를 많이 본다. 누가 좋으냐 나쁘냐는 논쟁은 끝이 없으니... 넘어가기로 하고... 골치아픈 브라우져간의 차이점을 신경쓰지 않고 개발할 수 있는 dojo의 이벤트 시스템을 알아보자.
dojo.connect(이벤트객체, 이벤트이름, 이벤트핸들러함수) : 지정한 객체에서 지정한 이름의 이벤트가 발생하면 이벤트 핸들러 함수를 호출하도록 "연결"한다. 핸들을 리턴한다.
dojo.disconnect(핸들) : dojo.connect()함수의 리턴값으로 받은 핸들을 사용하여 이벤트와 이벤트 핸들러 함수의 연결을 끊는다.
어랏?! 더 단순하자나! 그러나 이 코드가 항상 동작하는 것이 아니다라는 점이 문제다. 기존의 방식에 익숙하다면 지금 당장 바꿀 필요는 없다. dojo.connect가 있다는 점만 기억해두면 머지않아 필요할때가 올 것이다. 하나 이상의 이벤트 핸들러를 연결해야할 때, 이벤트 핸들러에서 이벤트의 전달을 중단해야 할 때, 또는 기본 동작(폼 submit같은)을 막고 싶을 때... 등등... dijit(dojo의 위젯 시스템)을 쓴다면 별도로 disconnect를 해줄 필요가 없지만, 그 외의 경우에는 꼼꼼하게 disconnect해주는 것이 좋다. DOM 노드를 제거할때 dojo._destroyElement()를 사용하면 자동으로 연관된 이벤트 핸들러들을 disconnect 해주므로 편리하게 쓸 수 있다.
script태그가 body태그 끄트머리에 들어가있는 것이 이상하다고 생각한 분 계신가? 그렇다. 일부러 그렇게 한거다. 저렇게 안하면 브라우져와 주변 상황에 따라(!) 예상치 못한 결과를 만나게 된다. 그래서 dojo에서 이런 방법을 제공한다:
dojo.addOnLoad(핸들러함수) : 페이지를 열고 난 직후에 호출될 핸들러 함수를 추가한다.
dojo.addOnUnload(핸들러함수) : 페이지를 닫기 직전에 호출될 핸들러 함수를 추가한다.
예제가 좀 길어졌지만, 기존에 자바스크립질에 익숙한 분들은 dojo.addOnLoad/Unload가 body의 onload/onunload핸들러와 크게 다르지 않다는 것을 알아챘을 것이다. 즉, <body onload="init()" onunload="destroy()"> 이라고 하면 된다는 얘기~ 그런데 왜 저렇게 복잡하게 하느냐고? 예전에도 한번 포스팅한 적이 있는데... onload의 타이밍이 미묘해서 브라우져마다 조금씩 다르다. 좀 다른 얘기지만, 위의 예제 코드에서 init()/destroy()에서 사용된 배열에 이벤트 핸들들을 보관해두고 일괄 해제하는 패턴은 자주 사용되므로 익해두면 편하다.
자바스크립트에 익숙한 분들은 return false; 이런 짓(!)을 하시는데... 이거 생각처럼 잘 안된다. 이 기회에 링크한 문서들을 참고해서 왜 되고 또 왜 안되는지 좀 더 파 보시는 것도 괜찮을 듯...
다소 뜬금없지만, 이벤트 시스템의 일부이기도 하고, 유용한 녀석들이니 언급은 하고 넘어가야 할 것 같다:
dojo.subscribe(토픽이름, 토픽핸들러함수) : 지정한 토픽이 발생하면 호출할 핸들러함수를 추가한다.
dojo.publish(토픽이름, 파라메터배열) : 지정한 토픽을 발생시킨다. 그러면, 위의 dojo.subscribe()를 통해 등록한 핸들러 함수들이 모두 호출된다.
AJAX의 특성상 비동기 동작이 많고, 이를 처리하기위해서 코드가 꼬이고, 이렇게 꼬인 코드를 풀기 위해 전역변수를
많이 쓰게된다. dojo.publish/subscribe()를 사용하면 이러한 상황을 많이 줄일 수 있다. C언어를 오랜동안
쓰신 분이라면 setjmp/longjmp와 비슷하다고 생각할 수 있겠다. 제대로된 예제를 만들려면 꽤 긴 코드가 필요하니, 나중에 따로 설명하기로 하자.
500ms(0.5초) 동안 domNode의 너비를 10px에서 500px로, 투명에서 불투명으로, 배경색을 빨강에서 초록으로, 글꼴 크기를 10pt에서 20pt로... 부드럽게~ 바꾼다. 즉, 위의 dojo.fadeIn/fadeOut()은 dojo.animateProperty()로 쉽게 구현할 수 있다.
var flyWelcome = function() { var bodyBox = dojo.contentBox(dojo.body()); var welcomeBox = dojo.contentBox('welcome'); var goArgs = { node: 'welcome', properties: { left: bodyBox.w - welcomeBox.w, top: bodyBox.h - welcomeBox.h }, duration: dojo.byId('flyTime').value * 1000, // 밀리초 }; var goAnim = dojo.animateProperty(goArgs); goAnim.play(); };
var hideWelcome = function() { dojo.fadeOut({ node: 'welcome', duration: 1000, delay: 100, onEnd: function() { dojo.style('welcome', 'display', 'none'); } }).play(); // 애니메이션은 비동기로 일어난다. // 여기에 dojo.style('welcome', 'display', 'none');를 넣으면 // 애니메이션을 시작하자마자 바로 사라져버리므로(display:none), // 스르륵 사라지는(투명도를 조절하는) 효과를 볼 수 없게된다. };
<div id="welcome"> <h1>Welcome!</h1> <p>Blah Blah...</p> <p> <button onclick="flyWelcome()">Fly me to the moon</button> during <input type="text" id="flyTime" value="1" size="5" /> seconds. </p> <button onclick="hideWelcome()">Let it be</button> </div>
</body> </html>
페이지가 로딩되면 잠시 뒤에 Welcome 창(?)이 스르륵~ 나타난다. 여기서 Let it be를 누르면 창이 스르륵~ 사라지고, Fly me to the moon을 누르면 지정한 시간동안 창이 아래쪽 구석으로 날아간다. 예제 자체는 별거 없지만, 중간에 코멘트 중에 중요한 내용이 있다. 애니메이션은 항상 비동기로 진행된다는 사실을 명심하자. 동기를 맞추려면 그래서 onBegin, onEnd 등의 이벤트를 이용해야 한다.
고급 효과(dojo.fx 패키지) dojo base에 효과를 자꾸 추가하면 dojo.js 파일의 크기가 너무 커지므로, dojo.fx 라는 별도의 패키지를 만들었다. 따라서 아래의 효과들을 사용하기 전에 dojo.require('dojo.fx');를 실행해야 한다.
dojo.fx.chain([애니메이션객체들...]) : 여러 개의 효과을 순서대로 실행하는 복합 효과.
dojo.fx.combine([애니메이션객체들...]) : 여러 개의 애니메이션을 동시에 실행하는 복합 효과.
dojo.fx.slideTo({ node: 노드, top: 세로좌표, left: 가로좌표 }) : 노드를 지정한 좌표로 이동하는 효과.
dojo.fx.wipeIn({ node: 노드 }) : 노드의 높이를 점점 크게하면서 나타나는 효과.
dojo.fx.wipeOut({ node: 노드 }) : 노드의 높이를 점점 줄이면서 사라지는 효과.
dojo.fx.Toggler(args) : 지정한 효과를 사용해서 노드를 show/hide() 하는 도우미 클래스. 동일한 효과로 show/hide()를 반복할 경우 편리하게 쓸 수 있다.
백문이불일견, 백견이불여일런! 말로 설명하려고 해도 말재주가 부족하니... 소스코드로 때우기로 하자.
페이지가 로딩된 다음, 작은 이미지를 클릭하면 뭔가 어수선한 애니메이션이 나오고 창(레이어)에 큰 이미지가 표시된다. 큰 이미지를 클릭하면 역시 어수선한 에니메이션이 나오고 창(레이어)이 닫힌다. 쉽게 얘기해서 어수선한 LightBox 다. 나도 안다... 하나도 안 비슷하다는 거... 그래도 조금만 손을 보면... -.-;;;; 중간에 보면 dojo.fx.combine()과 dojo.fx.chain()을 쓴 부분이 있는데... 위의 코드를: dojo.fx.chain([f1, dojo.fx.combine([f2, f3]), f4]); 처럼 바꾸면 f1이 먼저 진행되고, f2와 f3는 동시에 진행되고, 그 다음에 f4가 진행된다. 예를 들려다보니 저렇게 됐지만, 잘 엮으면 멋진 효과를 쉽게~ 만들 수 있다. 정말 잘~ 엮어야 된다. 뭔 소린지는 엮어 보면 안다. -.-;;; 그리고 dojo.addOnLoad()를 사용해서 페이지가 로딩된 직후 overlay와 lightbox노드를 숨기고(display: none)있는데, 이는 보이지 않는 노드에 대해서 높이 계산을 안하는 불여우의 버그(또는 기능)를 피하기 위한 꼼수다.
혹시 prototype을 사용하고 있다면 script.aculo.us를 사용해서 비슷한 효과(더욱 현란한 효과)를 낼 수 있다. prototype과 scrit.aculo.us에 관심이 있는 분들은 인사이트에서 번역 출판한 프로토타입과 스크립타큘러스를 참조하시라(절때! 내가 추천글을 썼다거나 교정을 봤다거나 해서 이러는 건 아니고...-.-;;;; 프로토타입 관련 책 중에서는 원서도 제일 좋고, 번역도 제일 좋다).
DOM 노드 위치/좌표 관련 함수들 CSS만으로 모든 레이아웃을 정확하게 계산하기란 쉽지 않다. 디자이너의 현란한 디자인이 IE의 더블마진 버그와 만나는 순간... 때문에 레이아웃 잡는게 여간 골치아픈게 아니다. 그래서 이런게 필요하다:
dojo.marginBox(domNode) : 지정한 DOM 노드의 margin box를 얻는다.
dojo.marginBox(domNode, {l:가로좌표, t:세로좌표, w:너비, h:높이}) : 지정한 DOM 노드의 위치/크기를 margin box로 설정한다. 위치는 부모 노드의 위치에 대한 상대좌표다(즉, CSS의 position:absolute 다). margin/padding/border는 바뀌지 않지만, content box의 크기는 바뀐다. 기본 단위는 픽셀(px)이다. 각 속성은 생략할 수 있다.
dojo.contentBox(domNode) : 지정한 DOM 노드의 content box를 얻는다.
dojo.contentBox(domNode, {l:가로좌표, t:세로좌표, w:너비, h:높이}) : 지정한 DOM 노드의 content box의 위치/크기를 설정한다. 위치는 부모 노드의 위치에 대한 상대좌표다(즉, CSS의 position:absolute 다) margin/padding/border는 바뀌지 않지만, margin box의 크기는 바뀐다. 기본 단위는 픽셀(px)이다. 각 속성은 생략할 수 있다.
위의 그림은 CSS의 박스모델을 설명하는 것인데, dojo의 contentBox는 위의 그림에서 맨 안쪽의 content를 둘러싼 사각형을, marginBox는 맨 바깥쪽의 margin까지 포함한 사각형을 의미한다. 이 결과는 IE이건 아니건, Quirks Mode이건 아니건 동일하다. 웬만하면 CSS로 처리하는 것이 바람직하지만, iframe이나 textarea같은 꼴통들에겐 dojo.contentBox/marginBox()가 도움이된다. 간단한 예를 들어보면(저번에 예제를 하나도 안만들어서 이번엔 좀 길게 만들어 봤다):
이 예제의 목적은 스크롤바 없이 화면에 꽉 채운 페이지를 만드는 것이다. IE에선 부모 노드의 크기가 %로 지정되면 right/bottom 속성이 동작하지 않기때문에 곤란하다. IE의 CSS expression을 써도 되겠지만, 개인적으로는 이 방법을 선호한다. CSS만으로 어떻게 할 수 있을 것 같긴한데... 내공 부족 -.-;;;(표준 CSS만으로 할 수 있는 좋은 방법이 있으면 알려주시면 콩다방 커피 한잔 대접합니다~). 예제를 위한 예제이다 보니 아직 설명한 것들이 잔뜩 있는데 핵심은 중간 쯤에 있는 자바스크립트 코드다. 중간에 있는 if (dojo.isIE) { ... } 스크립트(주황색 배경)를 지우고 IE에서 실행시켜보시라. 나중에 dijit layout widget을 사용하면 이것보다 훨씬 복잡한 작업을 한 방에 해치울 수 있다!
DOM 편의 함수 앞에서 알아본 dojo.attr()/dojo.style()dojo.*Class() 외에도 잡다한 편의함수들이 많다. 이 함수들의 완전한 목록은 dojo API Reference에서 찾아보시고~ 여기서는 맛보기로 생각나는 것 몇 개만 알아보자:
dojo.place(domNode, refDomNode, position) : domNode를 refDomNode를 기준으로 위치/순서를 변경한다. position은 인덱스(숫자) 또는 before/after/first/last 중의 하나다. position 인자에 따라 appendChild/insertBefore() 등의 DOM API를 적절히 호출한다. 처음엔 복잡해 보이겠지만, 익숙해지고 나면 DOM API가 얼마나 구린지(?), 그리고 dojo.place()가 얼마나 자연스러운지 알게 될 것이다.
dojo.isDescendant(domNode, ancestorDomNode) : domNode가 ancestorDomNode의 하위 노드인지 확인한다.
dojo._destroyElement(domNode) : 지정한 노드를 해제한다. IE에서는 이벤트 핸들러가 걸린 노드와 관련한 메모리가 새는 버그가 있어서 domNode.parentNode.removeChild(domNode) 또는 domNode.parentNode.innerHTML = '' 또는 domNode.ownerDocument.detachNode(domNode) 로는 충분하지 않다. domNode.parentNode.innerHTML'_'로 시작하는 private함수지만 public으로 해도 무방할 정도로 유용하다.
dojo.setSelectable(domNode, selectable) : domNode와 모든 자식노드들을 선택불가능하게(마우스로 긁을 수 없게) 만든다. 이걸 활용하면 블로그 본문 긁지 못하게 하는 것을 쉽게 할 수 있겠지만... 이런 짓 하지말자. 어차피 긁을려면 다 긁는다. 다만, 메뉴나 툴바 같은 녀석들을 이 함수를 사용해 긁지 못하게 하면 잡다한 오동작을 원천봉쇄할 수 있다.
dojo._getOpacity(domNode) : domNode의 투명도를 얻어온다. 0이면 완전 투명, 1이면 완전 불투명이다. 이 함수 없이 할려면 IE이냐 아니냐에 따라서 삽질을 좀 해야 한다.
(중간에 dojo API Reference가 있다고 얘기했다. 아는 사람은 다 알겠지만... 이걸 지금까지 얘기안한 이유가 있다. 이거 보고 괜히 dojo를 오해하지 말기 바란다. 이렇게 느려터지게 만드는 것도 쉬운 일이 아니다. 굳이 API가 필요하다면 이것 보다는 dojo toolbox를 쓰길 권한다.)
유틸리티 함수는 일단 마무리~ 다음에는 재미있는(?) 애니메이션/효과 관련 함수들을 간단히 알아보고, 이어서 이벤트 시스템, 클래스 시스템, 그리고 dijit... 갈 길이 멀구나...
dojo.byId(id) document.getElementById(id)와 동일하며 프로토타입의 $(id)와 동일하다. 지정한 id를 가진 DOM 노드를 찾는다. 참고로 dijit.byId(id)와는 전혀 다른 녀석이니 헷갈리지 않기를... 그리고 다른 window(frame/iframe)의 노드를 찾고 싶을 때는 dojo.withGlobal(window,'byId',dojo,[id]) 처럼 하면 된다(뭐가 이렇게 복잡하냐고? 그래도 없는 것 보다는 낫다). $()보다 길어서 타이핑하기 귀찮겠지만... 덕분에 프로토타입이나 jQuery와 섞어서 사용해도 아무런 문제를 읽으키지 않는다. 굳이 $()를 쓰고 싶으면 예전에 쓴 블로그를 참조하시길...
dojo.attr() - DOM 속성 관련 함수들 dojo.attr()은 getter/setter 두가지 역할을 겸하고 있으며, domNode 대신 해당 노드의 id를 문자열로 지정해도 된다.
dojo.attr(domNode, attrName) : 지정한 DOM 노드의 지정한 속성 값을 가져온다. domNode.getAttribute(attrName)와 비슷하다.
dojo.attr(domNode, attrName, attrValue) : 지정한 DOM 노드의 지정한 속성 값을 변경한다. domNode.setAttribute(attrName, attrValue)와 비슷하다. 참고로, dojo 1.1.1 이후 버전에서는 attrName/Value 쌍을 배열로 지정할 수 있다. 예를 들면: dojo.attr(domNode, { action: 'add.php', method: 'POST' });
dojo.removeAttr(domNode, attrName) : 지정한 DOM 노드의 지정한 속성을 삭제한다. domNode.removeAttribute(attrName)와 비슷하다.
domNode.setAttribute/getAttribute/removeAttribute를 사용하면되는데 왜 이런게 필요하냐고 물으신다면... 자바스크립트 닭질을 좀 더 하시길 권해드리는 바이다. 일단 dojo에서 이런 걸 제공한다는 사실만 기억해두면 조만간 고맙게 쓰게 될 날이 온다.
dojo.style() - DOM CSS 속성 관련 함수들 dojo.style()도 getter/setter 두가지 역할을 겸하고 있으며, domNode 대신 해당 노드의 id를 문자열로 지정해도 된다.
dojo.style(domNode, cssAttrName) : 지정한 DOM 노드의 스타일 중에서 지정한 CSS속성 값을 가져온다. 주의 할 점은 cssAttrName은 CSS 형식(font-size, left-padding ...)이 아닌 자바스크립트 형식(fontSize, leftPadding ...)으로 지정해야 한다는 것이다. 참고로, dojo 1.1.1 이후 버전에서는 CSS 형식과 자바스크립트 형식 둘 다 사용할 수 있으며,
cssAttrName/Value 쌍을 배열로 지정할 수 있다. 예를 들면: dojo.style(domNode, {
fontSize: '20pt', 'text-decoration': 'underline' });
dojo.style(domNode, cssAttrName, cssAttrValue) : 지정한 DOM 노드의 스타일 중에서 지정한 CSS속성 값을 변경한다.
dojo.style()함수는 계산된 스타일(computed style)을 대상으로 한다. 즉, 캐스캐이드된 여러가지 CSS속성들이 모두 적용된 상태를 기준으로 값을 얻어오거나 변경한다.
dojo.*Class() - DOM CSS 클래스 관련 함수들 별거 아닌거 같지만 짱 편한 함수 몇 개 소개한다. 이런 짓 해보신 적 없으신가?
뭐가 달라졌을까... 1. 맨 아랫줄을 보면 document.getElementById() 대신 dojo.byId()가 사용된 것을 볼 수 있는데, 이건 프로토타입의 $() 와 비슷하다. 그냥 타이핑을 줄여주는 것 뿐이다. 2. 고전적인 for 루프 대신 dojo.forEach() 가 사용되었다. for (var item in items) { } 도 있으므로 별 필요가 없다고 생각할 수 있지만, 자바스크립트의 for...in...은 유명한 evil 중의 하나다. 믿어라. 크록포드옹께서 그리 말씀하셨다. 다음에 보게 되겠지만 dojo.forEach()는 dojo의 배열 지원 함수 중의 하나에 불과하다. 모두 합체하면 게키강가3가 나온다. 믿거나 말거나... -.-;;; 루리루리짱께서 그리 말씀하셨다.
아무튼 다시 원래 길로 돌아와서... 별로 편한거 같지 않다는 분들이 계시다. 한가지만 더 해보자.
결정적인 차이는(?!) prototype과 달리 dojo는 'on'을 항상 붙여야 한다(예: onclick, onchange)는 것이다. dojo의 위젯(dijit)들은 이벤트 이름을 대소문자를 섞어서 사용해서(예: onClick, onChange) 표준 이벤트들과 구별한다.
사실 dojo.connect()는 이벤트 핸들러 연결보다 더 많은 일을 한다. 실제 구현은 AOP의 before advice에 가깝다고 볼 수 있다. 즉, 일반적인 함수에 대해서도 적용 가능하다.
BOM(Browser Object Model) 코드는... 열라 길다. -.-; 생략할려다가... prototype이나 dojo가 얼마나 많은 노가다를 줄여주는지 확인하는 차원에서 주욱 적어봤다. prototype과 dojo 코드는 얼추 비슷해 보이지만, 가만히 살펴보면 자질구레한 부분에서 조금씩 다르다:
* BOM
var xhr; try { xhr = new XMLHttpRequest(); } catch (e) { try { xhr = new ActiveXObject("Msxml2.XMLHTTP"); } catch (e) { try { xhr = new ActiveXObject("Microsoft.XMLHTTP"); } catch (e) { // ... Orz } } } xhr.onstatechange = function() { select (xhr.readyState) { case 4: // loaded select (xhr.statusCode) { case 200: // ok break; case ... } case ... } } xhr.open('GET','your/url', true); xhr.send('paramName1=' + encodeURIComponent(paramValue1) + '¶mName2=' + encodeURIComponent(paramValue)); ...
요청 메소드를 파라메터로 처리하지 않고, 요청 메소드별로 함수가 따로 있다(코멘트 부분 참조).
요청 url을 별도의 파라메터가 아니라 다른 옵션들과 똑같이 취급한다.
동기/비동기 모드를 지정하는 파라메터가 거꾸로다(당연히 이름도 asynchronous가 아니고 sync다).
응답 형식을 지정할 수 있으며, 응답이 바로 콜백함수로 전달된다.(즉, json 응답의 처리 방법이 다르다.)
prototype처럼 성공/실패 여부에 따라 콜백 함수를 지정할 수도 있지만, 내 경우에는 dojo.Deferred(twisted의 deferred와 유사한데...)를 선호한다. 여기서는 많은 옵션들을 생략했는데, 자세한 것은 각각의 온라인 문서를 찾아보시라.
Ajax.Updater / Ajax.PeriodicalUpdater에 해당하는 녀석은 없다. 그냥 window.setInterval()과 domNode.innerHTML을 쓰면 되니까 그다지 아쉽지 않다. 그리고, dojo의 위젯(dijit) 중에 ContentPane이라는 녀석을 쓰면 이 모든걸 한 줄로 끝낼 수 있다.
dojo에는 XHMLHttpRequest 대신 <script>태그와 <iframe>태그를 사용하는 dojo.io.script와 dojo.io.iframe이라는 두개의 특별한 녀석들이 있다. AJAX의 Cross Domain 제한을 경험해본 개발자라면 귀가 솔깃할 만한 녀석들이다. 자세한 것은 역시 온라인 문서를 찾아보시라.
유틸리티 함수들을 살펴보자. dojo에는 아쉽게도(?) prototype사용자들이 제일 좋아하는 함수 $()가 없다.
대신 dojo.byId()가 있다. 이게 귀찮다고 생각하시는 분이 계시겠지만 dojo 개발팀에서도 꽤 오랜 고민과 토론 끝에
내린 결론이다. 관심있는 분은 메일링을 뒤져보시라.
* 지정한 id를 가진 DOM node를 검색
* dom
var node = document.getElementById('abc');
* prototype
var node = $('abc');
* dojo
var node = dojo.byId('abc');
너무 길다고? 익숙해지면 별 차이없다. document.getElementById()보다는 짧지않은가! 거기다 나는 $ 기호에 알르레기반응이 있다. per~~~~~l 씨러=3=3=3=33
* 지정한 노드 이름(태그 이름) / CSS 셀렉터를 가진 DOM node들(배열)을 검색
* dom
var nodes = document.getElementsByTagName('div');
* prototype
var nodes = $$('div');
var nodes = $$('.item');
var nodes = $$('#title);
var nodes = $$('div.item');
...
* dojo
var nodes = dojo.query('div');
var nodes = dojo.query('.item');
var nodes = dojo.query('#title');
var nodes = dojo.query('div.item');
...
dojo.query()도 prototype이나 jquery와 비슷한 CSS 2.1 셀렉터를 지원한다. 더 자세한 내용은 온라인 문서를 찾아보시라.
나중에 얘기하겠지만 prototype에서 $$와 each()의 결합을 많이 쓰는 것처럼, dojo.query()도forEach()와 함께 쓰면 편리하다.
제목은 거창하게 "prototype 사용자를 위한 dojo 입문"이지만... 글쎄... 그냥... dojo 0.4 이전 버전의 dojo에서 실망하거나 좌절하고, 두번 다시는 dojo를 거들떠 보지 않는 분들에게... dojo도 꽤 쓸만하다는(쓸만해졌다는?) 얘기를 하고 싶었다.
제대로 될지는 모르겠지만, 일단 시작해보자!
제 1 장 getting dojo
prototype은 홈페이지에서 prototype.js를 받아서 로컬 웹서버에 올려놓고 참조했다. dojo도 마찬가지다.
주의: 할 점은 dojo가 두 번 들어간다는 것이다. 내 경우엔 js/dojo에 dojo를 풀었더니 무려 js/dojo/dojo/dojo.js가 되었다. -.-;;;
그러나 120k짜리 js파일(1.6은 120K)만 받으면 되는 prototype에 익숙한 개발자들에게 3M를 훌쩍 넘는 dojo의
zip파일(1.0은 3.9M)은 압박(!) 그 자체이다. 내가 알고 있는 많은 개발자들이 여기에서 좌절하고...
prototype으로 돌아간다.
그런 분들을 위해 미리 말해두면 prototype 대체용의 dojo라면 잡다한 다른 파일 필요없이 아래의 파일 중에 하나만 받아서 쓰면 된다:
TurboAjax는 dojo초창기부터 잘 다듬어서 상용으로 팔아먹던 회사였는데... 버틴 보람이 있군.
대충 훑어보면:
General Features
Browser support matching Dojo 1.0: IE6/7, Firefox 2/3, and Safari 3
on Windows, Safari 3 and Firefox 2/3 on Mac, and Firefox 2/3 on Linux.
Opera support will follow.
Subgrids, nested grids, and tree grids
In-place editing
Context menus and tooltips specific to a particular row or column
Data sorting through sort functions or custom sort filter functions
Cell formatters for separation of data from layout
Documentation and unit tests
Dijit Features
Standard Dijit with markup and declarative instantiation
Data binding between data columns and grid columns, and dojo.data provider support
il8n and a11y
Grid events for easy manipulation and event handling for event-driven application development
Keyboard support, including cell selection
Theme infrastructure with structured CSS in dijit.css and tundra.css
Rows, Columns, formatting, and more
Virtual scrolling: 100,000+ rows without performance lags
User sizable columns
Complex rows of various widths and heights, just like HTML tables
Cells, rows, and columns may be patterned based on pattern or state
Fixed position columns
Column dimensions specified in a variety of CSS units, not just pixels
dojo.connect calls to selectively show/hide portions of rows
Optional auto-sizing to fill content or a container node
Auto-adjust dimensions in response to user-controlled font-size adjustments
Variable row height
Selection by row and multirows
Column layout may be changed on the fly by users and code