让我们添加一些东西,以便我们可以看到发生了什么。首先,我们将使用唯一 ID标记Save按钮:
render: ->
id = "b#{Math.floor(Math.random() * 1000)}"
console.log('button id = ', id)
#...
然后我们可以看到点击了哪个按钮:
save: ->
console.log('pressed = ', @$('button').attr('id'))
#...
我们还将添加一个全局点击处理程序来观察<button>
Backbone 的外部内容:
$(document).on('click', 'button', ->
console.log('global click = ', @id)
)
现场版:http: //jsbin.com/oviruz/6/edit
稍微玩一下那个版本,你可能会看到发生了什么:
- 更改
<input>
.
- 尝试单击保存。
- 一旦
<input>
失去焦点,就会触发更改事件。
- 该事件调用
fieldChanged
which does @model.set(...)
。
- 该
@model.set
调用触发 Backbone 的事件,特别是@model.on(...)
来自视图的initialize
.
- Backbone 事件将我们发送到
render
which do a@$el.html(...)
替换 the<input>
和 the <button>
。
- 该
html
调用会杀死视图中的所有 DOM 元素el
。但是,这是一个很大的问题,浏览器需要在此过程完成之前再次获得控制权。
- 现在我们回到事件队列来处理点击Save。但是
<button>
我们点击的是一个僵尸,因为浏览器的工作队列是这样的:处理点击事件,替换3.4中的 DOM 元素。此处3.4中的工作尚未完成,因此<button>
您单击的内容一半在 DOM 中,一半已死,并且不会响应任何事件。
您有两个相互竞争的事件队列;您的 Backbone 事件正在更改浏览器背后的 DOM,并且由于 JavaScript 是单线程的,因此浏览器正在丢失并变得混乱。
如果您延迟@$el.html
调用足够长的时间让浏览器赶上:
set_html = =>
@$el.html """
<input type="text" id="text" value="#{@model.get('foo')}"/>
<button class="save" id="#{id}">Save</button>
"""
setTimeout(set_html, 1000) # Go higher if necessary.
你会得到你期望的行为。但这是一个可怕的、可怕的、讨厌的和可耻的组合。
当您仍在处理这些 DOM 元素上的事件时,乱搞 DOM 是充满危险的,而且只不过是一种伤害自己的复杂方法。
如果您想在字段更改时验证字段并将视图绑定render
到"change"
模型上的事件,那么我认为您必须手动进行验证并使用静默set
调用:
fieldChanged: (e) ->
field = @$(e.currentTarget)
@model.set({ foo: field.val() }, { silent: true })
// @model.validate(@model.attributes) and do something with the return value
如果您@model.save()
在Save按钮的回调中执行 a,则静默更改将被批量验证并发送到服务器。像这样的东西:http: //jsbin.com/oviruz/7/edit
或者您跳过@model.set
内部fieldChanged
并使用@model.validate
:
fieldChanged: (e) ->
val = @$(e.currentTarget).val()
// @model.validate(foo: val) and do something with the return value
并将所有设置内容留给save
:
save: ->
@model.save(foo: @$('#text').val())
像这样的东西:http: //jsbin.com/oviruz/8/edit