자바스크립트로 모듈화된 상호 대화식 사용자 인터페이스 만들기 :: 에이젝스 일반[SSISO Community]
 
SSISO 카페 SSISO Source SSISO 구직 SSISO 쇼핑몰 SSISO 맛집
추천검색어 : JUnit   Log4j   ajax   spring   struts   struts-config.xml   Synchronized   책정보   Ajax 마스터하기   우측부분

에이젝스 일반
[1]
등록일:2008-11-23 22:59:49 (0%)
작성자:
제목:자바스크립트로 모듈화된 상호 대화식 사용자 인터페이스 만들기

난이도 : 중급

Greg Travis, Software Engineer, Google

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

2008 년 11 월 11 일

끌어다 놓기 기능을 사용해 웹 페이지 섹션을 이동하는 기능을 살펴봅시다. 여러 상호 대화식 엘리먼트를 독립적으로 구현한 다음 하나로 합쳐 웹 사용자를 무척 행복하게 만들 유연한 개인화 기능을 제공합시다.

자바스크립트는 웹 기반 응용을 만들기 위한 강력한 언어다. 자바스크립트는 안정적이면서도 성숙한 기술이기에, 높은 안정성과 풍부한 기능 측면에서 라이벌인 전통적인 데스크톱 응용과 맞붙을 만한 응용 프로그램을 작성하기에 충분하다. 자바스크립트는 조금은 정적인 웹 페이지에 상호대화성을 추가하는 언어로 시작했으며, 여전히 이런 목적으로 사용된다.

이 기사에서 보여주고자 하는 기법에는 자바스크립트 코드와 연동하기 위해 페이지를 적절히 구조화하는 어려운 점이 하나 있다. 흔히 자바스크립트 코드를 염두에 두고 구조적으로 페이지를 설계해야 한다. 하지만 종종 설계가 끝난 다음에 새로운 상호대화식 기능을 기존 페이지에 추가하고 싶을 수도 있다. 이렇게 하려면 상당히 미묘한 문제를 풀어야 한다. 자바스크립트 코드는 문서 구조를 거슬러 올라가 적절한 장소에 코드를 추가하고 일반적으로 현존하는 구조와 이미 페이지에 존재하는 자바스크립트 코드와도 조화를 이뤄야 하기 때문이다. 요약하자면, 시스템에 입히는 피해를 최소로 줄이기를 원한다.

자주 사용하는 약어
  • UI: User interface
  • GUI: Graphical user interface
  • HTML: Hypertext Markup Language
  • XML: Extensible Markup Language
  • DOM: Document Object Model

자리바꿈 가능한 시스템

이 기사는 웹 페이지 섹션을 다른 곳으로 이동하도록 페이지를 활성화하는 방법을 설명한다. 구체적으로 웹 페이지 섹션 하나를 끌어서 다른 섹션으로 놓는 방법으로 두 섹션 사이를 자리바꿈할 수 있다.

이런 섹션을 활성화하기 위해, class 매개변수를 추가하고 자바스크립트 파일만 올리면 끝난다. <body> 태그에 onload 메서드를 추가해 코드를 활성화한다. 이 태그는 페이지를 읽어들이자마자 바로 코드를 수행한다. 코드가 나머지 작업을 알아서 처리한다.

참고: 이 기사를 위한 예제 코드는 다운로드 절에서 내려받기 바란다.

또한 추상화 수준을 최대한 높이는 방법으로 코드 구조를 잡는다. 프로그램에 속한 다양한 구성 요소가 종종 불필요하게 얽히며, 특히 UI 코드에서는 항상 그렇다. 자리바꿈 가능한 시스템은 여러 조각으로 만들어지며, 각 조각은 상호대화식 기능을 다양하게 구현한다. 각 조각을 통합하고 나면, 함께 동작해서 단순하면서도 빈틈없는 인터페이스를 제공한다. 이는 쉬운 UI 튜닝과 쉬운 사용자 경험에 중요한 특징이다.




위로


자리바꿈 가능한 인터페이스

