体感!JavaScriptで超速アプリケーション開発 -Meteor完全解説

第5回Handlebarsの使用法

今回は、Meteorアプリケーションのビューを作成する上で必要とされるHandlebarsについて解説します。

前回お伝えした通り、MeteorのHTML内ではHandlebarsを用いたテンプレートが利用できます。MeteorにおけるHandlebarsは、Meteorと緊密に統合されており、特別なインストール作業などを必要とする事なく利用できます。

式の実行

前回もお伝えしていますが、テンプレート内では{{式}}という構文で式を実行し、結果をテンプレート上に書き出すことができます。たとえば、⁠personName」という変数を参照するための記述は以下のようになります。

{{personName}}さん、こんにちは!

また、ピリオドを用いて入れ子になっている変数を参照することもできます。以下のコードを実行すると、personという変数のnameプロパティを参照します。

{{person.name}}さん、こんにちは!

また、⁠../」というパス指定により、親のスコープ変数を参照することもできます。これはたとえば、すぐ後に説明するwith句とともに用いるなどの利用シーンが考えられます。

{{#with family}}
  {{#with mother}}
    お母さんの名前は{{name}}です。
    <!-- ../により、family変数を参照できる -->
    お父さんの名前は{{../father.name}}です。
  {{/with}}
{{/with}}

上のテンプレートに対応するJavaScriptコードは以下になります。

if (Meteor.isClient) {
    // 式personNameに対応する値
    Template.simpleVar.personName = 'こうたろう';
    // 式personに対応する値
    Template.nestedVar.person = {
        name: 'ちほ'
    };
    // 式familyに対応する値
    Template.parentVar.family = {
        father: { name: 'しゅんぺい' },
        mother: { name: 'たえこ' }
    };
}

実行結果は以下のようになります。

図1 サンプル5-1の実行結果
図1 サンプル5-1の実行結果

サンプルは以下からダウンロードできます。

ブロック構造の使用

Handlebarsには、条件分岐や繰り返しを行うためのブロック構造が組み込みで提供されています。 こうしたブロック構造は、⁠{{#if}} {{/if}}」のように、 ⁠#」で始まり「/」で終了するという形式を取ります。

#withブロック

#withブロックは、引数で渡した変数を現在のコンテキストに設定します。テンプレート内で、入れ子のメンバーが持つ数多くのプロパティにアクセスする場合などに便利です。 たとえば、以下の様なテンプレートがあるとします。dateは日時の情報を格納したオブジェクトです。

現在時刻は
{{currentDate.year}}年
{{currentDate.month}}月
{{currentDate.date}}日
{{currentDate.hours}}時
{{currentDate.minutes}}分
{{currentDate.seconds}}秒
です。

#withブロックを使用すると、以下のようにオブジェクトアクセスを簡略化して記述することができます。

現在時刻は
{{#with currentDate}}
  {{year}}年
  {{month}}月
  {{date}}日
  {{hours}}時
  {{minutes}}分
  {{seconds}}秒
{{/with}}
です。

#withブロックのサンプルは以下からダウンロードできます。

#ifブロック

条件分岐を行うためのブロックです。引数の真偽値に応じて、ブロック内を実行するかどうかが決定されます。

以下のテンプレートは、oddSecondsという式を評価した結果に応じて、実行されるブロックが変わります。また、見ておわかりの通り、間に{{else}}節を含めることができます。

現在時刻は
{{#if oddSeconds}}
  <span style="color: green;">{{dateString}}</span>
{{else}}
  <span>{{dateString}}</span>
{{/if}}
  です。

このテンプレートに対応するJavaScriptファイルは以下のとおりです。テンプレート内で使用されているoddSecondsdateStringといった式の値を返します。

if (Meteor.isClient) {
  // 現在の秒が奇数かどうかを返す
  Template.currentTime.oddSeconds = function() {
      return new Date().getSeconds() % 2 === 1;
  };
  // 現在の日時を文字列にして返す
  Template.currentTime.dateString = function() {
      var date = new Date();
      return date.getFullYear() + '年' + (date.getMonth() + 1) + '月' + date.getDate() + '日 '
          + date.getHours() + '時' + date.getMinutes() + '分' + date.getSeconds() + '秒';
  };
}

#ifブロックのサンプルは以下からダウンロードできます。ちなみに、サンプルは「秒の値が奇数の時は文字を緑にする」というもので、あまり意味はありません。

#unlessブロック

#ifと逆の意味を持つブロックです。上の#ifのサンプルを#unlessで書きなおすと、以下のようになります。サンプルのダウンロードはこちら(sample5-4.zip)

  現在時刻は
  {{#unless oddSeconds}}
    <span>{{dateString}}</span>
  {{else}}
    <span style="color: green;">{{dateString}}</span>
  {{/unless}}
    です。

#eachブロック

繰り返しを行うためのブロックです。配列をループ処理することができます。また、ループ内で「this」を用いると、配列の各要素を参照できます。

たとえば、以下のようなテンプレートとJavaScriptの組み合わせがあった場合、配列要素からなるリストが出力されます。

<h1>フルーツ</h1>
<ul>
  {{#each fruits}}
    <li>{{this}}</li>
  {{/each}}
</ul>


Template.fruitsList.fruits = ['バナナ', 'りんご', 'ぶどう', 'オレンジ'];

#eachブロックのサンプルは以下からダウンロードできます。

ブロックヘルパー関数を自作する

以上のようなブロック構造は、自作することも可能です。自作する際に必要な処理は、⁠ブロックヘルパー関数」⁠以下、ヘルパー関数)と呼ばれます。ヘルパー関数を定義するためには、Handlebars.registerHelper()という関数を使用してHandlebarsへの登録を行います。

たとえばここでは、関数を逆順にループ処理する(#eachの逆バージョン)ためのreverseEachというヘルパー関数を作ってみます。

<!-- 配列を引数に取り、逆順にループ処理する -->
{{#reverseEach fruits}}
  <li>{{this}}
{{/reverseEach}}

registerHelper()の第一引数は、ブロック名を表す文字列、第二引数はヘルパー関数の本体です。ヘルパー関数は、テンプレート中で渡される引数に加えて、最後にoptionsという引数を取ります。

optionsfnという関数プロパティを持ち、これを呼び出すことで、ブロック内部のテンプレートを評価・実行することができます。fnの引数には、ブロック内部でthisを表すオブジェクトを渡します。また関数の戻り値は、テンプレートを実行した結果となるHTML文字列です。

Handlebars.registerHelper('reverseEach', function(items, options) {
    var contents = [];
    // 関数を逆順で処理する
    for (var i = items.length - 1; i >= 0; i--) {
        contents.push(options.fn(items[i]));
    }
    // テンプレート内部を実行した結果を、文字列として返す
    return contents.join('');
});

また、ブロック構造をとらないヘルパー関数を作ることもできます。その場合、最後のoptions引数は使用しません。以下は、渡された文字列を強調するだけのヘルパー関数です。戻り値がHTMLエスケープ不要な文字列の場合は、Handlebars.SafeStringのコンストラクタに文字列を渡したものを返します。

Handlebars.registerHelper('strong', function(content) {
    return new Handlebars.SafeString('<strong>' + content + '</strong>');
});

こうして作成したヘルパー関数は、{{strong 引数}}と記述すると呼び出すことができます。上の#reverseEachブロックと同時に用いることもできます。

{{#reverseEach fruits}}
  <li>{{strong this}}
{{/reverseEach}}

elseブロックを使用する

{{else}}というブロックを用いると、if-elseのような「条件に合致しなかった場合の処理」を記述することが可能になります。ヘルパー関数上では、引数として渡されるoptionsオブジェクトが持つinverseメソッドを呼び出すとelseブロックの内容が実行されます。

以下のサンプルは、引数に渡されたオブジェクトが'りんご'かどうかで実行されるブロックが切り替わるというものです。

// ifAppleというヘルパー関数を作成
Handlebars.registerHelper('ifApple', function(obj, options) {
    var contents = [];
    // りんごの場合はifブロック内を実行
    if (obj == 'りんご') {
        contents.push(options.fn(this));
    }
    // それ以外の場合はelseブロックを実行
    else {
        contents.push(options.inverse(this));
    }
    return contents.join('');
});

上のヘルパー関数を使用している例は以下になります。thisの値が'りんご'だった場合のみ強調されます。

{{#ifApple this}}
  <li>{{strong this}}
  {{else}}
  <li>{{this}}
{{/ifApple}}

Meteor上でのヘルパー関数の作成

MeteorにおけるHandlebarsは、Meteorと緊密に統合されており、ヘルパー関数の定義を、Meteorが持つテンプレートシステムの上で行うことができます。上のヘルパー関数(ifApple)を、Templateに対するメンバー指定で作成した場合は以下のようになります(Templateについては前回の記事を参照してください⁠⁠。

Template.fruitsList.ifApple = function(obj, options) {
  ...
});

ヘルパー関数を自作したサンプルは、以下からダウンロードできます。

まとめ

今回は、Meteorのテンプレートを記述する上で、理解しておく必要があるHandlebarsについて解説しました。次回は、テンプレートに関する残りの話題として、テンプレート内にイベントハンドラを登録する仕組みや、テンプレートに関するAPIを紹介します。

おすすめ記事

記事・ニュース一覧