Backbone.js入門 「View と Model の連携」

### 閲覧上の注意

この記事で対象としているバージョン0.5.3は結構古いので注意してください。

その他の割りと新しい情報は [Backbone.js Advent Calendar 2012](http://www.adventar.org/calendars/15) などにあります。

（追記ここまで）

----

今日のテーマは `View` と `Model` の連携です。

##基本的なパターン

例えばブログサービスを作っているとします。
おそらく `Blog` というモデルや、それを表示するページのビューを作ることになると思いますが、記事の全文を表示するビューや複数の記事をリスト形式で表示するビュー、編集ビューなどなど、一つのモデルに対して複数のビューが存在するのが一般的ではないでしょうか。
このように `View` と `Model` は基本的に *多対一* の関係になっています。
そのため、`Model` の属性が変更されたら、複数の `View` に変更を通知・反映しなければなりません（でなければユーザはシステムが不整合を起こしているように見えてしまいます）。
ここで思い出していただきたいのが、[Backbone.js入門 「Events」](http://qiita.com/yuku_t/items/16b799d0ec0a0ae3f78e) で取り上げた **オブザーバ・パターン** です。
[Backbone.js入門 「Model」](http://qiita.com/yuku_t/items/5acef8dd49f67fd7813c) で説明したように、`Model` の属性値が変更されると change イベントが発生するので、それをトリガーにして、[Backbone.js入門 「View」](http://qiita.com/yuku_t/items/8ee2ed1949cd7c337f06) で説明した `render` メソッドを起動してやるようにするのが、オーソドックスなパターンです。
例題を使って説明していきましょう。
このブログサービスには `(・∀・)ｲｲﾈ!!` ボタンがあり、それをクリックするとイイね数が増えていくという機能があるとします。

```js
// ブログモデル
var Blog = Backbone.Model.extend({
    // ...
});
blog = new Blog({text: 'foo', like: 0}); // (1)

// ブログ閲覧用のビュー
var ShowBlogView = Backbone.View.extend({
    events: {
        "click .likeBtn": "countUp" // (2)
    },
    initialize: function (options) {
        _.bindAll(this, "render");  // (3)
        this.model.bind("change", this.render); // (4)
        this.render(); // (5)
    },
    render: function () {
        $(this.el).html(_.template($("#ViewA-template").html(), this.model.attributes)); // (6)
    },
    countUp: function () {
        this.model.set({like: this.model.get('like') + 1}); // (7)
    }
});
var showBlogView = new ShowBlogView({el: $("#blog"), model: blog}); // (8)
```

```html
<script type="text/template" id="ViewA-template">
    <div>
        <span class="likeBtn">(・∀・)ｲｲﾈ!!</span>
        <span class="likeCnt"><%= like %></span>
    </div>
    <div class="text"><%= text %></div>
</script>
<div id="blog"></div>
```

順番に説明していきましょう。

まず **(1)** で `Blog` モデルのインスタンスを生成し、`text` を foo  `like` を 0 に初期化しています。
そして **(8)** で `ShowBlogView` ビューのインスタンスを生成し、`el` を設定し、`model` に先ほど作った `Blog` インスタンスを渡しています。このように `model` という名前でオブジェクトを渡すと、`View` インスタンスに `model` という名前で代入されます。**(4)** では、これを利用して、ブログの change イベントが発生したら `render` を起動するようにしています。Backbone.js 入門「Events」の最後で書いたように、コールバック関数として渡される `render` のコンテキストを束縛するために、**(3)** で `_.bindAll` を使っています。そして、初期化の最後に **(5)** で自ら `render` を実行し、 **(6)** で DOM 操作を行なっています。

**(6)** の結果、 `<div id='blog'></div>` は次のようになっているはずです。

```html
<div id="blog">
    <div>
        <span class="likeBtn">(・∀・)ｲｲﾈ!!</span>
        <span class="likeCnt">0</span>
    </div>
    <div class="text">foo</div>
</div>
```

ここでユーザが `(・∀・)ｲｲﾈ!!` をクリックしました。
すると **(2)** で書いてあるように、`countUp` メソッドが呼ばれ、**(7)** でブログの `like` の値が1上昇します。この時 change:like イベントが発生し、**(4)** の結果、改めて `render` が呼ばれますが、今度はモデルの値が `{text: 'foo', like: 1}` になっているので、`<div id='blog'></div>` の中身は結果として次のように変更されます。

```html
<div id="blog">
    <div>
        <span class="likeBtn">(・∀・)ｲｲﾈ!!</span>
        <span class="likeCnt">1</span>
    </div>
    <div class="text">foo</div>
</div>
```

`(・∀・)ｲｲﾈ!!` をクリックしたら数字が1つ大きくなりました！
現状では、`<div id="blog"></div>` の中身を全て書きなおしていて無駄が多い状態ですね。これが気になる場合は、 **(4)** の他に `this.model.bind('change:like', this.renderLikeCnt)` を追加し `renderLikeCnt` メソッドの中で必要な箇所だけを更新するようにする、などの方法が考えられると思います。

ここから先でブログのビューが増え、他のビューによってモデルが変更されても、change イベントが発生する度に `render` が呼ばれて、最新のモデルの状態を使って DOM を構築し直すことができます。

オブザーバ・パターンを用いた Backbone.js プログラミングの勘所が少しは分かっていただけたでしょうか。

##次回は

次回は `Collection` です。
それではまた明日。

---

1. [Backbone.js入門 Events](http://qiita.com/yuku_t/items/16b799d0ec0a0ae3f78e)
2. [Backbone.js入門 MVC](http://qiita.com/yuku_t/items/03f49c64d6c4bacdbb26)
3. [Backbone.js入門 View](http://qiita.com/yuku_t/items/8ee2ed1949cd7c337f06)
4. [Backbone.js入門 Model](http://qiita.com/yuku_t/items/5acef8dd49f67fd7813c)
5. Backbone.js入門 ViewとModelの連携
6. [Backbone.js入門 Collection](http://qiita.com/yuku_t/items/c19d2fff0dc23e4015ff)
7. [Backbone.js入門 ViewとModelとCollectionの連携](http://qiita.com/yuku_t/items/7095a32cc5e438172844)
8. [Backbone.js入門 RouterとHistory](http://qiita.com/yuku_t/items/13f3d1f71d31f3e78123)

- [BackboneJS 日本語メーリングリスト](https://groups.google.com/forum/?fromgroups#!forum/backbonejs-ja)