자리바꿈 가능한 시스템은 사용하기가 쉽다. 웹 페이지 디자이너는 특정 섹션을 자리바꿈 가능하다고 표시한다. 그러고 나서 자리바꿈 가능한 엘리먼트 중 하나를 선택하고 끌어서 다른 자리바꿈 가능한 엘리먼트에 놓는다. 마우스 버튼을 떼자마자 두 엘리먼트는 자리를 바꾼다.

어떤 일이 일어나는지 확실하게 보여주기 위해 표준 GUI 큐를 쌍으로 사용한다.

끄는 구성 요소를 강조하기

자리바꿈 가능한 엘리먼트를 클릭할 때, 커서 아래에 반투명 사각형이 나타난다. coveringDiv() 함수가 만들어내는 이 사각형 영역은 엘리먼트를 정확하게 덮는다. 실제로 다른 엘리먼트로 끄는 대상은 이 사각형 영역이다. 끄는 도중에는 반투명 사각형만 움직이며, 마우스 버튼을 뗄 때까지 원본 엘리먼트는 제자리에 머문다.

놓는 목표를 강조하기

놓는 위치를 명확하게 파악하기 위해 또 다른 중요한 큐가 필요하다. 반투명 사각형을 움직일 때, 자리바꿈 가능한 다양한 엘리먼트 위를 커서가 지나다닌다. 커서가 자리바꿈 가능한 엘리먼트 위를 지나갈 때, 해당 엘리먼트는 또 다른 반투명 사각형으로 강조된다. 이렇게 강조하는 방법으로 끌어서 놓는 최종 목표를 명확하게 표시한다. 마우스 버튼을 뗄 때, 두 엘리먼트는 자리를 바꾸며, 모든 반투명 사각형은 다음 자리바꿈 세션까지 화면에서 사라진다.

시스템 활성화하기

직전에 언급했듯이, 코드가 시스템에 입히는 피해를 최소로 줄이기를 원한다. 이는 HTML이나 XML로 작업하는 페이지 디자이너가 자리바꿈 가능한 시스템을 끙끙대면서 다루기를 원하지 않는다는 의미다. 이런 작업은 디자이너 몫이 아니다.

페이지에 다음 세 가지 요소만 있으면 된다.

  • 자바스크립트 태그
  • <body> 태그에서 onload 메서드
  • 자리바꿈 가능하다고 표시된 자리바꿈 가능한 영역

자바스크립트 태그

다음 태그를 페이지 파일 상단에 놓아야 한다.

<script src="rearrange-your-page.js"></script>

이 태그는 페이지를 올리는 과정 초반에 읽히지만, <body> onload 함수 호출이 일어날 때까지 수행되지 않는다.

<body> 태그에 있는 onload 메서드

이 메서드는 전체 페이지를 읽고 나서 자리바꿈 가능한 시스템을 호출하는 작용을 한다. 이렇게 하는 이유는 코드가 처음으로 하는 작업이 자리바꿈 가능한 구성 요소를 찾기 위한 페이지 탐색 작업이기 때문이다. 따라서 이미 구성 요소를 읽은 다음에 스크립트 실행을 원하기 때문이다. <body>onload 메서드는 Listing 1에서 제시한 내용이 되어야 한다.


Listing 1. <body>의 onload 핸들러
	
<body onload="swappable_start();">
    ... rest of page
  </body>

자리바꿈 가능하다고 표시된 자리바꿈 가능한 영역

자리바꿈이 가능하도록 만들기를 원하는 각 영역은 class 매개변수로 표시해야 한다. 이는 페이지 작성자나 디자이너가 지킬 필요가 있는 유일한 제약이다. 이 매개변수를 자리바꿈이 가능하도록 만들기를 원하는 각 섹션마다 붙여야 하기 때문이다. Listing 2를 참조하자.


Listing 2. 자리바꿈 가능한 class를 div로 표시하기
	
<div class='swappable'>
    lorem ipsum lorem ipsum
  </div>

자리바꿈 가능한 섹션 찾아내기

