让我们添加一些东西,以便我们可以看到发生了什么。首先,我们将使用唯一 ID 标记 保存 按钮:
render: ->
id = "b#{Math.floor(Math.random() * 1000)}"
console.log('button id = ', id)
#...
然后我们可以看到点击了哪个按钮:
save: ->
console.log('pressed = ', @$('button').attr('id'))
#...
我们还将添加一个全局点击处理程序以在 Backbone 内容之外查看 <button>:
$(document).on('click', 'button', ->
console.log('global click = ', @id)
)
直播版:http://jsbin.com/oviruz/6/edit
试一试那个版本,你可能会看到发生了什么:
- 更改
<input>的内容。
- 尝试点击保存。
- 只要
<input> 失去焦点,就会触发更改事件。
- 该事件调用
fieldChanged 调用@model.set(...)。
-
@model.set 调用会触发 Backbone 的事件,特别是来自视图 initialize 的 @model.on(...)。
- Backbone 事件将我们发送到
render,它执行 @$el.html(...) 替换 <input> 和 <button>。
-
html 调用会杀死视图的el 内的所有 DOM 元素。但是,这是一个很大的但是,浏览器需要在此过程完成之前再次获得控制权。
- 现在我们回到事件队列来处理点击保存。但是我们点击的
<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(),则静默更改将被批量验证并发送到服务器。像这样的东西:http://jsbin.com/oviruz/7/edit
或者你跳过fieldChanged中的@model.set,直接使用@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