前回はEmber.jsの概要を解説しました。Ember.jsの歴史から今後の展望、そして動作環境を構築して画面の表示ができることまでを示しました。
今回からは簡単なアプリケーションを作成しながら、Ember.jsの構成要素について一つひとつ見ていくことにしましょう。
今回のテーマはRoutingとTemplatesです。
なぜこの2つから解説を始めるかというと、RoutingとTemplatesはEmber.jsアプリケーションの入口と出口に相当するものだからです。
今回のゴールは次のようなEmber.jsアプリケーションの仕組みを理解することです。
前準備
さて、さっそくアプリケーションの作成……といきたいところですが、その前に少しだけ前準備をしておきましょう。
前回からのアップデート
前回の記事からのEmber.jsのアップデートについて簡単に解説します。2014年12月8日に新しいバージョンである1.9.0がリリースされました[1]。Ember.js 1.9.0では、依存するHandlebarsのバージョンが1.3.0から2.0.0に変更されました。そこで、あらためて環境の準備から始めます[2]。
ファイルの準備
まずはEmber.jsを使うために必要なライブラリをダウンロードします。
HTMLとJavaScriptファイルを作成します。
次のディレクトリ構成で配置します。
開発ツールのインストール
Ember.jsのデバッグツールであるEmber Inspectorをインストールします。
この拡張を入れると、ブラウザの開発者ツールに"Ember"タブが増えます。
このタブでは、Ember.jsアプリケーションのデバッグやパフォーマンスチェックが行えるようになります。詳しい使い方は、今後の連載の中で少しづつ解説していく予定です。
さて、ここまでで前準備が完了しました。
Templates
では、さっそくTemplatesから見ていきましょう。
Ember.jsでは、アプリケーションで表示する画面はHandlebarsと呼ばれるテンプレート言語を用いて記述します[3]。HandlebarsはHTML中に式を埋め込む形式のテンプレート言語です。
ただ、JavaScriptの任意の式を記述することはできず、Handlebarsで定義されたヘルパーと描画時に与えたオブジェクトのプロパティのみを記述できます。一見すると不便に感じるかもしれませんが、記述内容を制約することでテンプレートとプレゼンテーションロジックの分離が強制されます。Ember.jsはこの特性を利用して、「埋め込まれた値が変化したときに自動で追随する」という仕組みを提供しています。
ではHandlebarsの記法を見ていくことにしましょう。
先ほど作成したindex.html
のbody
タグの中に次のタグを追加してください。
また、このテンプレートに表示するためのオブジェクトを用意しましょう。app.js
に次の内容を追記してください。
これは、変数post
が参照するオブジェクトをテンプレートに渡すためのコードです(詳細は次項で解説します)。
さて、動かしてみると次のような表示になっているはずです。
とくにCSSを入れていないのでとても簡素な見た目ですが、ここにはHandlebarsの基本となる要素が含まれています。では順に説明していきます。
<script type="text/x-handlebars" data-template-name="index">
script
タグにtype="text/x-handlebars"
を指定することで、Ember.jsで使うためのテンプレートであることをEmber.jsに知らせています。また、data-template-name
に指定した値がテンプレートの名前になります[4]。Ember.jsがテンプレートを探索する際はこの名前を利用します。
<h1>{{title}}</h1>
{{
から}}
で囲まれた部分にはオブジェクトのプロパティやHandlebarsのヘルパーを埋め込むことができます。ここではオブジェクトのプロパティであるtitle
を埋め込んで表示しています。
{{#each tag in tags}}
...{{/each}}
値が配列の場合、each
ヘルパーを使うと要素をひとつひとつ取り出せます。この#
と/
で範囲を指定するヘルパーをHandlebarsの用語では"Block Expressions"と呼びます。
{{!これはコメントです}}
{{!
~}}
の範囲はコメントです。HTMLには描画されません。
{{author.name}}
オブジェクトがネストしている場合、.
で深い階層の値を参照できます。
<div {{bind-attr class=status}}>
HTMLの属性を埋め込みたい場合はbind-attr
ヘルパーを使います。下の例のように属性に{{
~}}
を埋め込んだ場合、エラーになってしまい動作しません。
これはEmber.jsが提供するHandlebarsでの値の自動更新の実装上の制約です。
ただ、Ember.js 1.10.0から導入予定のHTMLBarsではこの制約はありませんので、上の例のように属性を直接埋め込んでも動作します。
{{#if author.twitter}}
値の真偽で出力を切り替えられます。通常のJavaScriptと同じく0
、''
(空文字)が偽として評価されるほか、空配列も偽として評価されます。if
の他に、値が偽のときに評価されるunless
もあります。そしてそのどちらもelse
が指定可能です。
次の2つの例は同じように評価されます。
else
は範囲を取らない({{#else}}
~{{/else}}
とならない)ことに注意してください。
{{input value=body}}
input
タグを生成するヘルパーです。
value=body
とすることで、input
タグのvalue
属性とオブジェクトのbody
プロパティを結びつけています。これによって、ユーザがテキストフィールドに値を入力することで画面に表示されているbody
も追従して変更されます。このような仕組みを「データバインディング」と呼びます。Ember.jsはデータバインディングを全面的にサポートすることで、アプリケーションから決まりきった画面更新のロジックを排しプログラマの負担を軽減してくれます。
以上、Templatesの簡単な説明でした。
Routing
次は Routingを見ていきます。
RoutingはURLとアプリケーションの画面を対応付ける機能です。
例えば、「記事の編集ページを開いている」「設定のタブを開いている」といった状態をURLで表現することで、JavaScriptで複雑な状態管理をしなくてはいけない状況からプログラマを解放してくれます。
もちろんこれはユーザにとってもメリットがあります。アプリケーションの状態をURLで表現できることは、SPAであっても任意の画面をブックマークできることになります。
RESTfulなWebアプリケーションを設計する際と同様、Ember.jsでSPAを設計する際にはURLを意識するのがよいプラクティスと言えます。
Ember.jsはデフォルトではURLを#
からはじまるフラグメントハッシュの形で提供します[5]。Webサーバが返したパスを起点にして、#/
以下にEmber.jsアプリケーションのパスを構築します。
RoutingにはRouter
とRoute
という2つの部品が登場します。それぞれについて、サンプルコードを用いて解説します。
ここではapp.js
とindex.html
を準備したばかりの状態から始めることにします。
テンプレートはこちらです。index.html
のbody
タグの中に記述してください。
では、これらについて順番に説明していきます。
Router
Router
は今ブラウザで表示しようとしているURLについて、対応するRouteを探し出す役割を担います。Router.map()
メソッドの中でthis.route(route名, オプション)
の形式でRoute
とURLの対応づけを行います。
この例では、WelcomeRoute
を/
というURLに対応付けています[6]。route名とは、Routeのクラス名からRoute
を削って小文字にしたものです。今回の例だと、WelcomeRoute
に対応するroute名はwelcome
です。(※7)。
もしURLとroute名が同じであれば、path
の指定を省略できます。
この記述だと、#/welcome
というURLでアクセスされた場合WelcomeRoute
が有効になります。
また、Routerはデフォルトで#/
とIndexRoute
の対応を持っています。したがって{path: '/'}
を省略した場合、IndexRoute
を自分で定義することで#/
の画面に表示するオブジェクトを用意できます(Templatesの例で取り上げたサンプルがまさにこれです)。
Route
Router
によって処理を引き渡されたRoute
は、自身に関連付けられたモデルオブジェクトとテンプレートを使って画面を描画します[8]。
model
メソッドの戻り値がこのRouteに対応するモデルオブジェクトになります。Route
はモデルオブジェクトをテンプレートに渡して画面を描画します。このとき利用されるテンプレートはroute名と同じものが使われます。この例の場合は、data-template-name="welcome"
のテンプレートが使われます。
通常だとモデルオブジェクトはサーバサイドのAPIを呼び出して取得したデータを利用することが多いのですが、この例ではサンプルとして固定のシンプルなオブジェクトを返しています。
デバッグオプション
さて、実際にアプリケーションを作成する際には画面の数だけRoute
を作成することになります。そうすると、どんなRoute
が定義されていて今どのRoute
が有効になっているのか知りたくなることでしょう。
そんなときのために、Ember.jsにはRoutingをデバッグするためのオプションが用意されています。
app.js
の以下のように書き換えてください:
これらのデバッグオプションを有効にしておくことで、「なんだかわからないけど画面に何も表示されない」といった場合に調査のヒントにできます。
これで、開発者ツールのコンソールに次のようなメッセージが表示されるようになります。
テンプレート
Route
のmodel
メソッドで返したオブジェクトを表示します。これは先ほどTemplatesの項で解説した通りです。
Route間の遷移
さて、ここまでだとRoutingのありがたみをあまり感じません。なぜならアプリケーションに対して何も操作を行っていないからです。実際のアプリケーションであれば、「ボタンをクリックして設定画面を開く」とか「リンクを押して詳細を表示する」といった操作を行うでしょう。
そして、画面遷移は有効になっているRouteが切り替わることで実現されます。では複数のRouteを用意してEmber.jsでの画面遷移を体験してみましょう。
まずはRouterとRouteを用意します。
次はテンプレートを用意します。
完成イメージは次の通りです。
URLが更新される様子を確認するために、別のタブで開いてみてください。
記事のタイトルをクリックして詳細を表示すると、URLにid
が含まれていることが確認できます。これで、画面を更新しても同じ画面を表示できます。
これは非常に簡単な例ですが、「SPAでの画面にURLを割り当てることができる」ことの雰囲気を感じてもらえたでしょうか?
新登場のヘルパー
では、動作について確認できたところで新しくでてきたヘルパーを紹介します。
link-to
link-to
はaタグを出力するヘルパーです。
これを使うと与えられたオブジェクトから生成したURLがhref
に設定され、Route間の遷移ができるようになります。
link-to
の引数は次のようになっています。
- (テキスト)
生成されるタグのテキストです。
と書くこともできるので、テキスト部分にHTMLを含めたい場合こちらを使う方が記述しやすいです。
- (route名)
this.route()
で指定したRouteの名前です。this.route(name, {path: path})
の形でRouteを定義している場合はname
の部分です。
- (オブジェクト)
テンプレート中でのmodel
は、Routeのmodel
メソッドで生成されたオブジェクトを指します。link-to
にオブジェクトを渡すと、this.route()
で定義したpath
にしたがってURLが生成されます。this.route('post', {path: '/post/:id'})
の場合、model.id
がURL生成に利用されます。
- (オプション)
ここにはlink-to
が生成するHTMLのためのオプションを渡すことができます(省略可能)。指定可能なオプション一覧はAPIリファレンスの"PROPERTIES"を参照してください。
例えば、以下のような使い方ができます。
アプリケーションのレイアウト
いくつもの画面を作成していった場合、「ヘッダやサイドバーが共通しているのでどこかにまとめておきたい」というケースが想定されます。そんなときはアプリケーション全体で共通するレイアウトを定義できます。
ここでのポイントは2つあります。
data-template-name
を指定していない
data-template-name
を省略すると、アプリケーションでの共通レイアウトとして設定されます[9]。
{{outlet}}
ヘルパーを使っている
画面遷移を行うと、共通のレイアウト中の{{outlet}}
に各Routeで描画されたテンプレートが差し込まれます。
この共通テンプレートを使うことで、各Route
で使われるテンプレートをシンプルに保つことができます。
以上でRoute間の遷移についての解説は終わりです。
まとめ
今回はEmber.jsアプリケーションを作るための基礎の部分であるRoutingとTemplatesを解説しました。
次回はもう少し実践的な例を用いて、この2つを深く解説します。