如果另一个事件触发重新渲染,Backbone.js'吞下'click事件

时间:2012-10-06 17:29:21

标签: backbone.js backbone-events

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

当用户编辑该字段并单击该按钮而不离开该字段时,前两个事件同时被触发:更改,单击。更改处理程序首先更新模型,从而触发重新呈现表单。当点击事件发生时,没有任何反应。我想这与重新渲染有关,因为当我注释掉

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

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

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

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

2 个答案:

答案 0 :(得分:2)

让我们添加一些内容,以便我们可以看到正在发生的事情。首先,我们将使用唯一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

稍微使用该版本,您可能会看到发生了什么:

  1. 更改<input>
  2. 的内容
  3. 尝试点击保存
  4. 一旦<input>失去焦点,就会触发更改事件。
    1. 该事件调用fieldChanged @model.set(...)
    2. @model.set调用会触发Backbone的事件,尤其是视图@model.on(...)中的initialize
    3. Backbone事件会将我们发送到render,其中@$el.html(...)会替换<input><button>
    4. html调用会杀死视图el中的所有DOM元素。但是,这是一个很大的但是,浏览器需要在此过程完成之前再次获得控制权。
  5. 现在我们回到事件队列来处理点击保存。但是我们点击的<button>是一个僵尸,因为浏览器的工作队列如下所示:处理点击事件,从 3.4 替换DOM元素。此处 3.4 的工作尚未完成,因此您点击的<button>是DOM中的一半而且已经半死,并且不会响应任何事件。
  6. 你有两个事件队列互相争斗;你的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

    或者您跳过@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

答案 1 :(得分:0)

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