yutaponのブログ

javascript界隈の興味あるネタを備忘録的に残しておく場所

Backbone.js+RequireJSでテンプレートエンジンにHandlebars.jsを使う

前回さらっとHandlebars.jsを使っていたんですが、ふわっとしていたのでもう少し詳しく説明します。
前回の記事: Backbone.js+D3.jsでデータの可視化【準備編】 - yutaponのブログ
Handlebars.js: Handlebars.js: Minimal Templating on Steroids


RequireJSでHandlebars.jsを使うプラグインとして
require-handlebars-pluginというのがあるので、こちらを使っていきます。

SlexAxton/require-handlebars-plugin · GitHub


require-handlebars-plugin

まずはプラグインを入れましょう。
既にBackbone.jsとRequireJSを使っていることを前提として進めます。

私はbowerを使って入れました。

$ bower install --save require-handlebars-plugin

.bowerrcの設定で、public/bower_components 以下にインストールされました。


プラグインがインストールされたディレクトリを見てみるとこうなってます。

public/bower_components/require-handlebars-plugin/
├── README.md
├── build.sh
├── demo
├── demo-build.html
├── demo.html
├── hbs    # この下に依存モジュールが入っている
│   ├── handlebars.js
│   ├── handlebars.runtime.js
│   ├── i18nprecompile.js
│   ├── json2.js
│   └── underscore.js
├── hbs.js # プラグイン本体
├── karma.conf.js
├── package.json
├── r.js
└── tests

 

プラグインを有効にするためRequireJSのconfigにいくつか追記します。

requirejs.config({

    paths : {
        // pathsに追記
        hbs : '/bower_components/require-handlebars-plugin/hbs',
    },

    // require-handlebars-plugin固有の設定を追加
    hbs : {
        helperPathCallback : function (name) {
            // ('/templates/helpers/'+name by default)

            return 'core/helpers/' + name;
        },
    }

 
このプラグインを "hbs" というモジュールIDで参照できるようにしています。
また、helperPathCallbackでHandlebarsのヘルパーを参照するディレクトリを変更しました。


Handlebars.jsの使い方

実際の使い方はこんな感じです。
d3view.jsから一部抜粋しました。

// @file d3.js <modules/index/view>

define([
    'backbone',
    'd3',
    'hbs!modules/index/hbs/index'  // テンプレートファイル
], function (
    Backbone,
    d3,
    hbsIndex
) {
    'use strict';

    var D3View = Backbone.View.extend({
        className : 'd3-main',

        render : function render() {
            this.$el.html(hbsIndex());  // hbsIndex()を呼ぶとコンパイルされたDOMが返る
            return this;
        },

// 以下略

 
hbs! に続けてテンプレートファイルへのパスを書けばいい訳です。
ビックリマークが付くとRequireJSのプラグインを使うって意味になります。


modules/index/hbs/index.hbsの中身はこうなってます。
※テンプレートは.hbsという拡張子で保存します。(設定で変更できます)

<section class="d3-svgWrapper"></section>

 

これだと味気ないですね。
テンプレートファイルに値を渡すにはこうします。

// この部分を
this.$el.html(hbsIndex());

// こうする
this.$el.html(hbsIndex({
    hoge: 'fuga',
    items: [1, 2, 3]
}));

 
テンプレート側ではこんな感じで反映できます。

<p>{{hoge}}</p>
<ul>
    {{#each items}}
    <li>{{@index}}: {{this}}</li>
    {{/each}}
</ul>


出力されるHTMLはこちら。

<p>fuga</p>
<ul>
    <li>0: 1</li>
    <li>1: 2</li>
    <li>2: 3</li>
</ul></div>


ちなみに、

{{ a }}    エスケープされる
{{{ b }}}  エスケープされない

という風になってます。


ヘルパーの使い方

Handlebars.jsが最初から用意している機能だけでは限界がくるので
ヘルパーを作成しましょう。

ここが今回詰まっていろいろ調べた部分でもあります。


まずrequire-handlebars-pluginでは特定のディレクトリ以下にヘルパーファイルを置く必要があります。
ヘルパーを使ったタイミングでそのディレクトリ以下から取得しているようです。

RequireJSのconfigでも記述があるように、デフォルトでは
/templates/helpers/ というディレクトリにヘルパーを置かなければなりません。
それだといろいろ都合が悪いので、core/helpers/ 以下からヘルパーを
探すように指定しました。


公式にも載っている、小数点のある数値を四捨五入するヘルパーを作ってみます。

まずテンプレ側はこうなります。

{{ roundNumber number }}

 
roundNumberというヘルパー名で、numberという変数には数値が入っていることを期待しています。

ヘルパーが使われると、require-handlebars-pluginでは
ヘルパー名と同じjsファイルを core/helpers/ から探します。
つまり、ヘルパーの数だけ core/helpers/ 以下には対応するjsファイルが必要(?)そうです。


core/helpers/roundNumber.js の内容はこちら。

// @file roundNumber.js <core/helpers>

define(['hbs/handlebars'], function (Handlebars) {

    function roundNumber ( context, options ) {
        // Simple function for example
        return Math.round( context );
    }

    Handlebars.registerHelper( 'roundNumber', roundNumber );
    return roundNumber;
});

 
Handlebarsの参照の仕方でかなり悩みましたが、これで動きました。
この記事のおかげ。
lots of 'unnormalized' template files failing to load · Issue #70 · SlexAxton/require-handlebars-plugin · GitHub


このヘルパーを使ってみると、

this.$el.html(hbsIndex({
    number: 1234.5678
}));

// 1235が出力された

ちゃんと動いているようです。


おわりに

Handlebars.jsはテンプレ側で提供する機能を最小限に抑えて
Viewとロジックを分けようという気にさせる。

テンプレ側でifとかeachとか使いすぎると出力されるHTMLを
追うのが難しくなるのでやりすぎは良くない。

そろそろr.jsもやりたいところ。