Backbone.js入門 「View」

### 閲覧上の注意

この記事で対象としているバージョン0.5.3は結構古いので注意してください。例えばこの記事の内容でいえば、`$el`プロパティなどが追加されています。

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

（追記ここまで）

----

Backbone.js Advent Calendar もようやく折り返し地点。
そんな節目のエントリで取り上げるのが Backbone.js プログラミングでの最重要登場人物の一人である `View` です。
前回のエントリで Backbone.js では `View` が各種の処理を起動するということを説明しました。
今回は `View` を使って、イベントハンドリングを行う方法について主に解説していこうと思います。

#View と DOM tree

`View` の役割は大きく分けて2つあります。

1. 特定の DOM element を監視し、それを根とする部分木で起こったイベントへの処理を管理する
2. その部分木に対する DOM 操作

です。よく分かりませんね。もう少し詳しく説明しましょう。

HTML が木と呼ばれる構造で表されることはご存知かと思います。例えば次のような HTML があったとしたら

```html
<html>
    <head>
        <title>hoge</title>
    </head>
    <body>
        <h1>foo</h1>
        <div>
            <p>bar</p>
        </div>
    </body>
</html>
```

これは以下のような木構造で表されます。

```
html
|-- head
|   `-- title
|       `-- hoge
`-- body
    |-- h1
    |   `-- foo
    `-- div
        `-- p
             `-- bar
```

この時、木構造の一番上にある要素( *element* )を **根** ( *root* ) と呼びます。上記の木における根は `html` です。
ここで、`head` の中だけを取り出すと

```html
<head>
    <title>hoge</title>
</head>
```

ですが、これも `head` を根とする木構造になっています。`head` を根とする木は `html` を根とする木の一部を取り出したものなので、前者を後者の **部分木** と呼びます。部分木の取り方は一通りでなく、例えば `body` を根とする部分木なども存在します。
HTML を IE や Firefox などの WEB ブラウザに読み込ませると、ブラウザは内部で単なる文字列である HTML 文書から、木構造を創り上ます。この木構造自体を **DOM tree**、各要素を **DOM element** と呼びます。

さて、ここで最初の HTML をブラウザに読み込み、表示された「bar」の文字をブラウザ上でクリックしたとします。この時ブラウザは click イベントを作り、`p` 要素に登録されている click イベントにハンドラに渡します。（この時渡されたイベントオブジェクトに対して、`stopPropagation()` を呼ばなかった場合）このイベントオブジェクトは次に `p` 要素の親である `div` 要素に渡され、一つずつ DOM tree を上に向かって進んでいきます。イベントが水中で発生した空気の泡のようにゆらゆらしながら木構造を上にたどっていく様から、これを *イベントバブリング* といいます。

Backbone.js において `View` は特定の要素を担当すると書きました。
つまり、その要素を監視して、そこをイベントバブリングで通過するイベントを捕捉する手段を提供してくれるのが `View` なのです。
そして、その要素の子供に対する操作(CSSを操作したり、新しい要素を追加したり)も `View` の役割です。
このような役割を負った `View` を使うことで嬉しいのは、JavaScript の記述が一箇所に集まることです。
jQuery を使った場合だと、DOM 操作とイベントハンドリングが全く別々の場所にあっても問題が無いですし、というか別々のところに記述されがちです。
それを `View` を使うと、一つのオブジェクトの中に集められるので、コードがすっきりします。

次からで、具体的な使い方を見て行きましょう。

##基本的な書き方

`View.extend` を用いて自分が必要とする View を作ります。`extend` は `View` に限らず `Model` や `Collection` などでも利用します。

```js
var MyView = Backbone.View.extend({
    // 設定項目を書いていく    
    initialize: function (options) {
        // インスタンスの生成時に呼ばれる
    }
});

var myView = new MyView({/* 初期化項目 */});
```

##el

`View` が管理する DOM element を指しています。
設定方法は大きく分けて2通りです。

```js
var View1 = Backbone.View.extend({
    el: $("body")
});
var view1 = new View1();

var View2 = Backbone.View.extend({});
var view2 = new View2({el: $("#hoge")});
```

`View1` のようにクラスの定義に書きこむ方法では、インスタンスが全て同じ DOM element を監視することになります。そのため、ここで書いているように `body` のような **一つしか存在しない要素** に対する `View` を作る際に便利な方法です。
一方 `View2` のようにインスタンスの生成時に渡す方法もあり、こちらの方が利用する機会は多いでしょう。

```js
// class='hoge' を全て管理する View
// 個別対応が難しくなる
new View2({el: $(".hoge")});