코드에서 수행하는 첫 번째 작업은 활성화될 페이지에 속한 섹션 탐색이다. 처음에 언급했듯이, 이 섹션을 둘러싼 태그에는 class 매개변수만 있으면 된다. 이런 섹션을 찾으려면, swappable이라는 class를 포함한 태그를 찾아야 한다. 이 함수는 표준 DOM 라이브러리의 일부는 아니지만, 구현은 아주 쉽다. Listing 3은 예제 구현을 보여준다.


Listing 3. getElementsByClass() 구현 예
	
// By Dustin Diaz
function getElementsByClass(searchClass,node,tag) {
        var classElements = new Array();
        if ( node == null )
                node = document;
        if ( tag == null )
                tag = '*';
        var els = node.getElementsByTagName(tag);
        var elsLen = els.length;
        var pattern = new RegExp("(^|\\\\s)"+searchClass+"(\\\\s|$)");
        for (i = 0, j = 0; i < elsLen; i++) {
                if ( pattern.test(els[i].className) ) {
                        classElements[j] = els[i];
                        j++;
                }
        }
        return classElements;
}

상호대화식 엘리먼트

프로그램은 기능 조각을 결합해서 하나로 합치는 방법으로 만든다. 프로그래머마다 나름대로 이를 추구하는 방법이 있지만, 대체로 큰 조각 몇 개가 아닌 작은 조각 여러 개로 프로그램을 만드는 편이 바람직하다. 작은 조각마다 명확하게 의미가 부여된 한 가지 작업을 해야 한다.

하지만 GUI 프로그래밍 과정에서 이렇게 하기란 어렵다. 훌륭한 GUI는 다양한 인터페이스 엘리먼트를 조정해야 하며, 개별 행동 방식을 결합해서 직관적으로 동작하는 전반적인 행동 양식으로 만들어야 한다. 이벤트 기반 시스템은 종종 복잡한 상호 작용으로 묶인 단일 콜백 집합으로 끝난다. 모듈화된 상호대화식 엘리먼트는 편법을 사용해 만든다.

모듈화된 상호 대화식 엘리먼트

자리바꿈 가능한 코드는 모듈화된 상호 대화 엘리먼트를 활용하려고 시도한다. 직전에 자리바꿈 가능한 시스템에서 두 가지 주요 상호대화 엘리먼트가 있다고 언급했다. 바로 끄는 구성 요소를 강조하기와 놓는 목표를 강조하기다. 코드에서 이 두 가지 엘리먼트를 독립적으로 구현한다.

상호 대화식 기능을 모듈화하는 과정은 편법을 사용하는 좋은 예다. 자리바꿈 가능한 인터페이스 설명이 의미하듯이 두 가지 상호대화 엘리먼트는 상당히 얽혀 있다. 강조와 강조 해제는 모두 단일 마우스 제스처로 일어나며, 각각은 마우스 입력 과정에서 다른 측면에 반응해 일어난다. 이런 두 가지 엘리먼트를 단일 코드 조각으로 구현할 경우에 읽기가 상당히 까다로운 이유는 동시에 여러 가지 일이 진행되기 때문이다.

drag 핸들러

GUI 구현을 모듈화하기 위해, drag 핸들러라는 뭔가를 활용한다. drag 핸들러는 GUI 시스템에 결합된 사건 핸들러와 비슷하다. 사건 핸들러는 특정 사건 유형을 처리하는 반면에 drag 핸들러는 전반적인 끌어다 놓기 과정을 다룬다. drag 핸들러는 단일 이벤트 대신에 일련의 이벤트를 다루는 핸들러의 예다. Listing 4에 drag 핸들러 골격을 실어놓았다.


Listing 4. drag 핸들러 골격
	
{
    start:
      function( x, y ) {
        // ...
      },

    move:
      function( x, y ) {
        // ...
      },

    done:
      function() {
        // ...
      },
  }

drag 핸들러는 start, move, done이라는 메서드 셋을 포함하는 객체다. 끌어다 놓기 행동을 시작할 때, start 메서드가 호출되며, 클릭 좌표가 넘어온다. 커서를 움직이면, move 메서드가 반복적으로 호출되며, 커서의 현재 좌표가 다시 넘어온다. 마지막으로 마우스 버튼을 떼면 done 메서드가 호출된다.

