私は Backbone.js を使用していますが、私のメイン ビューの 1 つで、非常に奇妙なバグに遭遇しました。
ビューは、新しい Twitter レイアウトのように見えます。オブジェクトの配列を受け取り、それぞれがコレクションを記述し、そのコレクションに作用する要素を表示します。各コレクションは、ビュー内の 1 つのタブで表されます。私のビューのrender()
メソッドは、このコレクション オブジェクトの配列を取得し、tabContainer
DOM 要素がまだ空でない場合はクリアし、タブをレンダリングしてから、それらの各タブにイベントをバインドします。
私のコードには、タブをレンダリングするメソッドと、クリック ハンドラをそれらのタブに順番にバインドするメソッドがあります。これは、初めて を実行render()
したときは正常に機能しますが、その後の の呼び出しでrender()
は、クリック ハンドラはバインドされません。関連するコード スニペットは次のとおりです。
initialize: function() {
// Context on render(), _addAllTabs and _bindTabEvents is set correctly to 'this'
_.bindAll(this, 'render', 'openModel', 'closeModel', 'isOpen', 'addAllModels', '_switchTab',
'addOneModel', '_addTab', '_removeTab', '_addAllTabs', '_loadCollection',
'_renderControls', '_setCurrentCollection', '_loadModels', '_bindTabEvents');
this.template = JST['ui/viewer'];
$(this.el).html(this.template({}));
// The tabContainer is cached and always available
this.tabContainer = this.$("ul.tabs");
this.collectionContainer = this.$("#collection_container");
this.controlsContainer = this.$("#controls");
this.showMoreButton = this.$("#show_more_button");
},
render: function(collections, dashboard) {
// If _bindTabEvents has been called before, then this.tab exists. I
// intentionally destroy this.tabs and all previously bound click handlers.
if (this.tabs) this.tabContainer.html("");
if (collections) this.collections = collections;
if (dashboard) this.$("#dashboard").html(dashboard.render().el);
// _addAllTabs redraws each of the tabs in my view from scratch using _addTab
this._addAllTabs();
// All tabs *are* present in the DOM before my _bindTabEvents function is called
// However these events are only bound on the first render and not subsequent renders
this._bindTabEvents();
var first_tab = this.collections[0].id;
this.openTab(first_tab);
return this;
},
openTab: function (collectionId, e) {
// If I move _bindTabEvents to here, (per my more thorough explanation below)
// my bug is somehow magically fixed. This makes no friggin sense.
if (this.isTabOpen(collectionId)) return false;
this._switchTab(collectionId, e);
},
_addAllTabs: function() {
_.each(this.collections, this._addTab );
},
_bindTabEvents: function() {
this.tabs = _.reduce(_.pluck(this.collections, "id"), _.bind(function (tabEvents, collectionId) {
var tabId = this.$("#" + collectionId + "_tab");
tabEvents[collectionId] = tabId.click(_.bind(this._switchTab, this, collectionId));
return tabEvents
}, this), {});
},
_addTab: function(content) {
this.tabContainer.append(
$('<li/>')
.attr("id", content.id + "_tab")
.addClass("tab")
.append($('<span/>')
.addClass('label')
.text(content.name)));
//this._loadCollection(content.id);
this.bind("tab:" + content.id, this._loadCollection);
pg.account.bind("change:location", this._loadCollection); // TODO: Should this be here?
},
etc..
前述したように、render()
ここでの方法は機能しますが、初回のみです。奇妙な部分は、次のスニペットのように行を移動しthis._bindTabEvents();
てメソッドの最初の行にするとopenTab()
、すべてが完全に機能することです。
openTab: function (collectionId, e) {
this._bindTabEvents();
if (this.isTabOpen(collectionId)) return false;
this._switchTab(collectionId, e);
},
もちろん、そのコード行はそのメソッドに含まれているわけではありませんが、全体が正常に機能するため、なぜそこで機能するのかを尋ねる必要がありますが、次のように順番に機能しません。
this._addAllTabs();
this._bindTabEvents();
これは私には意味がありません。次の行の後に配置しても機能しません。
var first_tab = this.collections[0].id;
実行順序に関する限り、それは機能するものと本質的に同じですが。
私が間違っていることと、これを正しくするために何をすべきか(動作とコーディングスタイルの両方の観点から)誰かが知っていますか?