// class='hoge' の数だけ View を作る
// 一つ一つが別々に管理する
$(".hoge").each(function (index, elem) {
    new View2({el: elem});
});
```

ここまでは、既に DOM tree 上に存在する要素を用いる方法でしたが、新しく要素を作ることもできます。
例えば `el` を指定せずに `View` のインスタンスを作ると `<div></div>` を指します。これはメモリ上に存在するだけで、ブラウザには表示されません。
このような初期状態を設定するには `tagName` と `className` が使えます。

```js
var MyView = Backbone.View.extend({
    tagName: "span",
    className: "foo"
});
var myView = new MyView();
myView.el // => <span class='foo'></span> まだブラウザには表示されていない。
$("body").append(myView.el);
myView.el // => <span class='foo'></span> ブラウザに表示されている。
```

##events

`View` が管理する DOM element の指定方法が分かりました。次は、これを根とする DOM tree で発生したイベントを捕捉する方法です。こちらも指定方法が2通りありますがここではより一般的な `events` を使う方法を紹介します。もう一つの方法は [動的に作られる要素にイベントをbindする](http://qiita.com/items/1256) を参照してください。

```html
<div id="foo">
    <span class="bar">BAR</span>
    <span class="baz">BAZ</span>
</div>
```

```js
var MyView = Backbone.View.extend({
    el: "#foo",
    events: {
        "click" : "click", // #foo 以下で発生する全ての click イベント
        "click .bar" : "clickBar", // #foo 以下の .bar で発生する click イベント
        "click .baz" : "clickBaz"
    },
    click: function(e) { console.log("click") },
    clickBar: function(e) { console.log("clickBar") },
    clickBaz: function(e) { console.log("clickBaz") }
});
new MyView();
```

ここで BAR をクリックすると `click` と `clickBar` が、**両方** 出力されます。
簡単ですね。
これを jQuery を用いて書くとこんな感じになるでしょうか。

```js
$("#foo").delegate("click", function () { console.log("click") });
$("#foo .bar").click(function () { console.log("clickBar") });
$("#foo .baz").click(function () { console.log("clickBaz") });
```

この程度の例だと、まだ `View` を使う良さがわかりにくいかも知れませんが、もっと大規模な JavaScript になってきた場合コードが分散してしまう可能性もありますし、`Model` と連携するようになってくると管理するのも大変です。
また、今回は `click` イベントの場合だけをハンドリングするようにしましたが、他にも `dblclick` `mouseover` なども組み合わせていくこともできますし、どこに追加すればよいのかも一目瞭然です。

##render

次のようなことをしたい場面での `View` の使い方です。

```html
<!-- これを -->
<div id="user">
</div>

<!-- このように変更したい -->
<div id="user">
  <span class="name">foo</span>
  <span class="text">bar</span>
</div>
```

この場合なら、`<div id='user'></div>` を `el` とする `View` を作ればいいわけです。
その際に、`el` の下に対する DOM 操作を記述するのが `render` です。
しかし、これは強制ではなく、別の名前を使っていても問題ありません。
例えばこんな感じでしょうか。

```js
var UserView = Backbone.View.extend({
  el: $('#user'),
  initialize: function () {
    this.render();
  },
  render: function () {
    $(this.el).html('<span class="name">foo</span><span class="text">bar</span>');
  }
});
new UserView();
```

これでも問題ありませんが、`Model` と連携したりして、もっと複雑になるとしんどいですし、というか、name や text の内容は簡単に変えられるようにしたいですね。
こういう場合は underscore.js に含まれる `template` 機能を使いましょう。
こんな感じで、まずはタグの中身を HTML に用意します。

```html
<!-- type='text/template' を忘れずに(text/javascript でなければOK) -->
<script type="text/template" id="user-template">
  <span class="name"><%= name %></span>
  <span class="text"><%= text %></span>
</script>
```

そして `render` メソッドを以下のようにします。

```js
render: function () {
  $(this.el).html(_.template($('#user-template').html(), {name: 'foo', text: 'bar'}));
}
```

これなら、後で挿入する要素が増えても、元になっている HTML タグの中身を変更するだけでいいので楽ですね。
underscore.js のドキュメントに関しては [有志の方が日本語訳を公開してくれている](https://sites.google.com/site/nextliteracy/javascript/underscore-js)ので、そちらを参照してください。

##まとめ

- `View` はある要素とその子要素で発生するイベントに対する処理を記述するもの
- その要素は `View.el` で表される
- イベントハンドリングのルールは `events` に書く

次回は `Model`、そしてその次は `View` の中で `Model` を如何にして使うかに焦点を当てて解説していこうと思います。
それではまた明日

---

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

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