자리바꿈 가능한 시스템은 두 가지 drag 핸들러를 동시에 사용한다. 두 가지 측면이 복잡한 연관 관계를 맺고 있음에도 불구하고 두 가지 다른 상호 작용을 깔끔하게 처리한다. 상호 작용 중에서 한쪽만 다룰 경우 충분하지 않다. 그 대신 양쪽 상호 작용을 고려해 핸들러를 빈틈없이 동시에 활용하기를 원한다.

rectangle_drag_handler

두 drag 핸들러 중 첫 번째는 rectangle_drag_handler다. 이 핸들러는 끌고 있는 엘리먼트를 대표하는 반투명 사각형 영역 이동을 맡는다. Listing 5는 start 메서드를 보여준다.


Listing 5. rectangle_drag_handler 핸들러
	
function rectangle_drag_handler( target )
  {
    this.start = function( x, y ) {
      this.cover = coveringDiv( target );
      make_translucent( this.cover, .6 );
      this.cover.style.backgroundColor = "#777";
      dea( this.cover );

      this.dragger = new dragger( this.cover, x, y );
    };
    // ...
  }

start 메서드는 반투명 영역을 생성해 dragger라는 또 다른 객체에 전달한다. dragger는 커서 이동에 따라 DOM 엘리먼트를 움직이는 객체다. dragger에 현재 커서 좌표를 넘기면, 커서 움직임에 따라 끌고 있는 객체를 갱신한다.

Listing 6에서 move 메서드는 dragger를 갱신한다.


Listing 6. dragger 갱신하기
	
this.move = function( x, y ) {
    this.dragger.update( x, y );
  };

마지막으로 Listing 7에서 소개하는 done 메서드는 반투명 사각형을 제거한다. 끌어다 놓기 과정이 끝났기 때문이다.


Listing 7. rectangle_drag_handler done 메서드
	
this.move = function( x, y ) {
    this.done = function() {
      this.cover.parentNode.removeChild( this.cover );
    };
}

drag 핸들러 결합하기

동시에 두 drag 핸들러를 활용하는 방법을 고안할 차례다. 이는 compose_drag_handlers() 함수를 사용해 쉽게 처리할 수 있다. 이 함수는 두 drag 핸들러를 받아서 결합된 drag 핸들러 하나를 만든다. 이렇게 결합된 drag 핸들러를 일반적인 drag 핸들러를 사용하는 곳에 놓으면 두 원본 drag 핸들러의 행동 방식을 빈틈없이 결합한 결과를 얻는다.

compose_drag_handlers() 함수는 구현이 쉽다. drag 핸들러처럼 보이지만, 모든 메서드는 두 가지 원본 drag 핸들러에 속한 대응 메서드를 호출한다. 함수는 Listing 8에 정리했다.


Listing 8. compose_drag_handlers()
	
function compose_drag_handlers( a, b )
  {
    return {
    start:
      function( x, y ) {
        a.start( x, y );
        b.start( x, y );
      },

    move:
      function( x, y ) {
        a.move( x, y );
        b.move( x, y );
      },

    done:
      function() {
        a.done();
        b.done();
      },
    }
  }

코드를 보면 알 수 있듯이, drag 핸들러 ab는 drag 핸들러 하나로 결합된다. 예를 들어, 결합된 핸들러의 start() 메서드 호출은 단순히 a.start()를 호출한 다음에 b.start()를 호출하는 방식으로 동작한다.

prepare_swappable()이라는 설정 함수에서 compose_drag_handlers를 호출한다. Listing 9를 살펴보자.


Listing 9. prepare_swappable() 함수
	
function prepare_swappable( o )
  {
    swappables.push( o );
    var sdp = new rectangle_drag_handler( o );
    var hdp = new highlighting_drag_handler( o );
    var both = compose_drag_handlers( sdp, hdp );
    install_drag_handler( o, both );
  }

