View – Ember.js入門(8)

2013.10.03

Ember.jsには盛りだくさんの機能がありますが、今回はViewを紹介します。次のエントリーで紹介するComponentと似た機能ですが、微妙に違っていて混乱します。このエントリーでは、Viewについて解説し、違いについては、次回のエントリーでまとめます。

Viewとは?

非常にざっくりとした言い方をするならば、Ember.jsのViewはテンプレートを分割して定義し、再利用するための機能です。ただし、partial, render, Componentという似た機能があります。Viewの特徴は、コンテキストを継承しつつ、細かいイベントや状態を制御できることです。これだけだと、なんのことか良くわからないと思うので簡単なサンプルを作りながら解説します。

メンバーを表示するView

リスト表示では、配列で定義されたメンバーの一覧を表示しました。この時、メンバー情報は、次のように

  • タグの中にハードコーディングされていました。

    <script type="text/x-handlebars">
        <ul>
        {{#each model}}
          <li><span>{{name}}({{color}})</span></li>
        {{/each}}
        </ul>
    </script>
    

    この「{{name}}({{color}})」の部分をViewとして定義します。

    Viewの定義

    ViewはEmber.Viewクラスを継承したクラスとして、JavaScriptで定義します。この時、対応するテンプレートをtemplateNameで設定します。

    App.MemberView = Ember.View.extend({
      templateName: 'member'
    });
    

    App.MemberViewで利用するテンプレートは次のように宣言します。

    <script type="text/x-handlebars" id="member">
          <span>{{name}}({{color}})</span>
    </script>
    

    idがテンプレート名です。テンプレートの内容は、Viewとして切り出したい部分をそのまま記述します。

    これで、App.MemberViewがmemberテンプレートを利用する形で定義できました。

    Viewの利用

    Viewを利用する場合は、次のようにviewヘルパーを利用します。

    <script type="text/x-handlebars">
        <h2 id="toc-view2">View</h2>
        <ul>
        {{#each model}}
          <li>{{view App.MemberView}}</li>
        {{/each}}
        </ul>
    </script>
    

    viewヘルパーの引数として、定義したViewクラス(App.MemberView)を指定します。実際にレンダリングさせてみると、リスト表示と同じように表示できることが確認出来ます。

    同じようにメンバーを表示したいところでは、Viewを再利用できるため、テンプレートをDRYに保つ事ができます。

    Viewの拡張

    独自に定義したViewには、イベントや状態を追加して拡張することができます。

    Viewに状態を追加する

    はじめに、App.MemberViewにcountプロパティ(状態)を追加してみましょう。

    App.MemberView = Ember.View.extend({
      templateName: 'member',
      count: 1
    });
    

    テンプレート側では、view.countでViewに定義したプロパティにアクセスできます。

    <script type="text/x-handlebars" id="member">
          <span>{{name}}({{color}}) - {{view.count}}</span>
    </script>
    

    Ember.jsでは、このように暗黙的に利用される名前が多くあるので、注意が必要です。

    Viewにイベントを追加する

    次にイベントを定義しましょう。クリックするとカウントが増えていく簡単なイベントです。

    App.MemberView = Ember.View.extend({
      templateName: 'member',
      count: 1,
      click: function(evt) {
        var current = this.get('count');
        this.set('count', current + 1);
      }
    });
    

    テンプレート側は変更ありません。各要素をクリックすると、それぞれのViewのcountがインクリメントされ、オートバインディングによりViewも自動更新されるのが解ります。

    View_-_Ember.js

    もし、この各要素へのカウント機能をView無しで実装しようとすれば、各メンバーのカウントに対する状態管理は、非常に複雑になってしまうと思います。さらに、クライアントサイドMVCフレームワークを利用せずにjQueryのみで実装するとしたらば、結構複雑になると思いますが、Ember.jsを利用すると非常にシンプルに書くことができます。

    Viewで参照するプロパティ

    重要なことは、Viewを利用した場合、表示するコンテキスト(対応するモデルとコントローラ)はviewヘルパーを呼び出した時点のコンテキストと同じということです。このため、App.MemberViewの中で参照しているnameやcolorは、Viewを使わずに定義した場合と同等になります。

    コンテキストが変わらないことは利点となる場合もありますが、欠点となる場合もあります。これについては、次回のComponentのエントリーで解説しましょう。

    まとめ

    Viewを使うことで、再利用可能なテンプレートを定義することができます。そして、Viewはそれぞれのインスタンスで独立した状態(プロパティ)を持つ事ができ、イベントの処理も行うことができます。これらを活用する事で、各MVCの責務が明確になり、保守性の高いコードを書くことができます。

    最後にサンプルコード全体を示します。

    <!doctype html>
    <html>
    <head>
    <meta charset="utf-8">
    <title>View - Ember.js</title>
    </head>
    <body>
    	<script type="text/x-handlebars">
        <h2 id="toc-view5">View</h2>
        <ul>
        {{#each model}}
          <li>{{view App.MemberView}}</li>
        {{/each}}
        </ul>
    </script>
    <script type="text/x-handlebars" id="member">
          <span>{{name}}({{color}}) - {{view.count}}</span>
    </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.ApplicationRoute = Ember.Route.extend({
      model: function(params) {
      	return [
      	  {name: '百田夏菜子', color:'レッド' }, 
      	  {name: '玉井詩織',  color:'イエロー' },
      	  {name: '佐々木彩夏', color:'ピンク' },
      	  {name: '有安杏果',  color:'グリーン' },
      	  {name: '高城れに',  color:'パープル' },
      	];
      },
    });
    App.MemberView = Ember.View.extend({
      templateName: 'member',
      count: 1,
      click: function(evt) {
        var current = this.get('count');
        this.set('count', current + 1);
      }
    });
    	</script>
    </body>
    </html>