閲覧上の注意
この記事で対象としているバージョン0.5.3は結構古いので注意してください。例えばこの記事の内容でいえば、$el
プロパティなどが追加されています。
その他の割りと新しい情報は Backbone.js Advent Calendar 2012 などにあります。
(追記ここまで)
Backbone.js Advent Calendar もようやく折り返し地点。
そんな節目のエントリで取り上げるのが Backbone.js プログラミングでの最重要登場人物の一人である View
です。
前回のエントリで Backbone.js では View
が各種の処理を起動するということを説明しました。
今回は View
を使って、イベントハンドリングを行う方法について主に解説していこうと思います。
#View と DOM tree
View
の役割は大きく分けて2つあります。
- 特定の DOM element を監視し、それを根とする部分木で起こったイベントへの処理を管理する
- その部分木に対する DOM 操作
です。よく分かりませんね。もう少し詳しく説明しましょう。
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
の中だけを取り出すと
<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
などでも利用します。
var MyView = Backbone.View.extend({
// 設定項目を書いていく
initialize: function (options) {
// インスタンスの生成時に呼ばれる
}
});
var myView = new MyView({/* 初期化項目 */});
##el
View
が管理する DOM element を指しています。
設定方法は大きく分けて2通りです。
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
のようにインスタンスの生成時に渡す方法もあり、こちらの方が利用する機会は多いでしょう。
// 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
が使えます。
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する を参照してください。
<div id="foo">
<span class="bar">BAR</span>
<span class="baz">BAZ</span>
</div>
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 を用いて書くとこんな感じになるでしょうか。
$("#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
の使い方です。
<!-- これを -->
<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
です。
しかし、これは強制ではなく、別の名前を使っていても問題ありません。
例えばこんな感じでしょうか。
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 に用意します。
<!-- 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
メソッドを以下のようにします。
render: function () {
$(this.el).html(_.template($('#user-template').html(), {name: 'foo', text: 'bar'}));
}
これなら、後で挿入する要素が増えても、元になっている HTML タグの中身を変更するだけでいいので楽ですね。
underscore.js のドキュメントに関しては 有志の方が日本語訳を公開してくれているので、そちらを参照してください。
##まとめ
-
View
はある要素とその子要素で発生するイベントに対する処理を記述するもの - その要素は
View.el
で表される - イベントハンドリングのルールは
events
に書く
次回は Model
、そしてその次は View
の中で Model
を如何にして使うかに焦点を当てて解説していこうと思います。
それではまた明日