특히, 이 함수는 자리바꿈 가능한 엘리먼트를 위한 rectangle_drag_handlerhighlighting_drag_handler를 생성하고, 이 둘을 합쳐 결합된 drag 핸들러를 만들어낸다. 마지막으로 이렇게 결합된 drag 핸들러는 다음 두 절에서 설명한 install_drag_handler()를 호출하는 방법으로 엘리먼트를 처리하기 위해 활성화된다.

마우스 핸들러를 안전하게 설치하기

일반적인 사건 핸들러와는 달리, drag 핸들러는 다중 사건을 처리한다. 하지만 이런 기능에도 불구하고, 일반 사건 핸들러와 마찬가지로 drag 핸들러는 객체에 붙어 있을 필요가 있다.

사건 핸들러 설치에는 편법이 필요하다. 변경 중인 엘리먼트에 사건 핸들러가 이미 설치되어 있을 가능성이 높기 때문이다. 사건 핸들러를 대체한다면, 페이지 동작 방식이 바뀔지도 모른다.

이런 상황을 피하기 위해 Listing 10에 제시한 install_mouse_handlers()라는 유틸리티 함수를 활용한다.


Listing 10. install_mouse_handlers() 함수
	
function install_mouse_handlers( target, onmouseup, onmousedown, onmousemove )
  {
    var original_handlers = {
      onmouseup: target.onmouseup,
      onmousedown: target.onmousedown,
      onmousemove: target.onmousemove
    };

    target.onmouseup = onmouseup;
    target.onmousedown = onmousedown;
    target.onmousemove = onmousemove;

    return {
      restore: function() {
        target.onmouseup = original_handlers.onmouseup;
        target.onmousedown = original_handlers.onmousedown;
        target.onmousemove = original_handlers.onmousemove;
       }
    };
  }

install_mouse_handlers() 함수는 지정된 객체에 지정된 마우스 핸들러를 추가한다. 또한 원본 핸들러를 복구하기 위해 사용하는 객체를 반환한다. 이런 방식으로 끌어다 놓기 과정이 끝나면 끌어다 놓기 과정을 시작하기 전의 동작 방식으로 되돌리도록 restore() 함수를 호출할 수 있다.

drag 핸들러 활성화하기

끌어다 놓기 연산을 위한 drag 핸들러는 onmousedown, onmouseup, onmousemove라는 세 가지 마우스 핸들러를 모두 사용한다. 하지만 우선 mousedown 핸들러를 설치하기를 원할 것이다. 끌어다 놓기 연산을 시작하는 클릭을 기다리고 있기 때문이다.

클릭 사건이 일어난 난 다음에야 mousemovemouseup 핸들러 설치를 원할 것이다. 또한 이 시점에서 더 이상 mousedown 이벤트는 필요하지 않다. 이미 클릭이 끝난 상황이기 때문이다. mousedown을 제거하고 끌어다 놓기 연산이 끝난 다음에 다시 복귀하면 된다. 초기 mousedown 핸들러는 Listing 11에 제시한다.


Listing 11. 초기 mousedown 핸들러
	
var onmousedown = function( e ) {
    var x = e.clientX;
    var y = e.clientY;

    p.start( x, y );

    var target_handler_restorer = null;
    var document_handler_restorer = null;

    var onmousemove = function( e ) {
      var x = e.clientX;
      var y = e.clientY;

      p.move( x, y );
    };

    var onmouseup = function( e ) {
      p.done();
      target_handler_restorer.restore();
      document_handler_restorer.restore();
    };

    target_handler_restorer =
      install_mouse_handlers( target, onmouseup, null, onmousemove );
    document_handler_restorer =
      install_mouse_handlers( document, onmouseup, null, onmousemove );

    e.stopPropagation();

    return false;
  };

끌어다 놓기 연산을 시작해서 이 핸들러를 부를 때, onmousemoveonmouseup 핸들러를 생성한 다음에 목표 엘리먼트에 설치한다. 물론 install_mouse_handlers()를 사용하므로, 나중에 제거도 쉽다.

