[handlebars] Block Helper

from : http://handlebarsjs.com/block_helpers.html

Block Helper는 커스텀 iterator를 정의하고 다른 기능을 수행할 수 있도록 해준다. 그리고 새로운 컨텍스트함께 전달된 블록을 호출할 수 있다.

Basic Blocks

보여주기를 목적으로 블록 헬퍼를 정의해보다. 이는 블록을 호출하며 이는 헬퍼가 존재하지 않는다. 
<div class="entry">
    <h1>{{title}}</h1>
    <div class="body">
        {{#noop}}{{body}}{{/noop}}
    </div>
</div>

noop 헬퍼 (줄임말로 no operation이다.) 옵션 해시를 받는다. 이 옵션 해시는 (options.fn)함수를 가지고 있으며 이는 일반적인 컴파일된 Handlebars template와 같은 행동을 한다. 특히 이 함수는 컨텍스트를 받고 스트링을 반환한다. 

Handlebars.registerHelper('noop', function(options) {
    return options.fn(this);
});
Handlebars는 항상 this와 같은 현재 컨텍스트 포함하는 헬퍼를 호출한다. 그래서 this를 블록에서 호출할 수 있다. 이는 현재 컨텍스트를 의미한다. 

어떤 헬퍼들은 이러한 방법으로 컨텍스트에 전의된 전체 필드들을 획득할 수 있다. 필드에 접근하는 것은 helper에 의해서 씌여진다. 이때 패스 레퍼런스가 사용될 것이다. 상단 예제어서 noop 는 컨텍스내의 필드이며 다음과 같이 레퍼런싱 된다. 
{{./noop}}

Basic block Variation

더욱 낳은 문법을 살펴보기 위해서 다른 블록 헬퍼 정의를 살펴보자. 이는 몇가지 마크업으로 둘러쌓인 텍스트를 추가하는 것이다. 
<div class="entry">
    <h1>{{title}}</h1>
    <div class="body">
        {{#bold}}{{body}}{{/bold}}
    </div>
</div>

bold헬퍼는 텍스트를 볼드로 만들어주는 마크업을 추가할 것이다. 이때 함수는 컨텍스트를 받아 들이고 스트링을 반환한다. 
Handlebars.registerHelper('bold', function(options) {
    return new Handlebars.SafeString(
        '<div class="mybole">'
        + options.fn(this)
        + '</div>');
});

The with helper

with 헬퍼는 헬퍼에게 파라미터를 어떻게 전달하는지 보여준다. 헬퍼가 호출될때 파라미터를 전달하기 위해서는 context와 함께 전달한다. 
<div class="entry">
    <h1>{{title}}</h1>
    {{#with story}}
        <div class="intro">{{{intro}}}</div>
        <div class="body">{{{body}}}</div>
    {{/with}}
</div>
아마도 JSON객체 섹션에서 깊게 중첩된 프로퍼티 부분이 있고 반복적인 부모 이름을 피하여 사용할 것이다. 
{
    title: "First Post",
    story: {
        intro: "Before the jump"
        body: "After the jump"
    }
}
이러한 헬퍼의 구현은 noop헬퍼의 구현과 매우 많이 닮았다. 헬퍼들은 파라미터를 받을 수 있다. 그리고 파라미터들은 내부적으로 {{mustache}}블록을 통해서 직접적으로 사용할 수 있다. 
handlebars.registerHelper('with', function(context, options) {
    return options.fn(context);
});
파라미터들은 헬퍼에 전달되며 기술한 순서대로 전달된다. 

Simple Iterators

block 헬퍼의 공통적인 유즈케이스는 커스텀 iterator를 정의하는 것이다. 사실 모든 핸들바스의 내장 헬퍼들은 일반적인 핸들바스 블록 헬퍼로 정의 되어 있다. each 헬퍼가 어떻게 동작하는지 보자. 

<div class="entry">
    <h1>{{title}}</h1>
    {{#with story}}
        <div class="intro">{{{intro}}}</div>
        <div class="body">{{{body}}}</div>
    {{/with}}
</div>
<div class="comments">
    {{#each comments}}
        <div class="comment">
            <h2>{{subject}}</h2>
            {{{body}}}
        </div>
    {{/each}}
</div>
이 케이스에서 우리는 comments의 배열의 각 엘리먼트가 each에 전달된 블록을 호출하기를 원할 것이다. 

Handlebars.registerhelper('each', function(context, options) {
    var ret = '';
    for (var i=0, j=context.length; i<j; i++) {
        ret = ret + options.fn(context[i]);
    }
    return ret;
});
이 예제에서 우리는 전달된 파라미터를 iterate를 통해서 접근하며 각 아이템들을 한번씩 호출한다. 우리는 스트링을 만들고 그것을 반환한다. 

이 패턴은 더 향상된 iterator를 구현을 사용할 수 있도록 한다. 예를 들어 <ul>로 래핑된 내용을 만들고, 내부의 각 결과는 <li>로 래핑하고자 할수도 있다. 
{{#list nav}}
    <a href="{{url}}">{{title}}</a>
{{/list}}
컨텍스트는 다음과 같다. 
{
    nav: [
        {url: "http://www.yehudakatz.com", title: "Katz Got Your Tongue" },
        {url: "http://www.sproutcore.com/block", title: "SproutCore Blog" },
    ]
}
헬퍼는 원래 each헬퍼와 유사하다. 

Handlebars.registerHelper('list', function(context, options) {
    var ret = "<ul>";
    for (var i=0, j=context.length; i<j; i++) {
        ret = ret + "<li>" + options.fn(context[i]) + "</li>";
    }
    return ret + "</ul>";
});
라이브러리는 underscore.js 혹은 SproutCore의 런타임 라이브러리와 같이 이용하며 이는 쫌 이쁘게 만들어 준다. 예를 들어 여기에는 SproutCore의 런타임 라이브러리와 같다. 

Handlebars.registerHelper('list', function(context, options) {
    return "<ul>" + context.map(function(function(item) {
        return "<li>" + options.fn(item) + "</li>";
    }).join("\n") + "</ul>";
});

Conditionals

다른 일반적인 유즈케이스는 조건문을 수행하는 것이다.  iterator 과 같이 핸들바스는 내장된 if와 unless 컨트롤 구조를 가지고 있으며 이는 일반적인 핸들바스 헬퍼이다. 

{{#if isActive}}
    <img src="start.gif" alt="Active">
{{/if}}
컨트롤 구조는 일반적으로 현재 컨텍스트를 변경하지 않는다. 대신에 이는 몇가지 변수에 대해서 어떠한 블록을 수행해야할지를 결정하게 된다. 

Handlebars.registerHelper('if', function(conditional, options) {
    if (conditional) {
        return options.fn(this);
    }
});
조건을 작성할때 조건에 만족하지 않을때 다른 구문을 실행하고자 할 수 있다. 이럴때는 else 블록 헬퍼를 이용할 수 있다. 

{{#if isActive}}
    <img src="star.gif" alt="Active">
{{else}}
    <img src="cry.gif" alt="Inactive">
{{/if}}
Handlebars는 options.inverse와 같은 else를 위한 프라그먼트 블록을 제공한다. else 프라그먼트의 존재여부를 위한 체크를 할 필요가 없다. Handlebars는 자동적으로 찾아낼 것이며, "noop" 함수를 등록한다.

Handlebars.registerHandler('if', function(conditional, options) {
    if (conditional) {
        return options.fn(this);
    } else {
        return options.inverse(this);
    }
});
Handlebars는 추가적인 메타데이터를 제공하여 블록헬퍼에게 전달할 옵션 헬퍼 프로퍼티를 추가할 수 있도록 해준다.

조건은 else mustache에 추가적으로 검사를 진행 할 수 있다.
{{#if isActive}}
    <img src="star.gif" alt="Active">
{{else if isInactive}}
    <img src="cry.gif" alt="Inactive">
{{/if}}
이후 처리를 위한 헬퍼와 동일하게 unless 헬퍼는 else위치에 사용할 수 있다. 헬퍼 값들이 다른경우 닫는 mustache는 여는 헬퍼의 이름과 매치 시켜주면 된다.

Hash Arguments

보통의 헬퍼와 같이 블록 헬퍼는 옵셔널 해시를 final 아규먼트로 전달 할 수 있다. 이전의 list헬퍼를 살펴보고 다른 선택적인 속성을 추가해보나. 이는 <ul>엘러먼트가 생성될 것이다.

{{#list nav id="nav-bar" class="top"}}
    <a href="{{url}}">{{title}}</a>
{{/list}}
handlebars final hash로 options.has형태로 제공한다. 이것은 다양한 개수의 파라미터를 수용할 수 있도록 해준다. 이는 또한 optional해시를 받아들일 수 있다. 만약 템플릿이 hash 아규먼트를 제공하지 않는다면 Handlebars는 자동적으로 빈 객체를 전달하며 ( {} )으로 전달한다.  그러므로 해시 아규먼트가 존재하는지 여부를 검사할 필요가 없다.

Handlebars.registerHelper('list', function(context, options) {
    var attrs = Em.keys(options.hash).map(function(key) {
        return key + '="' + options.hash[key] + '"';
    }).join(" ");
    return "<ul " + attrs + ">" + context.map(function(item) {
        return "<li>" + options.fn(item) + "</li>";
    }).join("\n") + "</ul>";
});
Hash 아규먼트들은 강력한 초가적인 파라미터들을 제공할 방법을 가지고 있다. 이는 위치 멪가 아규먼트를 제공해야하는 복잡함 없이 제공한다.

Block 헬퍼들은 또한 자식 템플릿에 private 변수를 인젝트 할 수 있다. 이것은 원래 컨텍스트에 들어있지 않는 데이터와 같은 추가적인 정보를 추가하기 위한 매우 유용한 방법이다.

예를 들어 list에 작업을 할때현재 엔덱스의 값을 제공할 수 있다.
{{#list array}}
    {{@index}}. {{title}}
{{/list}}
Handlebars.registerHelper('list', function(context, options) {
    var out = "<ul>", data;
    if(options.data) {
        data = Handlebars.createFrame(options.data);
    }
    for(var i=0; i<context.length; i++) {
        if(data) {
            data.index = i;
        }
        out += "<li>" + options.fn(context[i], { data: data }) + "</li>";
    }
    out += "</ul>";
    return out;
});
private변수는 data옵션을 이용하여 제공된다. 이것은 모든 파생된 스콥에서 지정이 가능하다.

부모 스콥에서 정의된 Private변수는 path된 쿼리를 통해 접근되어진다. index필드를 접근하기 위해서 @../index가 사용되어진다.

각 헬퍼에서 새로운 데이터 프레임을 생성하는 것을 보장하는 것은 자신의 데이터에 assign하는 것이다. 그렇지 않으면 다운스트림 헬퍼는 의도하지 않은 업스트림 변수를 변경할 것이다.

또한 데이터 필드는 이전에 존재하는 데이터 객체와 상호작용 하기 위해서 추가된 것임을 보장한다. private 변수의 행동은 조건적으로 컴파일 되고 몇몇 템플릿들은 이 필드로 생성되지 않는다.

Block Parameters

Handlebars 3.0에서는 제공하고 있는 헬퍼로 부터 이름있는 파라미터를 수신받을 수 있다.
{{#each users as | user userId}}
    Id : {{userId}} Name: {{user.name}}
{{/each}}
이 예제에서 user는 현재 컨텍스트와 같은 동일한 값을 가진다. 그리고 userId는 iteration을 위한 인덱스 변수를 가진다.

이는 중첩된 헬퍼를 제공하여 이름 충돌을 회피한다. 이는 private변수들에 발생할 수 있다.
{{#each users as |user userId}}
    {{#each user.book as |book bookId}}
        User Id: {{userId}} Book Id: {{bookId}}
    {{/each}}
{{/each}}
내장 헬퍼의 넘버는 블록 파라미터들을 젝오하고 다른 커스텀 헬퍼는 blockParams옵션 필드를 통해서 제공된다.
Handlebars.registerHelper('block-params', function() {
    var args = [],
        options = arguments[arguments.length - 1];
    for(var i=0; i < argument.length - 1; i++) {
        args.push(arguments[i]);
    }
    return options.fn(this, {data: options.data, blockParams: args});
});
{{#block-params 1 2 3 as |foo bar baz|}}
    {{foo}} {{bar}} {{baz}}
{{/block-params}}
 헬퍼의 구현은 주어진 블록의 이름지어진 변수의 선언을 허용한다. 이 예제는 출력으로 1, 2, 3을 렌더시에 수행한다.

헬퍼들은 블록 파라미터들의 number를 결정할 수 있다. 이는 options.fn.blockParams 필드 템플릿을 통해서 가능하며 이는 정수 카운터이다. 이 값은 블록 파라미터의 수를 표현하며 자식 템플릿에 의해서 참조될 수 있다. 이 카운트 뒤에 있는 파라미터들은 절대 참조되지 않는다. 그리고 필요한경우 헬퍼에 의해서 안전하게 제거된다. 이것은 선택적이고 다른 추가적인 파라미터들이며 이는 템플릿에 전달된 파라미터들로 조용히 무시된다.

Raw Blocks

Raw 블록들은 처리되지 않는 mustache블록들을 처리하기 위해 필요한 템플릿으로 이용가능하다.
{{{{raw-helper}}}}
    {{bar}}
{{{{/raw-helper}}}}
이것은 컨텐츠의 인터프리터를 수행하지 않고 raw-helper 헬퍼를 실행할 것이다.
Handlebars.registerHelper('raw-helper', function(options) {
    return options.fn();
});
이는 다음과 같이 렌더링된다.
{{bar}}

Share this

Related Posts

Previous
Next Post »