前回は画面遷移について解説しました。 今回はデータを画面に表示するところから一歩進んで、 ユーザのインタラクションを受け取って画面を更新する方法を解説します。
今回は次のEmber.jsアプリケーションの仕組みを理解することを目標にします。
『記事が長い場合は途中から省略して「もっと読む」をクリックすると続きが表示される』というアプリケーションです。
前準備
本稿の対象バージョンはこちらです。
前回の記事と本稿執筆時点でのEmber.jsの最新バージョンは同じです。 前回の記事を参考にして必要なファイルを作成してください。
ファイルの作成が完了したら、次は今回の解説の基礎となるルーティングとテンプレートを準備します。
さて、ここまでで記事が画面が表示されるようになりました。
ではここから、記事が長い場合に省略して表示するようにしましょう。
Controller
記事の詳細画面では「記事の全文を表示しているか、それとも省略して表示されているか」というフラグをどこかに持っておく必要があります。 そこで今回解説するのがControllerです[1]。コントローラは、URLともmodelとも関係のない一時的なデータを保持できます。
これだけではイメージしづらいので、コントローラについて詳しく見ていきましょう。
Route
が画面を描画する際、model
と一緒にコントローラも準備されます。 そしてコントローラのmodel
プロパティにRoute
で解決されたmodel
が設定され、 その状態でコントローラがテンプレートに描画されます。 テンプレートのコンテキストは、実はこのコントローラだったのです。
テンプレートではコントローラのプロパティにアクセスできるため、モデルにアクセスしたい場合はmodel
をつけてプロパティを参照していました。
つまり、コントローラ自身にプロパティを設定することで、そのプロパティをテンプレートから参照できることになります[2]。
さっそく次のコードを追記して確認してみましょう。
コントローラのプロパティであるpageTitle
が画面に表示されていますね。
ちなみに、Route名とコントローラ名の対応はEmber Inspectorで確認できます。 また、Route名に対応しないコントローラを利用したい場合、Route
のcontrollerName
を設定することで変更可能です。
この例の場合、App.SomeRoute
のコントローラとしてApp.OtherController
が設定されます。
Handlebars Helpers
では「記事を省略して表示をするかどうか」というフラグをコントローラにもたせて、「もっと見る」というリンクをクリックした際にその情報を更新することにします。
次のコントローラを作成してください。
テンプレートを書き換えてください。
{{truncate model.body length=20}}
の部分は独自に定義したヘルパーを利用しています。 そのため、その定義を記述しておきましょう。
ヘルパーの定義
Ember.Handlebars.helper
で定義したヘルパーはテンプレートで利用できます。 ヘルパーの書式は次の通りです。
- ヘルパー名
Ember.Handlebars.helper
でヘルパーを定義する際に指定したヘルパー名です。
- 引数
ヘルパー関数の引数です。
- オプション
key=value
の形で指定します、必要に応じていくつでも設定できます。ヘルパー関数では、一番最後の引数のhash
プロパティから取得できます。
さて、ここまでで記事の本文が一定文字数を超える場合は「…」で表示されるようになりました。 次は、「もっと見る」リンクを作成してみましょう。
Actions
ユーザからのイベントを受け取るために、コントローラに任意のイベントハンドラを定義することができます。 この仕組みをEmber.jsではアクションと呼びます。
以下のコントローラとテンプレートを書き換えてください。
テンプレートで{{action "expand"}}
と記述している点がポイントです。 このaction
ヘルパーは、ユーザがDOMをクリックした際Controller
に対して発火するアクション名を指定します。 アクションがController
でハンドルされない場合はRoute
に発火します。
ちなみに、きっかけとなるDOMのイベントを変更したい場合はon
オプションを利用し、アクションが発火するオブジェクトを変更したい場合はtarget
オプションを利用します。
ここまでで、「もっと見る」が機能するようになりました。 しかしながら、一度「もっと見る」をクリックすると、他の記事を表示しても全文が表示されるようになってしまいます。
なぜこのような動きをするかというと、Controllerは一度生成されるとそれ以降は紐づくmodelが差し替わる形で再利用されるからです。 画面遷移しても状態を記憶しておきたい場合は便利ですが、今回の例では画面遷移で情報は初期化することにしましょう。
コントローラの初期化
画面を遷移する際、RouteがControllerとmodelを紐付ける部分を差し替えたり独自処理を付け加えることができます。 その際、利用できるのがRoute
のsetupController
メソッドです。
setupController
のデフォルトの実装は次のようになっています。
このsetupController
を上書きすることで、独自処理を行います。
では、Routeを次の内容で書き換えてください。
ポイントはsetupController
メソッドを定義して、その中でcontroller.set('isExpanded', false);
としてコントローラのプロパティを初期化している部分です。
これにより、画面を切り替えるたびに記事の本文が省略して表示されるようになりました。
さて、今は記事の本文だけを省略して表示しましたが、もし他の画面でも省略して表示したい項目が出てきた場合はどうすればよいでしょうか? それぞれの画面を担当するコントローラとテンプレートに同じような処理を実装していくことはできそうですが、同じような処理を何度も書くのはできれば避けたいところです。
そんなときは、コントローラでの状態管理をやめて「省略して表示する機能を持った部品」を用意することにしましょう。 これを実現するためにEmber.jsにはComponentと呼ばれる機能があります。
Component
Componentは画面上のひとまとまりの部品をカプセル化したものです。
例えば、次のようなComponentを定義した場合を考えてみましょう。
ポイントは次のとおりです。
- クラスとテンプレートをセットで定義します。
Component名は、クラス名からComponent
を削除し、単語の区切りを-
(ハイフン)で表現したものになります。
テンプレート名はcomponents/
+Component名です。
HelloWorldComponent |
hello-world |
components/hello-world |
また、このテンプレートのコンテキストはComponent自身です。
Componentを使う際はテンプレートの任意の場所でComponent名を記述します。
Componentを使う際、必要に応じてComponentのプロパティを設定できます。
描画されたHTMLはdiv
タグで囲まれます。
他のタグに変更する場合、tagName
プロパティを上書きします。
また、タグを生成したくない場合はtagName
に空文字を与えます。
Component名は必ず2単語以上で-
(ハイフン)を含む必要があります。
これは、将来的にはWeb Componentsとの統合が視野に入れられているためです(Web Componentsはタグ名に-
(ハイフン)を含む必要があります)。
Ember.js 1.11.0からは、Componentを利用する際は{{hello-world}}
ではなく、<hello-world>
と記述できるようになる予定です(参照:The Road to Ember 2.0 RFC #15)。
テンプレートのみで十分な場合、クラス定義は省略可能です。
今回はこのComponentを使って、テキストを省略して表示する機能をもったパーツを作成してみましょう。
ComponentはControllerと同様、アクションを定義できます。
次はComponentのテンプレートを作成します。
これでComponentを用意できました。 では、このComponentを実際に使ってみましょう。
記事詳細のテンプレートを以下のように書き換えてください。
Componentは画面が切り替わると破棄されるため、コントローラで「記事を省略して表示するかどうか」のフラグは不要になりました。 Route
のsetupController
でコントローラの初期化をしている部分を削除しましょう。
そうすると、App.PostsShowController
も定義する必要ななくなるので削除します。
これで、コントローラで実装した場合と違って、わざわざフラグを初期化しなくてもそれぞれの記事ごとに折り畳みの状態が保持されるようになりました。
まとめ
今回はユーザのインタラクションを受け取る方法を解説しました。
Controller、Componentは今回紹介したもの以外にもまだまだ機能があります。 それらについて今後の記事で随時解説する予定です。
次回はEmber.jsが提供する汎用的な機能を解説します。