document 객체에 이런 핸들러를 설치했다는 사실에 주의하자. 이렇게 하는 이유는 사용자가 끌어다 놓기 과정 중에 커서를 끌어서 페이지를 이동하기 때문이다. 대부분 그렇겠지만, 자리바꿈 가능한 엘리먼트 외부에서 마우스가 움직이더라도 여전히 마우스 이벤트를 받기를 원할 것이다. 다시 한번 말하지만 나중에 복귀를 위해 install_mouse_handlers()를 사용한다.




위로


하나로 합치기

이 기사에서 여러 클래스와 함수를 설명했다. 각각은 개별적으로 보면 아주 간단하지만, 함께 동작하는 방식 또한 중요하다.

다음 목록은 전반적인 끌어다 놓기 과정을 요약한 내용이다.

  • 자리바꿈 가능한 엘리먼트를 클릭한다.
  • 엘리먼트에 속한 onmousedown 핸들러가 호출된다. 이 핸들러는 onmousemoveonmouseup 핸들러를 설치한다.
  • 마우스 움직임에 따라 등록된 두 핸들러가 호출된다.
  • 두 핸들러는 차례로 직전에 설치했던 drag 핸들러를 호출한다.
  • drag 핸들러는 실제로 통합 drag 핸들러이므로, 두 가지 다른 drag 핸들러를 결합한 효과를 나타낸다.
  • drag 핸들러 중 하나인 rectangle_drag_handler는 끌기가 가능한 엘리먼트임을 보여주기 위해 커서가 자리바꿈 가능한 엘리먼트 위로 지나갈 때 강조하는 임무를 맡고 있다.
  • 다른 drag 핸들러인 highlighting_drag_handler는 놓기가 가능한 엘리먼트임을 보여주기 위해 커서가 자리바꿈 가능한 엘리먼트 위로 지나갈 때 강조하는 임무를 맡고 있다.
  • 목표 엘리먼트에서 마우스 버튼을 떼면, highlighting_drag_handler에 속한 done() 메서드가 두 엘리먼트 자리를 바꾼다. drag 핸들러가 삭제되며, 원본 onmousedown을 복구해 다음 번 끌어다 놓기 절차를 시작할 준비를 한다.



위로


결론

끌어다 놓기 연산은 상대적으로 단순하지만, 사용자 입력을 추적하고 과정을 통해 즉각적인 피드백을 제공하기 위한 여러 가지 상호대화 과정이 연관되어 있다. 이 기사는 모듈화된 상호대화식 엘리먼트를 결합해 하나로 만드는 방법을 통해 완벽한 GUI 구축 방법을 보여준다.

이런 접근 방법에는 여러 가지 장점이 있다. 코드가 모듈화되어 있으므로, 작성과 유지가 쉽다. 모든 함수나 클래스 길이가 40행을 넘지 않으며, 종종 더 짧아진다.

상호대화식 엘리먼트 각각은 전형적인 GUI 처리 과정이나 효과를 구현하므로, 다른 문맥에서도 재사용이 가능하리라 쉽게 상상할 수 있다. 이런 상호대화식 엘리먼트를 풍부하게 갖춘 라이브러리 형태로 개발하면 부품을 끼워 맞추는 방식으로 복잡한 UI를 쉽게 만들 수 있다.





위로


다운로드 하십시오

설명 이름 크기 다운로드 방식
예제와 소스 코드1 rearrange-your-page-src.zip 181KB HTTP
다운로드 방식에 대한 정보

Note

  1. 이 기사는 자리바꿈 가능한 라이브러리를 위한 원시 코드와 활용 예제를 포함한다.


참고자료

교육

제품 및 기술 얻기

토론


필자소개

Greg Travis는 구글에서 일하는 소프트웨어 엔지니어다. Travis는 웹 프로그래머, 독립 계악자, 게임 개발자 생활을 했다.

[본문링크] 자바스크립트로 모듈화된 상호 대화식 사용자 인터페이스 만들기
[1]
코멘트(이글의 트랙백 주소:/cafe/tb_receive.php?no=31415
작성자
비밀번호

 

SSISOCommunity

[이전]

Copyright byCopyright ⓒ2005, SSISO Community All Rights Reserved.