yutaponのブログ

javascript界隈の興味あるネタを備忘録的に残しておく場所

Backbone.jsに入門してみる【View編】

今回はBackbone.Viewを取り扱います。
この記事を書く前にいろいろ試してみたのですが、
なかなか難しく理解が甘いので2回やります。

Backbone.Viewとは

MVCモデルでいうVCがBackbone.Viewになります。
サーバーサイドのMVCフレームワークを使ってきた人には
ビューなのにコントローラ?だと思いますが、Backbone.Viewでは
コントローラメソッドビューメソッドの二つを意識して使い分けます。
意識して、なので決まりがあるわけではありません。
慣れないうちはModelに書くべきロジックもViewに書いてしまいがちですが、
できればそれぞれの責務に応じて分離を心掛けましょう。


Backbone.Viewの使い方

Backbone.Viewを拡張して使います。
el(element)という属性にセレクタを渡すと、$elという属性にelの
jQueryオブジェクトが自動的にキャッシュされます。
従ってelに指定する時点でDOM上に該当する要素が存在しなければなりません。
また、Viewは単一の要素を管理するものなのでセレクタは唯一に絞れるものを渡します。

// elに指定した要素が既にあることが前提
var MyView = Backbone.View.extend({
    el: '#hoge'
});
var view = new MyView();


まだDOM上にない要素を扱いたい場合は
tagName, className, id, attributesなどを指定します。
コレクションと連携するときに使えそうですね。

var MyView = Backbone.View.extend({
    tagName: 'div',
    className: 'fuga',
    id: function(){ return _.uniqueId('hoge'); },
    attributes: { 'data-piyo': 'bar' }
});
var view = new MyView();
console.log(view);  // -> <div id="hoge8" class="fuga" data-piyo="bar"></div>

 

コントローラメソッド

DOMからイベントを受け取り、ModelやCollectionに処理を委ねることのみを
行うメソッドをコントローラメソッドと呼んでいます。

var MyView = Backbone.View.extend({
    el: '#hoge',
    events: {
        'click #huga': 'click'    // #hoge以下にある#hugaがクリックされたらclickメソッドを実行する
    },
    initialize: function(){
        _.bindAll(this);
    },
    click: function(e){
        this.model.trigger('alert');  // Modelのalertイベントを発火
    }
});
var view = new MyView({ model: new MyModel });  // MyModelの実装は省略

clickメソッドのようなものをコントローラメソッドと呼びます。
単に自身のmodelに処理を任せているだけですね。
また、eventsに設定したセレクタの評価はelに設定した要素以下から行なわれます。
従って普通に$()で検索するよりは速くなります。
よくeventsに指定したメソッドが動かない場合がありますが、それはelの範囲外にある
要素を指定しているのが原因だったりするので注意です。

ビューメソッド

DOMの整形や出力まわりを担当するメソッドです。
DOMにアクセスして値を取得したり、差し替えたりなどはビューメソッドになります。
Backbone.Viewでいうと、renderメソッドがビューメソッドの代表です。

var MyView = Backbone.View.extend({
    el: '#hoge',
    events: {
        'click #huga': 'click'
    },
    initialize: function(){
        _.bindAll(this);
        this.listenTo(this.model, 'change', this.render);
    },
    render: function(){
        this.$el.append('<p>fuga</p>');  // モデルの属性が変わる度にfugaを追記
        return this;
    },
    click: function(e){
        this.model.trigger('alert');
    }
});
var view = new MyView({ model: new MyModel });  // MyModelの実装は省略

Backbone.Viewにもrenderメソッドがありますが、それは単にthisを返すだけの
メソッドなので、オーバーライドして使います。
今回の例ではel属性に設定されたセレクタにp要素を追加するだけですが、
テンプレートエンジンと連携する場合や、Collectionと連携する場合などは
もっと複雑な記述になるはずです。


jsRenderと連携してみる

Backbone.Viewとテンプレートエンジンとの連携をやってみます。
いろいろなテンプレートがありますが、今回は使い慣れた jsRender を使います。
BorisMoore/jsrender · GitHub

jsRenderを使うにはjQueryを読み込んだ後にjsRenderのファイルを読み込みます。

<script src="//cdnjs.cloudflare.com/ajax/libs/underscore.js/1.4.4/underscore-min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery/2.0.2/jquery.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/backbone.js/1.0.0/backbone-min.js"></script>
<script src="js/lib/jsrender.min.js"></script><!-- 追記 -->
<script src="js/myapp.js"></script>

これでjsRenderが使えるようになりました。
Backbone.Viewのrenderで使ってみます。

var MyView = Backbone.View.extend({
    el: '#hoge',
    initialize: function(){
        _.bindAll(this);
        this.listenTo(this.model, 'change', this.render);
    },
    render: function(){
        var json = { "message": "fuga" };
        var html = $.templates('<p>{{>message}}</p>').render(json);
        this.$el.append(html);  // モデルの属性が変わる度にfugaを追記
        return this;
    }
});

今回はp要素一行程度だからソース上に書けましたが、
もっと複雑なテンプレートを扱いたい場合はHTML上に記述して、
それをセレクタで取得すると良いです。

<!-- htmlに追記 -->
<script id="hoge-template" type="text/x-jsrender">
  <div>
    <ul>
      <li>{{>title}}</li>
      <li>{{>message}}</li>
    </ul>
  </div>
</script>

上記のテンプレートを取得するように変更します。

var MyView = Backbone.View.extend({
    el: '#hoge',
    initialize: function(){
        _.bindAll(this);
        this.listenTo(this.model, 'change', this.render);
    },
    render: function(){
        var json = { "title": "hoge", "message": "fuga" };
        var template = $('#hoge-template').html();  // セレクタで取得
        var html = $.templates(template).render(json);
        this.$el.append(html);  // モデルの属性が変わる度にfugaを追記
        return this;
    }
});

このままでも動きますが、renderが呼び出される度に
テンプレートを取得しに行くので、一回のみ取得するように変更します。
こういう場合はBackbone.View.extendの第二引数に指定すると
クラスプロパティになることを利用します。

var MyView = Backbone.View.extend({
    el: '#hoge',
    initialize: function(){
        _.bindAll(this);
        this.listenTo(this.model, 'change', this.render);
    },
    render: function(){
        var json = { "title": "hoge", "message": "fuga" };
        // クラスプロパティから取得するように変更
        var html = $.templates(MyView.template).render(json);
        this.$el.append(html);
        return this;
    }
}, {  // 第二引数に指定したオブジェクトがクラスプロパティになる
    template: $('#hoge-template').html()
});

これでrenderが呼ばれる度にtemplateを探しにいかなくなりました。


おわりに

今回はBackbone.Viewの使い方とjsRenderの導入で終わってしまいました。
今回はjsRenderを使いましたが、もっとパフォーマンスの良いものがあったりするので、
興味があればいろいろ試してみるのがいいです。
Hadlebars vs. Hogan vs. Mustache vs. jsRender vs Dust · jsPerf

次回はViewとModel, Collectionの連携を行います。