3

我想要实现的是,在表单更改时,应该重新渲染整个视图。这是为了提供刚刚编辑的数据的预览,并在勾选复选框时隐藏表单中的某些元素。

当用户编辑字段并在不离开字段的情况下单击按钮时,会同时触发前两个事件:更改,单击。更改处理程序首先更新模型,这会触发表单的重新呈现。当点击事件发生时,什么也没有发生。我想这与重新渲染有关,因为当我注释掉

@model.on 'change', @render, @

两个事件处理程序都按原样执行。

可能由于单击目标已从 dom 中删除并添加了新按钮,因此未执行单击处理程序?我将如何解决这个问题?我在想我写的代码是'惯用的'Backbone.js,但我还在学习:-)

这是我的代码的简化版本,显示了问题: jsbin

4

2 回答 2

2

让我们添加一些东西,以便我们可以看到发生了什么。首先,我们将使用唯一 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

稍微玩一下那个版本,你可能会看到发生了什么:

  1. 更改<input>.
  2. 尝试单击保存
  3. 一旦<input>失去焦点,就会触发更改事件。
    1. 该事件调用fieldChangedwhich does @model.set(...)
    2. @model.set调用触发 Backbone 的事件,特别是@model.on(...)来自视图的initialize.
    3. Backbone 事件将我们发送到renderwhich do a@$el.html(...)替换 the<input>和 the <button>
    4. html调用会杀死视图中的所有 DOM 元素el但是,这是一个很大的问题,浏览器需要在此过程完成之前再次获得控制权。
  4. 现在我们回到事件队列来处理点击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

于 2012-10-06T19:10:40.473 回答
0

您可以在更新模型之前添加一点延迟fieldChange,您可以将change事件替换为keyup. 可能有很多解决方法,但最好的办法是不要重新渲染模型更改的整体视图。

于 2012-10-06T17:57:34.073 回答