この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
先日、Ember.js 1.0.0 がリリースされました。RC1から約半年となりましたが、ついに正式版となります。しかし、最後の最後まで大きな変更が続いていたため、安定するのは先になるような気がします。
さて、Ember.jsでクライアントサイドMVCを学ぶシリーズの3回目は、ControllerとModelの関係についてです。一言で言えば、Ember.jsではControllerがModelをdecorateする設計となっており、View(Template)から自然にControllerやModelのプロパティにアクセスできる事が特徴です。
ModelとControllerの関係を理解する
ModelとControllerの関係を理解するために簡単なアプリケーションを作成しましょう。ここで作成するのは、ある曲を再生するためのミュージックプレイヤーです。Modelとして曲データ(タイトルとアーティスト名)を持つとし、UIでミュージックプレイヤーのボリュームを操作できることを想定します。
Templateの定義
解りやすさのため、単純なHTMLを用意します。画面はひとつであるため、Template名は省略し、デフォルトの名前(Application)を利用します。
<script type="text/x-handlebars">
<h1>Music Player</h1>
<p>Title: {{title}}</p>
<p>Artist: {{artist}}</p>
<p>Volume: {{soundVolume}}</p>
</script>
なお、今回のバージョンでは、ボリュームの変更は行えません。
Controllerを定義する
Controllerは、Ember.ObjectControllerクラスを継承して定義します。例によって命名規約に準じる必要があるため、Templateの名前に対応させなければなりません。Template名は省略しているので、ApplicationControllerとなります。
window.App = Ember.Application.create();
App.ApplicationController = Ember.ObjectController.extend({
});
なお、明示的なControllerの定義が存在しない場合、Templateに対応するデフォルトのControllerが定義されます。また、Modelが複数(配列)である場合、Ember.ObjectControllerの代わりにEmber.ArrayControllerを利用します(後日、解説します)。
Modelを定義する
ModelはRouteで定義します(参考:Routingの基本 – Ember.js入門(2))。
App.ApplicationRoute = Ember.Route.extend({
model: function() {
return {title: 'Brianstorm', artist: 'Arctic Monkeys'};
}
});
曲データをModelとしました。
永続化データと状態
さて、残るのはボリュームの情報です。勿論、ModelにsoundVolumeプロパティを追加すれば動作しますが、本当にその設計は妥当でしょうか?
いえ、Ember.jsでは次のようにControllerにsoundVolumeプロパティを追加します。
App.ApplicationController = Ember.ObjectController.extend({
soundVolume: 6
});
GUIアプリケーションを構築していくと、ミュージックプレイヤーの曲情報のように主として表示したいデータと、ボリュームのように補助的なデータの両方があることに気付きます。ほとんどの場合、主として表示したいデータはサーバから取得したり、ファイルから読み込むなど、永続化データです。一方、補助的なデータは、永続化することもありますが、一時的な情報で状態を表すデータです。これらを同じModelに放り込んでいくと、ModelとViewの結合度が高くなり、破綻していきます。
基本的な方針としては、永続化データはModelに、状態はControllerに定義すると綺麗な設計となります。ただし、一般的にViewからModelのプロパティを参照するために、{{model.title}}のように参照が一段階深くなってしまうのが難点です。しかし、Ember.jsではControllerがModelをデコレート(装飾:いわゆるデコレートパターン)するため、Template(View)から自然な形でModelにアクセスできます。
Template, Controller, Modelの関係
最後に、もう一度、Template(View), Controller, Modelの関係を整理しましょう。
Templateは、レンダリングする情報をControllerから取得します。Controllerに対応するプロパティがあるならば、その情報がTemplateに反映されます(soundVolume)。もし、Controllerに対応するプロパティがないならば、Modelの対応するプロパティを取得し、Templateに反映します(title, artist)。
一般的にMVCでは、ViewとControllerとModelは一方通行の参照関係を持ちます(ただし、ControllerからViewはある)。この原則を守らないと、ModelとViewの関係が強くなってしまうでしょう。変更の可能性が大きいViewに依存したModelを作らないようにしてください。
最後にコード全体を記述します。
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>ControllerとModel - Ember.js</title>
</head>
<body>
<script type="text/x-handlebars">
<h1>Music Player</h1>
<p>Title: {{title}}</p>
<p>Artist: {{artist}}</p>
<p>Volume: {{soundVolume}}</p>
</script>
<script type="text/javascript" src="js/libs/jquery-1.9.1.js"></script>
<script type="text/javascript" src="js/libs/handlebars-1.0.0.js"></script>
<script type="text/javascript" src="js/libs/ember-1.0.0.js"></script>
<script type="text/javascript">
window.App = Ember.Application.create();
App.ApplicationController = Ember.ObjectController.extend({
soundVolume: 6
});
App.ApplicationRoute = Ember.Route.extend({
model: function() {
return {title: 'Brianstorm', artist: 'Arctic Monkeys'};
}
});
</script>
</body>
</html>