基本的な使い方は、ModelやCollectionと一緒です。流れとしては、Backboneのコンポーネントをextendした物を宣言して、必要な時にnewしていくという形になります。
var PostView = Backbone.View.extend({
// your source
});
var postView = new PostView
Backbone.Viewの役割は、まとめると以下の通りです。
- 役割
- htmlを描写する
- DOMと関数を紐づける
- Model/Collectionと紐づける
これらについて、それぞれ1つずつ見ていきたいと思います。
htmlを描写するには、render関数を使います。
ここに、どんなソースを書いても良いのですが、一般的には、templateを準備してこれを描写するという事がよく行われます。例を見てみましょう。
- 元になるhtml
<div id="main"></div>
- Viewクラス
define([
'jquery',
'underscore',
'backbone',
'templates'
], function ($, _, Backbone, JST) {
'use strict';
var BlogView = Backbone.View.extend({
template: JST['app/scripts/templates/blog.hbs'],
el: "#main",
render: function(){
this.$el.html(this.template(this.model.toJSON()));
return this;
}
});
return BlogView;
});
- templateファイル
name is {{name}} !
※これは、yeomanのBackbone generatorで作ったソースの一部です。全部試したい方は、gistに書いたのでご参考に。
他の言語やフレームワークを触った事がある人は、このソースを見たら大体想像が付くと思いますが、一応、簡単に説明をしたいと思います。(なんとなく分かる方は読み飛ばして下さい)
- 別fileとして存在する静的なtemplateを準備しておきます。これは、呼ばれる迄画面に出る事はありません。
- この定義があった後で、Backbone.Viewのrender()の中からこのtemplateの呼び出しが行われます。
- この時、値を受け渡す事が出来て、これをキーに紐づけると、templateの中の同名のキーの中にその値が出力されます。
- ※よくBackboneではModelのtoJSONやattributesを渡して、そのキーをtemplateで書いておき、その値を表示させる事をします。
- 最後に、実際のhtmlとして出力します。
ここでは、handlerbars.jsを使いました。
templateは、多くの種類から自由に選ぶ事が出来ます。今日、よく聞く選択肢として以下の物があるでしょう。
- ejs
- handlebars
- mustache
以下の比較記事を見ると良いかと。
BackboneのViewで独特でやっかいなのが、elという要素です。
これは、このViewが紐づくDOM要素のセレクタを指定するためのプロパティです。
Viewがnewされる際に、$el
に、elに指定したセレクタを元に取得した要素を格納します。
この後、説明していくEventや、View内の$等は、全部、$elに対して実施する操作になります。
elは指定しない事もできます。その時、$el
に、tagNameやidの指定値を元にDOM要素を作り出して格納します。もし、tagNameの指定が無ければ、勝手にdivタグの要素を作成します。
これは、実際のhtmlと結びついていない状態になりますので、どこかで、この紐付きを書いてあげる必要があります。
- elの指定のないView
define([
'jquery',
'underscore',
'backbone',
'templates'
], function ($, _, Backbone, JST) {
'use strict';
var BlogView = Backbone.View.extend({
template: JST['app/scripts/templates/blog.hbs'],
render: function(){
this.$el.html(this.template(this.model.toJSON()));
return this;
}
});
return BlogView;
});
- viewを呼び出す所
var blog = new Blog({name: "yamada"});
var view = new BlogView({model: blog});
$('body').html(view.render().$el);
View自体は、elが無いだけで、特に変化はありません。 Viewを呼び出す所を見てみると、render()の後に、$elが付いています。 これで、このelの指定が無いViewで作成されたDOM要素を取り出してます。
render関数は、慣例として、最後にreturn thisを書かないといけません。これは、何故かというと、別のViewからel要素を取り出したい時があるためです。 丁度、上のel指定のないViewソースがそのシチュエーションに該当します。renderがthisを返しているから、render().$elしてDOM要素が取れるのです。
次にDOMとEventの紐付きについて見ていきましょう。
普段、jQueryを書いていると、長いjavascriptファイルの中で、 どこでDOMとEventと関数の紐付きを書いたのか分からなくなる事はありませんか? Backbone.Viewは、こういった混乱をおさえるために、DOMとEventと関数の紐付きを書く所を決めています。 この決まった所に書く事で、どこのDOMで、どんなイベントに、どのような処理を任せたいのかが明確になります。これも例を見てみましょう。
var BlogView = Backbone.View.extend({
events: {
"click #btn" : "onClickButton"
},
onClickButton: function(){
console.log('click!!!');
}
});
Eventsの中にplainなjsのオブジェクトを書きます。
キーは、空白を挟んで2つの要素(この例ではclick
と#btn
)があり、
それぞれ、イベントとDOMセレクタを記載します。
値としては、このイベントで実行したい関数名を書きます。
どうでしょう?とても、直感的なルールではないでしょうか?
勘の良い人は、ここで呼ばれた関数のthisコンテキストはどうなるのか、気になる人がいらっしゃるかもしれません。 以前のversionのBackboneでは、ここで呼ばれる関数のthisを自分でbindする必要がありました。が、最近のBackboneは、このbind処理が本体に入っているので、特に意識する必要はありません。
※ただし、events内の指定以外の所で、callbackから呼び出す関数等のbindingは自分で書く必要がありますので、注意して下さい。
初めてViewを書く時によくあるのですが、Event書いたのにうまく実行されない事があります。その時は、以下をチェックしましょう。
- elが存在するのか?
- eventで指定した要素がelの配下に存在するのか?
$elに対して、イベントのbindをしているので、elが実際のhtmlに存在しなかったり、elの中に無い要素だとうまくいきません。
BackboneのViewはModelを監視して、状態変化に伴い処理を実行します。これを書くために、Viewには、ModelやCollection用のプロパティが準備されています。 実際の使われ方としては、以下の様な流れになる事が多いでしょう。
- Viewをnewする時に、model(collection)を指定
- View内のinitializeで、modelに対してeventをbinding
- 何かしらのmodelの状態変化があれば、上記のbindingを辿って関数を実行
この時、最後の関数へのthisのbindingは自分で書く必要があるので、忘れないようにしましょう。
var PostView = Backbone.View.extend({
initialize : function() {
this.listenTo(this.model, 'change', this.onChangeModel)
},
onChangeModel : function() {
console.log('has changed!');
}
});
post = new Backbone.Model({name: "first"});
new PostView({model: post});
post.set('name', 'seconds');
さて、modelやcollectionとの紐付けについて、基本的な話から少し脱線したいと思います。 最近、Angularjsの勢いが激しく、非常に活発になってきました。 AngularjsとBackboneの違いでよく出てくるのが、データバインディングの有無です。Backbone本体にはデータバインディングが無いので、一見劣っているように見えますが、実は、プラグインを入れると簡単に使う事が出来ます。 実際に実装する時に、データバインディングがあるかないかでソースの量が大分違うので、簡単に見てみましょう。
NYTimes社のpluginです。 使い方がとてもsimpleで良いので、見てみましょう。
http://lxyuma.hatenablog.com/entry/2013/11/07/233930
Viewを書く上で非常に頻繁に出て来るシチュエーションが、親子階層を伴うViewです。最後に、このパターンについて見てみましょう。
Backboneで開発していると、どうしても、親子の階層を持つViewを書く必要が出てきます。ここでいう「親子の階層を持つView」とは、例えば、ulタグが親のViewでliタグが子供のViewであるような階層を持ったViewの事です。さて、何故、このViewを親子の階層に分ける必要があるのでしょうか?
もし、1つのViewでこの親子階層を表現しようとすると、毎回、どのViewがどのModelを指すのかの紐付け処理を書く必要がでてきます。最悪、htmlの属性要素にカスタム属性を入れなくてはならず、せっかくjavascriptのframeworkを使っているのにスマートな形ではありません。そこで、親と子供のそれぞれをViewとして分離して、1つの子供Viewのインスタンスが、elとmodelを紐づけて持っておき、Eventに対応した処理もそのViewの中でやってしまう事で非常にsimpleでスマートな実装にする事ができるのです。
さて、実際これをソースで書くと結構面倒くさい事になります。親から見て子供の追加があったら関数A、削除があったら関数B、等々毎回同じソースを書く事になり、また、書き方も沢山あるので、場合によっては非常に複雑なソースが出来てしまいます。
そこで、MarionetteのCollectionViewとItemViewが使えます。
一通り、Viewがどんなものか分かった所で、是非、ソースを読んでみると良いと思います。 Backbone.Viewが恐ろしくsimpleで、非常にソース行数が少ない事に驚くと思います。 細かな動き等は、適宜Backbone.Viewを見て確かめる事をお勧めします。その方が早いです。
そうです。Backboneが非常にsimple/compactだからこそ、出来る事です。困ったらソースをgrepしてしまいましょう。