jQuery를 사용하면 좋은 JavaScript 기반 웹 애플리케이션을 쉽고 간단하게 작성할 수 있습니다. 하지만 좋은 웹 애플리케이션을 우수한 웹 애플리케이션으로 만들려면 몇 가지 추가 단계가 필요합니다. 이 기사에서는 웹 애플리케이션의 품질을 최종적으로 한 단계 더 높일 수 있는 방법에 대해 자세히 설명합니다.
고급 jQuery(1)에서 이어지는 내용입니다.
이제부터는 또 다른 위젯을 사용하여 이 기사에서 다루고자 하는 마지막 3가지 주제를 설명할 것이며 먼저 위젯을 보고 설명한 후 위젯 코드를 자세히 살펴본다. 이 위젯은 이전 기사(기사에 대한 링크는 참고자료 참조)에서 보았던 익숙한 401k 위젯이다. 그렇지만 여기에서는 같은 페이지에 위젯을 두 번 포함시켰기 때문에 약간의 차이가 있다. 이 위젯은 별도의 두 테이블에 연결되어 몇 가지 흥미로운 점을 보여 준다. 그림 3에서는 위젯의 모습을 보여 준다.
필자는 이 위젯에서 몇 가지 작업을 수행하고 있다. 첫 번째는 텍스트 필드의 합계를 구하고 합계가 100이 되는지 여부를 판별하는 작업이다. 합계가 100이 되지 못하면 위젯을 올바르게 사용하지 못하고 있음을 알려 주기 위해 사용자에게 오류를 표시한다. 두 번째 작업은 텍스트 입력을 받을 때마다 각 항목을 정렬하는 것이다. 즉, 투자 비율이 가장 높은 항목이 항상 테이블의 맨 위로 올라가게 된다. 그림 3에서 각 항목이 백분율을 기준으로 하는 내림차순으로 정렬된 모습을 볼 수 있다. 마지막으로 보기 좋게 꾸미기 위해 줄무늬를 추가한다.
이 위젯을 생성하는 HTML 코드는 놀라울 정도로 간단하다. Listing 8에서 전체 코드를 보여 준다.
<p><table width=300 class="percentSort" cellpadding=0 cellspacing=0> <tbody> <tr><td>S&P 500 Index</td> <td><input type=text> %</td></tr> <tr><td>Russell 2000 Index</td> <td><input type=text> %</td></tr> <tr><td>MSCI International Index</td> <td><input type=text> %</td></tr> <tr><td>MSCI Emerging Market Index</td> <td><input type=text> %</td></tr> <tr><td>REIT Index</td> <td><input type=text> %</td></tr> </tbody> <tfoot> </tfoot> </table> |
이처럼 HTML 코드가 간단한 이유는 jQuery 내에서 위젯을 설정하고 필요한 모든 코드가 jQuery 코드에 들어 있기 때문이다. 이러한 방법은 분명 이벤트를 항목에 연결하거나 특정 상황에서 클래스를 연결하는 등의 간단한 작업을 수행할 때 사용된다. 어떤 경우에는 여기에서 한 단계 더 나아가기도 한다. 이러한 위젯의 설정 코드는 모두 jQuery 코드 자체에 포함된다. HTML 설계자, JavaScript 개발자 등의 역할에 따라 내용을 분리하여 각 역할에 필요한 여러 가지 내용을 장황하게 설명할 수도 있지만 그러한 설명은 많이 들었을 것이므로 여기에서는 한 가지만 추가할 것이다. 그것은 바로 "클래스 데코레이션"이라는 아이디어로 플러그인 작성자도 자주 사용하고 있는 방법이다. Listing 8의 HTML 코드를 보면 percentSort
클래스를 테이블에 연결하는 것만으로 테이블의 전체 기능과 모습을 변환시킬 수 있다는 것을 알 수 있다. 이와 같이 우리가 설계한 위젯의 진정한 목표는 클래스를 추가하는 것처럼 쉽게 위젯을 추가 및 제거할 수 있는 방법을 제공하는 것이다.
이제 jQuery에서 위젯을 설정하는 몇 가지 단계를 살펴보자. 이러한 단계를 살펴보고 나면 Listing 9에서 이 설계 패턴이 어떻게 구현되었는지 알 수 있다.
$(document).ready(function() { // the first step is to find all the tables on the page with // a class of percentSort. These are all the tables we want to // convert into our widget. // After we find them, we need to loop through them and take some // actions on them // At the conclusion of this block of code, each table that's going to // be a percentSort widget will have been transformed $("table.percentSort").each(function(i){ // each table needs a unique ID, for namespace issues (discussed later) // we can simply create a uniqueID from the loop counter $(this).attr("id", "percentSort-"+i); // within each table, let's highlight every other row in the table, to // give it that "zebra" look $(this).find("tbody > tr").filter(":odd").addClass("highlight"); // because each table needs to show the "Total" to the user, let's create a new // section of the table in the footer. We'll add a row in the table footer // to display the words "Total" and a span for the updated count. $("#"+$(this).attr("id") + " tfoot") .append("<tr><td>Total</td><td> <span></span> %</td></tr>"); // finally, let's add the CLASS of "percentTotal" to the span we just // created above. We'll use this information later to display // the updated totals $("#"+$(this).attr("id") + " tfoot span").addClass("percentTotal"); }); // now the second step, after we've completed setting up the tables themselves // is to set up the individual table rows. // We can similarly sort through each of them, taking the appropriate actions // on each of them in turn. // Upon completion of this block of code, each row in each table will be // transformed for our widget $("table.percentSort tbody > tr").each(function(i){ // get the namespace (to be discussed in the next section) var NAMESPACE = $(this).parents("table.percentSort").attr("id"); // attach a unique ID to this row. We can use the loop counter // to ensure the ID is unique on the page (which is a must on every page) $(this).attr("id", "row"+i); // now, within this row of the table, we need to find the text input, because // we need to attach a class to them. We utilize the namespace, and also // find the :text within the table row, and then attach the correct class $("#"+$(this).attr("id") + " :text").addClass("percent"); // Finally, we attach a unique ID to each of the text inputs, and we do this by // making it a combination of the table name and the row name. $("#"+$(this).attr("id") + " .percent"). attr("id", NAMESPACE + "-" + $(this).attr("id")); }); // Finally, because we know we only want numerical inputs, we restrict the text entry // to just numbers. We must do this now, because up until this point, the page // contained no elements with the CLASS "percent" $(".percent").numeric(); |
이 예제에서 볼 수 있는 것처럼 jQuery 코드에서 수많은 기능을 HTML 코드에 제공할 수 있다. 명확한 장점을 가지고 있는 이 설계 패턴은 역할 분리, 코드 재사용 등의 아이디어를 충실히 따르고 있다. 플러그인 개발자도 간단한 HTML 마크업을 위젯 플러그인으로 변환하는 방법을 사용하기 때문에 위젯 플러그인에서도 이 설계 패턴을 볼 수 있다. 기본적으로 여기에서 수행하고 있는 작업은 간단한 테이블을 정렬 및 합계 테이블로 변환하는 플러그인을 작성하는 것이다.
중요: jQuery 코드 자체에 되도록 많은 코드를 설정하고 HTML 코드는 최소한으로 사용한다.
이 설계 패턴과 jQuery를 함께 사용할 때 가장 혼란스러운 부분 중 하나는 아마도 코드가 작동하는 네임스페이스를 올바르게 이해하는 것일 것이다. 이 예제를 보면 이 문제점이 매우 명확하게 보이므로 네임스페이스를 이해하는 데 많은 도움이 될 것이다. 페이지에 있는 모든 ID와 클래스를 알고 있는 상태에서 jQuery 코드를 작성한다면 매우 간결하고 쉽게 코드를 작성할 수 있다. 결국 설계의 목적도 바로 이를 위한 것이다. 하지만 페이지에 있는 클래스를 정확히 알지 못하거나 동일한 기능이 두 클래스 모두에 있다면 어떻게 될까? 이 예제에서는 이 문제를 정확히 파악할 수 있다. 이제 완전히 동일한 두 위젯을 살펴보자. 정말로 모든 것이 동일하다. 이 예제는 네임스페이스 문제를 고려하지 않았을 때 발생하는 상황을 보여 준다. 즉, 두 위젯이 서로 간섭하기 시작하면서 결국에는 잘못된 결과를 제공하고 만다.
이러한 문제가 발생하는 이유는 잘못된 코드에서 업데이트되어야 하는 위젯을 구체적으로 지정하지 않았기 때문이다. 이에 대한 예로는 $(".percentTotal")
을 호출하는 경우를 들 수 있다. 같은 페이지에 여러 테이블이 있기 때문에 이 페이지에는 percentTotal
클래스의 여러 인스턴스가 있게 된다. 물론 이들 테이블 중 하나만 있다면 이 클래스의 인스턴스도 하나만 있다고 생각할 수 있다. 하지만 페이지가 복잡해지고 위젯을 다시 사용하는 경우가 많아지면 이러한 가정이 아무런 도움이 되지 않는다. 여기에서 ID를 대신 사용해도 되지 않느냐고 물을 수도 있겠지만 이 방법은 근본적인 솔루션이 되지 못한다. 과연 어떤 ID를 지정할 수 있을까? "percentTotal"은 명료하지 않기 때문에 사용할 수 없다. 그리고 "percentTotal-1"은 페이지의 어떠한 항목도 실제로 의미하지 않기 때문에 사용할 수 없다. (결국 이들 숫자는 개발자가 임의로 만든 것에 불과하다.) 또는 "percentTotal-percentSort1"과 같은 참조를 테이블에 연결할 수도 있다. 이 방법은 하나의 대안이 될 수도 있겠지만 지나치게 복잡한 방법이다. jQuery에는 매우 정교하고 사용하기 쉬운 선택 구문이 있기 때문에 이러한 유형의 하이브리드 이름 지정 스키마를 사용하지 않아도 된다. 여기에서 많은 내용을 변경할 필요 없이 jQuery의 선택 엔진을 사용하여 네임스페이스 문제를 해결할 수 있다.
이 위젯과 관련된 문제의 핵심은 작업이 발생하는 위젯을 결정하는 것이다. 텍스트 필드 중 하나에 숫자를 입력할 때 jQuery에서 텍스트가 입력된 텍스트 필드를 어떻게 알 수 있을까? 물론 이 코드에서는 "percent" 클래스에 이벤트를 연결했으며 코드 내에서 $(this)
를 사용하여 해당 클래스를 참조할 수 있다. 그렇다면 jQuery에서는 어떤 방법으로 이 이벤트가 발생한 위젯을 찾아내서 해당 percentTotal
필드를 업데이트할 수 있을까? 필자의 생각으로는 jQuery에서는 이 이벤트가 발생한 위젯을 찾기 어려울 것이다. 페이지에서 "percent" 클래스를 가지고 있는 모든 텍스트 필드를 이벤트와 연결할 수 있다는 점에서는 좋은 코드지만, 이벤트가 발생한 곳에서 포함된 위젯이 무시된다면 좋은 코드라고 볼 수 없다.
이 문제점을 네임스페이스 문제라고 부른 이유는 위젯의 이름을 혼동하여 문제를 일으킬 수 있기 때문이다. jQuery 코드가 올바르게 작동하려면 각 이름이 고유 공간 즉, 네임스페이스에서 명확하게 정의되어야 한다. 코드는 이름이 겹치지 않도록 작성하여 각 위젯이 자체 포함되도록 해야 한다. 겹치는 이름 없이 같은 페이지에 있는 같은 위젯의 여러 인스턴스를 추가할 수 있어야 한다. 기본적으로 각 위젯은 페이지에서 독립적으로 실행되어야 한다.
이 네임스페이스 문제는 여러 가지 방법으로 해결할 수 있으므로 이 기사에서는 필자의 방법을 소개한다. 이 방법을 사용자의 코드에서 사용하거나 문제점에 대한 설명을 참조하여 보다 나은 솔루션을 직접 구현할 수도 있다. 필자가 이 코드를 좋아하는 이유는 사용하기 쉽고(단 1행) 고유 네임스페이스를 제어할 수 있기 때문이다. Listing 10을 살펴보자.
// our event is attached to EVERY input text field with a CLASS of "percent" // this makes our code look good, but can lead to namespace issues $("table.percentSort input.percent").keyup(function(){ // this simple line can establish a namespace for us, by getting the unique // ID attached to our table (the table is considered the "container" for // our widget. It could be anything, depending on your specific code.) // We pass the CLASS of our widget to the parents() function, because // that effectively encapsulates the widget var NAMESPACE = "#" + $(this).parents("table.percentSort").attr("id"); // with the namespace established, we can use the jQuery selection // syntax to use this namespace as our prefix for all of our remaining // searches. Notice how the ambiguity is removed for our search // by CLASS of "percent" and "percentTotal" // This solves our namespace issues var sum = $(NAMESPACE + " input.percent").sum(); var totalField = $(NAMESPACE + " .percentTotal"); |
이처럼 단 한 줄의 코드를 추가하여 위젯을 캡슐화하면 위젯의 인스턴스끼리 함수가 겹쳐지는 현상을 방지할 수 있으며 게다가 같은 이름을 가진 ID나 클래스를 사용하는 위젯 사이에서도 이러한 문제를 피할 수 있다. 이러한 유형의 코딩은 플러그인 코드에서 자주 사용된다. 잘 작성된 플러그인을 보면 네임스페이스 문제를 염두에 두고 있지만 그렇지 못한 플러그인에서는 이 문제를 중요하게 다루지 않는다. 이 예제에서 볼 수 있듯이 이 방법은 코드에서 비교적 쉽게 활용할 수 있으며 페이지가 복잡해지더라도 많은 시간을 절약하는 데 도움이 된다. 이러한 장점을 감안하면 jQuery 코드를 작성할 때 항상 네임스페이스 문제를 염두에 두어야 한다. 그리고 이 기사에서 제시한 솔루션을 자신의 모든 코드에서 활용하면 좋을 것이다.
중요: jQuery 코드 작성을 시작할 때는 항상 네임스페이스 솔루션을 먼저 포함시킨다. 위 솔루션을 사용하거나 자신만의 고유한 솔루션을 작성할 수 있다. 이 방법을 따르면 코드가 복잡해지더라도 네임스페이스로 인한 문제가 발생하지 않는다.
이 마지막 섹션에서는 1.3 릴리스의 jQuery에 도입된 새 기능에 대해 설명한다. 1.3 릴리스는 성능 측면에서 매우 중요한 릴리스이며 이 한 가지 이유만으로도 기존 코드를 이 릴리스로 전환할 충분한 이유가 될 것이다. 하지만 코드 개선에 도움이 되는 몇 가지 간단한 기능도 코어 코드에 추가되었다.
1.3 코어의 첫 번째 추가 기능은 이 기사에서 설명한 live()
/die()
함수이다. 기사의 한 섹션 전체를 할애한 것만 보아도 짐작할 수 있듯이 이들 함수는 추가 기능 중 가장 중요한 기능이다. 1.3 코어의 다른 추가 기능으로는 jQuery.Event
클래스가 있다. 이 클래스는 페이지에서 발생한 이벤트를 하나의 오브젝트로 캡슐화한다. 이 클래스는 이벤트와 관련하여 필요한 모든 정보를 전달하는 잘 포함된 오브젝트를 제공하므로 이벤트 위주의 애플리케이션에서 매우 유용하게 활용할 수 있다. 유형, 대상, X 및 Y 좌표, 시간 소인 등의 정보가 전달된다. 이러한 정보는 1.3 이전 릴리스에서도 사용할 수 있었지만 문서화도 잘 되어 있지 않았을 뿐만 아니라 캡슐화도 되지 않았다. 1.3 릴리스의 마지막 추가 기능은 개발자와 별로 상관이 없을 수도 있겠지만 알아둘 만한 기능이다. 이 릴리스에서는 특정 if
명령문을 사용하여 브라우저 또는 버전을 검색하는 브라우저 검색이 더 이상 사용되지 않는다. 지원해야 하는 모든 브라우저를 처리하기 위해 코드가 얼마나 복잡해져야 할지 충분히 상상할 수 있을 것이다. 사용되는 브라우저가 얼마든지 변경될 수 있기 때문에 이 릴리스에서는 각 jQuery 버전에 특정 소멸 기능을 추가했다. 대신 브라우저 유형/버전을 검색하지 않은 채로 브라우저의 기능을 검색한다. 즉, 새 브라우저가 만들어져서 기존 브라우저가 사라지더라도 jQuery 버전을 업데이트할 필요가 없다. 이제는 해마다 jQuery 버전을 업그레이드하고 테스트를 수행하는 등의 번거로운 작업을 하지 않아도 된다.
이 기사에서는 좋은 jQuery 코드를 우수한 jQuery 코드로 전환하는 데 필요한 몇 가지 팁을 설명했다. jQuery는 사용하기가 쉽고 향상된 독립 실행형 JavaScript이기 때문에 좋은 jQuery 코드를 매우 쉽게 작성할 수 있다. 대부분의 개발자가 몇 분 이내에 코드를 작성해서 실행할 수 있다. 하지만 좋은 코드와 우수한 코드 사이에는 한 가지 구별되는 점이 있다. 우수한 jQuery 코드에서는 페이지 복잡도의 증가에 따라 성능을 고려한다. 우수한 jQuery 코드는 페이지의 현재 위치가 아닌 앞으로 이동할 위치를 미리 생각할 뿐만 아니라 그러한 기대가 코드에 반영되어 있다. 우수한 jQuery 코드는 가장 복잡한 애플리케이션을 감안하여 설계되기 때문에 간단한 애플리케이션의 경우에는 쉽게 처리할 수 있다.
이 기사에서는 좋은 jQuery 코드를 우수한 jQuery 코드로 전환하는 데 도움이 되는 5가지 개념을 소개했다. 첫 번째 개념은 bind()
/unbind()
메소드를 사용하는 것이다. 이들 메소드는 페이지의 수명 기간 중 코드에 이벤트를 연결하지 않으려는 경우 페이지 요소에 이벤트를 연결/분리하는 데 매우 유용하게 사용할 수 있다. 이 방법은 전체 페이지에서 이벤트가 발생할 수 있는 경우와 같이 성능에 영향을 줄 수 있는 상황이나 특정 사용자 인터페이스 상황에서 중요하게 사용할 수 있다. 두 번째 개념은 1.3 릴리스의 새 기능인 live()
/die()
기능을 사용하는 것이다. 이들 함수를 사용하면 이벤트를 페이지 요소처럼 동적으로 만들 수 있다. 즉, 페이지 요소가 웹 애플리케이션에 추가될 때 코드도 페이지와 함께 확장된다. 이전 릴리스에서는 이 기능이 지원되지 않았다. 이제는 이벤트를 페이지처럼 동적으로 처리할 수 있다. 세 번째 추가 기능은 Ajax Queue/Sync 플러그인이었다. 이 플러그인은 클라이언트 퍼스펙티브에서 제어할 수 없는 상황과 리턴 순서가 중요한 경우에 서버에 대한 Ajax 호출을 제어하는 데 사용된다. 네 번째 권장 사항은 페이지 설정 코드를 jQuery 코드에 되도록 많이 작성하라는 것이었다. 이렇게 하면 HTML을 단순하게 작성할 수 있으며 페이지를 설정할 때 훨씬 쉽게 작업할 수 있다. 마지막 단계는 코드에서 필자가 제공하거나 자신이 직접 작성한 네임스페이스 솔루션을 활용하여 위젯 간에 함수가 겹쳐지는 것을 방지하여 페이지 오류를 없애는 것이었다. 각 페이지 요소 및 위젯은 스스로를 포함할 수 있어야 하며 페이지의 다른 특성을 간섭하지 않아야 한다. 이 솔루션이 이러한 요구를 충족한다.
이러한 5가지 개념은 결코 어려운 개념이 아니다. 실제로 이중 4가지는 한 줄의 코드를 변경하는 것처럼 간단하다. 하지만 이러한 솔루션을 코드에서 사용하는 방법을 이해하는 것이 중요하다. 다른 모든 것과 마찬가지로 올바르게 사용하지 못하면 코드에 도움을 주기 보다는 피해를 입힐 수도 있다. 필자는 우선 jQuery 코딩을 시작할 때처럼 곧바로 이러한 5가지 개념을 직접 사용해보기를 권한다. 모든 개발자들이 말하는 것처럼 피쳐 크리프(feature creep)는 개발자 인생의 일부분이다. 초기 단계에서 내린 잘못된 결정으로 인해 전체 웹 애플리케이션을 다시 설계하고 싶지 않다면 또는 관리자의 요구 때문에 특정 기능을 어쩔 수 없이 변경해야 한다면 우수한 애플리케이션 개발을 염두에 두고 있다면 코드 작업 시 이 기사에서 제안한 내용을 실제로 구현해 보자.
마지막으로 이 기사는 필자가 집필한 jQuery 시리즈 중 두 번째 시리즈의 마지막 기사이다. 이제 여러분은 이러한 5편의 기사를 통해 jQuery 실력이 한 단계 업그레이드되었기에 이 라이브러리를 사용하여 다양한 유형의 애플리케이션을 작성할 수 있을 것이다. 필자가 마지막으로 권하는 바는 즐거운 마음으로 코드를 작성하면서 많은 작업을 실제로 구현해 보라는 것이다. 이렇게 하면 단순히 많은 배우는 차원에 머물지 않고 정말 멋진 작품을 만들어서 웹에 올릴 수도 있을 것이다.