jQuery 中的点击绑定与骨干
我正在使用 Backbone.js,在我的主要观点之一中,我遇到了一个非常奇怪的错误,我一生都无法弄清楚如何解决。
该视图看起来像新的 Twitter 布局。它接收一个对象数组,每个对象都描述一个集合并查看作用于该集合的元素。每个集合由视图中的一个选项卡表示。我视图上的 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;
即使这与执行顺序方面的工作本质上相同。
有谁知道我做错了什么以及我应该做什么来使其正确(就行为和编码风格而言)?
I'm using Backbone.js, and in one of my main views I've encountered a very strange bug that I can't for the life of me figure out how to solve.
The view looks a look like the new Twitter layout. It receives an array of objects, each of which describes a collection and views elements that act on that collection. Each collection is represented by one tab in the view. The render()
method on my view takes this array of collection objects, clears out the tabContainer
DOM element if it isn't already empty, renders the tabs and then binds events to each of those tabs.
Now in my code I have the method to render the tabs and the method to bind the click handlers to those tabs sequentially. This works fine the first time I execute render()
, but on subsequent calls of render()
, the click handlers are not bound. Here's the relevant code snippet:
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..
As I said, the render()
method here does work, but only the first time around. The strange part is that if I move the line this._bindTabEvents();
and make it the first line of the openTab()
method like in the following snippet, then the whole thing works perfectly:
openTab: function (collectionId, e) {
this._bindTabEvents();
if (this.isTabOpen(collectionId)) return false;
this._switchTab(collectionId, e);
},
Of course, that line of code has no business being in that method, but it does make the whole thing work fine, which leads me to ask why it works there, but doesn't work sequentially like so:
this._addAllTabs();
this._bindTabEvents();
This makes no sense to me since, it also doesn't work if I put it after this line:
var first_tab = this.collections[0].id;
even though that is essentially the same as what does work insofar as execution order is concerned.
Does anyone have any idea what I'm doing wrong and what I should be doing to make this correct (in terms of both behavior and coding style)?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
在视图的渲染函数中,
return this.delegateEvents();
我认为您在渲染中丢失了事件绑定,需要重新建立它们。请参阅此链接以获取该函数的backbone.js 文档:
backbone.js - delegateEvents
In your view's render function,
return this.delegateEvents();
I think you are losing your event bindings across your renderings and you need to re-establish them.See this link for the backbone.js documentation for that function:
backbone.js - delegateEvents
当您切换选项卡时,您不仅仅是显示/隐藏内容,您正在销毁和重建 dom 元素,因此您也正在销毁附加到它们的事件监听器。这就是为什么这些事件只能工作一次以及为什么将 _bindTabEvents 添加到渲染中可以工作,因为您每次都会重新附加事件。
当此行执行时:
this.tabContainer.html("");
噗...没有更多选项卡,也没有更多选项卡事件。When you switch tabs you are not simply showing/hiding content you are destroying and rebuild dom element so you are also destroying event liseners attached to them. that is why the events only work once and why adding _bindTabEvents into render works, because you are re-attaching the events each time.
when this line executes :
this.tabContainer.html("");
poof... no more tabs and no more tab events.