Backbone.jsの開発をしていると、(経験がないうちは)しばしばinitialize()
のところでel
とか$el
とかevents
とか何がどうなってるのかわからなくなるのですが、ソースを読むと非常にシンプルな流れなのでざっと見ておくと参考になるのではないかというエントリーです。
コンストラクタ
var View = Backbone.View = function(options) {
this.cid = _.uniqueId('view');
options || (options = {});
_.extend(this, _.pick(options, viewOptions));
this._ensureElement();
this.initialize.apply(this, arguments);
};
初期化処理
_.extend(this, _.pick(options, viewOptions));
まず、特定のプロパティ(viewOptions
で定義)について、optionsに初期値がふくまれていれば自動的にViewインスタンスのプロパティに設定してくれます。
var viewOptions = ['model', 'collection', 'el', 'id', 'attributes', 'className', 'tagName', 'events'];
viewOptions
はmodel
関連とか、el
やid
といった要素関連、events
が対象です。
これ以外の情報をインスタンス生成時に渡す場合はView#inititalize()
のほうで調整する必要があります・
要素の設定とイベントのバインディング処理
this._ensureElement();
this.initialize.apply(this, arguments);
次にView#_ensureElement()
で el
とか $el
など要素にまつわる処理をしています。
最後に、View#initialize()
がコールされています。
なお、this.initialize.apply(this, arguments);
としているので、インスタンス生成時にコンストラクタが受け取ったオブジェクトがそのまま引き渡されているのがわかります。
View#_ensureElement()
_ensureElement: function() {
if (!this.el) {
var attrs = _.extend({}, _.result(this, 'attributes'));
if (this.id) attrs.id = _.result(this, 'id');
if (this.className) attrs['class'] = _.result(this, 'className');
this.setElement(this._createElement(_.result(this, 'tagName')));
this._setAttributes(attrs);
} else {
this.setElement(_.result(this, 'el'));
}
},
ここでは el
が設定されていなければatteributes
やid
、className
、tagName
などからDom要素を生成したうえでView#setElement()
に渡します。el
にセレクタの文字列などが設定されていれば素直にView#setElement()
に渡します。
setElement: function(element) {
this.undelegateEvents();
this._setElement(element);
this.delegateEvents();
return this;
},
ここでDom要素($el
)のセットアップとイベントバインディングの処理が行われます。
すでに$elが存在する場合を想定して、いったん View#undelegateEvents()
が呼ばれています。
View#_setElement()
はセレクタ文字列かBackboneに指定されたセレクタ関数(jQueryなど)を渡すことで $el
にjQueryオブジェクトなどが設定されます。
ここで$elプロパティが設定されますので、View#delegateEvents()
を呼び出しevents
に定義された値を使い、$el
内の要素に対するイベントとViewインスタンスのメソッドとのバインドを行います。
つまり
var MyView = Backbone.View.extend({
events: {
'click .open': 'open'
},
initialize: function(options) {
// この時点で、new MyView({***}) で渡されるmodel, el, eventsなどが設定され、
// this.$elの準備が行われ、this.$el.find('.open').on('click', this.open)のようにイベントバインディング済みの状態になります。
},
open : {
// do something
}
});
new MyView({el:'#hoge'});
new MyView({el:$('#hoge')});
このどちらでも同じように動作します。当然、events
に記載された#hoge .open
の要素がないとクリックしても何も行われません。
よくある間違い
コンストラクタに$elを渡しても何も設定されない
var view = new MyView({ '$el': $('#hoge')});// view.el にも view.$elにも値が反映されない
MyView#initialize()や MyView#render()で Domを変更したらイベントが反映されない
var MyView = Backbone.View.extend({
events: {
'click .open': 'open'
},
initialize: function(options) {
this.$el = options.$el;
},
open : {
// 呼ばれません
}
});
自動で設定されるので、MyView#initialize
の時点で$el
を上書きする、あるいはDomを変更するとevents
の設定が無効になることがあります。
もしどうしてもこのような処理を行う場合は、前後に View#undelegateEvents()
と View#delegateEvents()
を自分で呼び出して再設定しましょう。
var MyView = Backbone.View.extend({
events: {
'click .open': 'open'
},
initialize: function(options) {
this.undelegateEvents();
this.$el = options.$el;
this.delegateEvents();
},
open : {
}
});
また、View#undelegateEvents()
を呼ばずに $el
を上書きするとメモリリークしたり、多重にイベントハンドラを設定してしまう可能性もありますので注意が必要です。
まとめ
Backbone.jsをつかっていて思うように行かないときは、ちょっと手を止めてソースコードを眺めてみると仕組みが理解できると思うのでオススメです。
Marionette.jsもViewの基本的なところの処理はほぼBackbone.jsのままなので(追加で処理しているのはui関係くらい)流れを把握しておくとハマリにくくなると思います。