Handlebars 코딩 해보기
1) 기본적인 구조를 통해 이해
Handlebar 템플릿은 아래처럼 script태그 안에 type을 “text/x-handlebars-template” 등으로 설정하고 위치시킨다. 그리고 템플릿 안 구조에 {{ }}로 감싸진 부분이 데이터가 binding되는 부분이다.
{{#users}} 와 {{/users}} 안에 들어있는 html 태그들은 items라는 배열의 길이만큼 반복하게 된다. 그리고 그 내부의 {{name}},{{id}},{{email}}은 배열 요소 객체의 name,id,email 속성값이 바인딩 된다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
<script id="entry-template" type="text/x-handlebars-template"> <table> <thead> <th>이름</th> <th>아이디</th> <th>메일주소</th> </thead> <tbody> {{#users}} <tr> <td>{{name}}</td> <td>{{id}}</td> <td><a href="mailto:{{email}}">{{email}}</a></td> </tr> {{/users}} </tbody> </table> </script> |
실제 템플릿과의 바인딩은 자바스크립트를 통해서 이루어진다. 아래는 자바스크립트를 통해 Binding하는 코드이다.
아래 코드를 보면 먼저 앞에서 작성한 Handlebar Template의 내용을 가져온다. Handlebars.compile 메소드를 통해 Template을 컴파일한 뒤, 리턴 된 함수의 Parameter에 바인딩할 데이터를 Parameter로 넣으면 데이터가 Binding된 HTML Template이 리턴된다. 이 HTML Template을 DOM에 추가하면 끝난다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
//핸들바 템플릿 가져오기 var source = $("#entry-template").html();
//핸들바 템플릿 컴파일 var template = Handlebars.compile(source);
//핸들바 템플릿에 바인딩할 데이터 var data = { users: [ { name: "홍길동1", id: "aaa1", email: "aaa1@gmail.com" }, { name: "홍길동2", id: "aaa2", email: "aaa2@gmail.com" }, { name: "홍길동3", id: "aaa3", email: "aaa3@gmail.com" }, { name: "홍길동4", id: "aaa4", email: "aaa4@gmail.com" }, { name: "홍길동5", id: "aaa5", email: "aaa5@gmail.com" } ] };
//핸들바 템플릿에 데이터를 바인딩해서 HTML 생성 var html = template(data);
//생성된 HTML을 DOM에 주입 $('body').append(html); |
||
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
//Handlebar 템플릿을 가져오기 var source = $("#entry-template").html(); //Handlebar 템플릿을 precompile var template = Handlebars.compile(source); //Handlebar 템플릿에 바인딩할 데이터 var data = { items:[ {name: "shoes", price:"5000", intro:"www.a.com"}, {name: "T-shirt", price:"1000", intro:"www.b.com"}, {name: "Jeans", price:"2000", intro:"www.c.com"} ] }; //Handlebar 템플릿에 데이터를 바인딩해서 HTML을 생성 var itemList = template(data); //생성된 HTML을 DOM에 주입 $('body').append(itemList); |
||
아래와 같은 결과가 출력된다.
1 2 3 4 5 6 |
이름 아이디 메일주소 홍길동1 aaa1 aaa1@gmail.com 홍길동2 aaa2 aaa2@gmail.com 홍길동3 aaa3 aaa3@gmail.com 홍길동4 aaa4 aaa4@gmail.com 홍길동5 aaa5 aaa5@gmail.com |
참고로 링크 부분의 malito는 저 링크를 클릭했을 때 바로 전자메일을 보낼 수 있게 해주는 태그이다.
코드를 통해 알 수 있듯이 실제 템플릿이 데이터를 컴파일하고, 바인딩해주는 부분이 모두 자바스크립트 코드에서 이루어진다. Handlebars.compile 메서드를 이용해 템플릿을 컴파일하고, 리턴 된 함수의 Parameter에 Binding된 데이터를 Parameter로 넣으면 Binding된 HTML템플릿이 리턴 된다. 그리고 이 HTML템플릿을 DOM에 추가함으로써 구현이 완료된다.
2) 사용자 정의 Helper 사용해보기
Handlebar가 Mustache를 기초로 개발되었지만, Mustache와의 차별 점은 바로 사용자 정의 Helper이다. Mustache는 단순하게 주입된 데이터를 바인딩해주는 템플릿 엔진이었다면 Handlebar는 if, unless 등의 기본 Helper뿐만 아니라 사용자 정의 Helper를 추가해서 좀 더 템플릿 엔진에서 간단한 로직을 포함시킬 수 있다는 것이다. 아래는 기본적인 사용자 정의 Helper예시이다.
1 2 |
{{!-- 사용자 정의 Helper인 email에 id를 인자로 넘긴다 --}} <td><a href="mailto:{{email id}}">{{email id}}</a></td> |
앞의 예시에서 HTML코드에서 email라고만 되었던 부분에 {{email id}} 형태로 바뀌고, id를 Parameter를 전달해주고 있다. 즉, email이라는 이름의 사용자 정의 Helper가 사용되고 있는걸 알 수 있다. 이를 해석하면 email이라는 이름의 사용자 정의 Helper를 호출하면서 Parameter로 id 값을 전달한다는 의미이다.
전체 코드를 보면 아래와 같다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
<script id="entry-template" type="text/x-handlebars-template"> <table> <thead> <th>이름</th> <th>아이디</th> <th>메일주소</th> </thead> <tbody> {{#users}} <tr> <td>{{name}}</td> <td>{{id}}</td>
{{!-- 사용자 정의 헬퍼인 email에 id를 인자로 넘긴다 --}} <td><a href="mailto:{{email id}}">{{email id}}</a></td> </tr> {{/users}} </tbody> </table> </script> |
그리고 {{!-- --}}를 통행 주석을 표시해 주고도 있다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
//핸들바 템플릿 컴파일 var template = Handlebars.compile(source);
//핸들바 템플릿에 바인딩할 데이터 var data = { users: [ { name: "홍길동1", id: "aaa1" }, { name: "홍길동2", id: "aaa2" }, { name: "홍길동3", id: "aaa3" }, { name: "홍길동4", id: "aaa4" }, { name: "홍길동5", id: "aaa5" } ] };
//커스텀 헬퍼 등록 (id를 인자로 받아서 전체 이메일 주소를 반환) Handlebars.registerHelper('email', function (id) { return id + "@daum.net"; });
//핸들바 템플릿에 데이터를 바인딩해서 HTML 생성 var html = template(data);
//생성된 HTML을 DOM에 주입 $('body').append(html); |
스크립트 부분을 보면 Binding할 데이터에 email이 없는걸 확인할 수 있다. 그 대신 Custom Helper가 등록하는 코드가 구현되고 있는걸 확인할 수 있다. Handlebars.registerHelper라는 메소드를 사용하여 사용자 정의 Helper를 등록하고 있다. 첫번째 Parameter로 Helper의 이름을 넣고, 두 번째 Parameter에서 Helper의 동작을 정의한다. 위 코드에서는 id을 인자로 받아서 ‘@daum.net’이라는 문자열을 더해주는 로직이다.
결과는 아래와 같다.
1 2 3 4 5 6 7 |
이름 아이디 메일주소 홍길동1 aaa1 aaa1@daum.net 홍길동2 aaa2 aaa2@daum.net 홍길동3 aaa3 aaa3@daum.net 홍길동4 aaa4 aaa4@daum.net 홍길동5 aaa5 aaa5@daum.net |
3) Handlebar에서 제공하는 기본 Helper
Handlebar는 if, unless 등의 기본 Helper와 사용자 정의 Helper를 이용해서 간단한 로직을 구현할 수 있다. 하지만 if의 경우 오직 true/false만 판별할 수 있기 때문에 복잡한 조건은 Handlebar를 통해서 처리할 수 없다. 사용자 정의 helper를 통해 가능하지만, Temple에 로직을 배제하는 사상과 맞지 않기 때문에 가능하면 사용하지 않는게 좋다.
그럼 Handlebars에서 제공하는 기본 Helper를 알아보자
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
<script id="entry-template" type="text/x-handlebars-template"> <table> <thead> <th>이름</th> <th>아이디</th> <th>메일주소</th> <th>요소정보</th> </thead> <tbody> {{!-- {{#each users}} 는 {{#users}} 로도 대체 가능하다 --}} {{#each users}} <tr> {{!-- {{name}} 은 {{this.name}} 과 같고 {{.}} 은 현재 name과 id를 포함하고 있는 오브젝트를 가리킨다 --}} <td>{{name}}</td> <td>{{id}}</td>
{{!-- 사용자 정의 헬퍼인 email에 id를 인자로 넘긴다 --}} <td><a href="mailto:{{email id}}">{{email id}}</a></td>
{{!-- 요소의 순번은 @index 혹은 @key로 잡을 수 있는데, array와 object 모두 잡을 수 있는 key가 더 나아보인다 --}}
{{#if @first}} <td>첫 아이템 ({{@key}} 번째 요소)</td> {{else if @last}} <td>마지막 아이템 ({{@key}} 번째 요소)</td> {{else}} <td>중간 아이템 ({{@key}} 번째 요소)</td> {{/if}} </tr> {{/each}} </tbody> </table> </script> |
배열 등을 반복해주는 each Helper의 경우에는 #배열명으로 대체가 가능하다. @기호와 함께 몇 번째 인덱스인지도 구분이 가능하다. @first로 첫번째 인덱스를 알 수 있고, @last로 마지막 인덱스를 확인할 수 있다. 또한 해당 인덱스가 몇 번째 인덱스인지, 오브젝트라면 어떤 오브젝트인지 @key를 통해 알 수 있다. 앞에서 설명했듯이 if는 true/false 만 판별이 가능하며, else와 else if 를 지원하며, unless 는 if의 반대되는 Helper이다.
4) partial template 등록 및 사용
다른 템플릿엔진처럼 Handlebar도 Handlebar 템플릿을 다른 Handlebar 템플릿의 일부로 포함시킬 수 잇다. 이를 통해 템플릿의 재 사용성과 가독성을 높일 수 있고, 좀 더 객체지향적인 프로그래밍이 가능하다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
<!-- 핸들바 템플릿 --> <script id="partial-template" type="text/x-handlebars-template"> {{!-- #unless 헬퍼는 #if 의 정반대 기능을 한다. --}} <h1>리스트 {{#unless users}}<small>사용자 리스트가 없습니다.</small>{{/unless}}</h1> </script>
<!-- 핸들바 템플릿 --> <script id="entry-template" type="text/x-handlebars-template">
{{!-- 조각 템플릿은 #> 를 사용해서 포함시킬 수 있다. --}} {{#> commonHeader}} partial template 로드 실패시 보여지는 내용 {{/commonHeader}}
<table> <thead> <th>이름</th> <th>아이디</th> <th>메일주소</th> <th>요소정보</th> </thead> <tbody> {{!-- {{#each users}} 는 {{#users}} 로도 대체 가능하다 --}} {{#each users}} <tr> <td>{{name}}</td> <td>{{id}}</td>
{{!-- 사용자 정의 헬퍼인 email에 id를 인자로 넘긴다 --}} <td><a href="mailto:{{email id}}">{{email id}}</a></td>
{{!-- 요소의 순번은 @index 혹은 @key로 잡을 수 있는데, array와 object 모두 잡을 수 있는 key가 더 나아보인다 --}}
{{#if @first}} <td>첫 아이템 ({{@key}} 번째 요소)</td> {{else if @last}} <td>마지막 아이템 ({{@key}} 번째 요소)</td> {{else}} <td>중간 아이템 ({{@key}} 번째 요소)</td> {{/if}} </tr> {{/each}} </tbody> </table> </script> |
위의 예를 보면, 앞서 나온 예제와는 다른 부분이 있다. Template을 담고 있는 Script 태그가 한 개 더 있다.
1 2 3 4 |
<script id="partial-template" type="text/x-handlebars-template"> {{!-- #unless 헬퍼는 #if 의 정반대 기능을 한다. --}} <h1>리스트 {{#unless users}}<small>사용자 리스트가 없습니다.</small>{{/unless}}</h1> </script> |
Entry-template 안을 잘 보면, #> 기호와 함께 partial template을 삽입해 주는 부분을 발견 할 수 있다. 이는 #>와 함께 partial template의 이름을 넣어주면 해당 위치에 partial template이 삽입된다. partial template의 이름은 자바스크립트에서 등록한다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
//핸들바 템플릿 컴파일 var template = Handlebars.compile(source);
//핸들바 템플릿에 바인딩할 데이터 var data = { users: [ { name: "홍길동1", id: "aaa1" }, { name: "홍길동2", id: "aaa2" }, { name: "홍길동3", id: "aaa3" }, { name: "홍길동4", id: "aaa4" }, { name: "홍길동5", id: "aaa5" } ] };
//조각 템플릿을 'commonHeader' 라는 이름으로 등록 Handlebars.registerPartial('commonHeader', partial);
//커스텀 헬퍼 등록 (id를 인자로 받아서 전체 이메일 주소를 반환) Handlebars.registerHelper('email', function (id) { return id + "@daum.net"; });
//핸들바 템플릿에 데이터를 바인딩해서 HTML 생성 var html = template(data);
//생성된 HTML을 DOM에 주입 $('body').append(html); |
위 코드를 보면, entry-template 과 마찬가지로 partial-template을 가져온 뒤, Handlebars.registerPartial 메소드를 사용해서 가져온 Template을 commonHeader라는 이름으로 등록해주고 있는 것을 확인할 수 있다. 이렇게 등록만 해주면 데이터를 Binding해주는 시점에서 partial template을 삽입해주게 되고, partial template에도 주어진 데이터와 Binding될 부분이 있다면 데이터 Binding역시 처리되게 된다.
5) with와 as 그리고 상대 경로 참조
지금까지 살펴본 내용들은 기본적인 Handlebars의 문법이었다면, 이런 문법을 조합한 좀더 복잡한 구조를 살펴보자. 자주 있는 상황은 아니지만, 이중 혹은 삼중의 Loop을 돌리는 상황에서 상위 Loop의 값을 하위 Loop에서 참조해야 하는 상황에서 활용할 수 있다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
<!-- 핸들바 템플릿 --> <script id="entry-template" type="text/x-handlebars-template">
<table> <thead> <th>이름</th> <th>아이디</th> <th>취미</th> </thead> <tbody> {{!-- {{#each users}} 는 {{#users}} 로도 대체 가능하다 --}} {{#each users as |user userId|}} <tr> <td>{{name}}</td> <td>{{id}}</td> <td> {{#each hobbys as |hobby hobbyId|}} {{!-- 처음이 아닌 경우에는 쉼표(,) 넣기 --}} {{#unless @first}}, {{/unless}}
{{!-- 상위 이터레이션의 인덱스 넘버를 가져올 때 --}} {{@../index}} == {{!-- 상위 이터레이션의 인덱스 넘버는 아래와 같은 방식도 가능하다 --}} {{userId}}
{{!-- 상대경로로 참조한 name 을 myName 이라는 변수로 할당 --}} {{#with ../this.name as |myName|}} {{!-- 상대경로를 참조해서 #hobby의 현재값 출력 --}} {{myName}} 의 {{hobbyId}}번 취미 {{hobby}} {{/with}} {{/each}} </td> </tr> {{/each}} </tbody> </table>
</script> |
../ 를 사용해서 상위의 경로를 참조할 수 있고, with 와 as 를 활용해서 이렇게 참조한 상위의 값을 다른 이름으로 할당해서 사용하고 있다.
상위 Loop에서 참조하고 있는 user 에 item에 있는 name 을 myName으로 할당하여 활용하고 있다. 그리고 상대 경로를 참조하여 hobby의 값도 출력해주고 있다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
//핸들바 템플릿 가져오기 var source = $("#entry-template").html();
//핸들바 템플릿 컴파일 var template = Handlebars.compile(source);
//핸들바 템플릿에 바인딩할 데이터 var data = { users: [ { name: "홍길동", id: "aaa", hobbys: ['축구', '야구', '농구'] }, { name: "이순신", id: "bbb", hobbys: ['족구', '피구', '탁구'] }, { name: "이성계", id: "ccc", hobbys: ['수구', '배구', '농구'] }, { name: "장영실", id: "ddd", hobbys: ['축구', '피구', '농구'] }, { name: "장보고", id: "eee", hobbys: ['배구', '야구', '족구'] } ] };
//핸들바 템플릿에 데이터를 바인딩해서 HTML 생성 var html = template(data);
//생성된 HTML을 DOM에 주입 $('body').append(html); |
결과는 아래와 같다.
1 2 3 4 5 6 |
이름 아이디 취미 홍길동 aaa 0 == 0 홍길동 의 0번 취미 축구 , 0 == 0 홍길동 의 1번 취미 야구 , 0 == 0 홍길동 의 2번 취미 농구 이순신 bbb 1 == 1 이순신 의 0번 취미 족구 , 1 == 1 이순신 의 1번 취미 피구 , 1 == 1 이순신 의 2번 취미 탁구 이성계 ccc 2 == 2 이성계 의 0번 취미 수구 , 2 == 2 이성계 의 1번 취미 배구 , 2 == 2 이성계 의 2번 취미 농구 장영실 ddd 3 == 3 장영실 의 0번 취미 축구 , 3 == 3 장영실 의 1번 취미 피구 , 3 == 3 장영실 의 2번 취미 농구 장보고 eee 4 == 4 장보고 의 0번 취미 배구 , 4 == 4 장보고 의 1번 취미 야구 , 4 == 4 장보고 의 2번 취미 족구 |
6) lookup을 활용한 변수 참조
lookup Helper를 활용하면 특정 상대 경로에 있는 변수 값을 참조할 수 있다. lookup Helper로 참조할 수 있는 변수 값은 with 와 as 를 활용하는 방법으로 거의 대부분 참조 가능하다. 개인적으로는 with 와 as 를 활용하는 것이 더 명시적이기 때문에 선호한다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
<!-- 핸들바 템플릿 --> <script id="entry-template" type="text/x-handlebars-template">
<table> <thead> <th>이름</th> <th>아이디</th> <th>취미</th> </thead> <tbody> {{!-- {{#each users}} 는 {{#users}} 로도 대체 가능하다 --}} {{#each users as |user userId|}} <tr> <td>{{name}}</td> <td>{{id}}</td> <td> {{#each hobbys as |hobby hobbyId|}} {{!-- 상대경로를 참조해서 상위 이터레이션의 name 값을 가져오는 것은 with 헬퍼를 사용해서 {{#with ../this.name as |myName|}} 와 같이 처리해줄 수도 있고 아래와 같이 lookup을 활용해서 처리해줄 수도 있다. --}} {{lookup ../this "name"}} 의 {{hobbyId}}번 취미 {{hobby}} {{/each}} </td> </tr> {{/each}} </tbody> </table>
</script> |
Lookup을 통해 상대 경로에 name값을 찾아서 출력해주고 있다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
//핸들바 템플릿 가져오기 var source = $("#entry-template").html();
//핸들바 템플릿 컴파일 var template = Handlebars.compile(source);
//핸들바 템플릿에 바인딩할 데이터 var data = { users: [ { name: "홍길동", id: "aaa", hobbys: ['축구', '야구', '농구'] }, { name: "이순신", id: "bbb", hobbys: ['족구', '피구', '탁구'] }, { name: "이성계", id: "ccc", hobbys: ['수구', '배구', '농구'] }, { name: "장영실", id: "ddd", hobbys: ['축구', '피구', '농구'] }, { name: "장보고", id: "eee", hobbys: ['배구', '야구', '족구'] } ] };
//핸들바 템플릿에 데이터를 바인딩해서 HTML 생성 var html = template(data);
//생성된 HTML을 DOM에 주입 $('body').append(html); |
1 2 3 4 5 6 |
이름 아이디 취미 홍길동 aaa 홍길동 의 0번 취미 축구 홍길동 의 1번 취미 야구 홍길동 의 2번 취미 농구 이순신 bbb 이순신 의 0번 취미 족구 이순신 의 1번 취미 피구 이순신 의 2번 취미 탁구 이성계 ccc 이성계 의 0번 취미 수구 이성계 의 1번 취미 배구 이성계 의 2번 취미 농구 장영실 ddd 장영실 의 0번 취미 축구 장영실 의 1번 취미 피구 장영실 의 2번 취미 농구 장보고 eee 장보고 의 0번 취미 배구 장보고 의 1번 취미 야구 장보고 의 2번 취미 족구 |
2. Handlebar의 장점
precompile할 수 있기 때문에 성능개선에 도움이 된다.
Helper를 자유롭게 추가할 수 있다.
JADE와 같은 문법에 비해 친숙하면서도 가독성이 높다.
Grunt 등으로 빌드 자동화에 precompile 과정을 포함시키기 쉽다.
템플릿에는 로직 코드를 넣지 않는 것이 일반적이다.
컴파일한 템플릿에서 오류가 발생했을 때 디버깅도 어렵다.
실제 문법에서도 간단한 분기문, 배열, 반복문 정도를 지원한다.
참고사이트
깃허브: https://github.com/wycats/handlebars.js/
http://ohgyun.com/427
http://sailboat-d.tistory.com/40
http://tmondev.blog.me/220398995882
http://programmingsummaries.tistory.com/381
https://blog.outsider.ne.kr/939
http://aroundck.tistory.com/957
http://uncle-bae.blogspot.kr/2016/06/handlebars-expression.html
'IT > Handlebar' 카테고리의 다른 글
Handlebar란 (0) | 2017.01.12 |
---|