【问题标题】:Backbone.js 'swallows' click event if another event triggers a re-render如果另一个事件触发重新渲染,Backbone.js 会“吞下”点击事件
【发布时间】:2012-10-06 17:29:20
【问题描述】:

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

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

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

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

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

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

【问题讨论】:

    标签: backbone.js backbone-events


    【解决方案1】:

    让我们添加一些东西,以便我们可以看到发生了什么。首先,我们将使用唯一 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. 只要<input> 失去焦点,就会触发更改事件。
      1. 该事件调用fieldChanged 调用@model.set(...)。
      2. @model.set 调用会触发 Backbone 的事件,特别是来自视图 initialize 的 @model.on(...)。
      3. Backbone 事件将我们发送到 render,它执行 @$el.html(...) 替换 <input> 和 <button>。
      4. html 调用会杀死视图的el 内的所有 DOM 元素。但是,这是一个很大的但是,浏览器需要在此过程完成之前再次获得控制权。
    4. 现在我们回到事件队列来处理点击保存。但是我们点击的<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

    【讨论】:

      【解决方案2】:

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

      【讨论】:

      • 主干的重点不是让视图在模型更改时重新呈现自己吗?在更新之前添加延迟将在应用新值之前保存模型...使用 keyup 将在每次按键后验证模型,在用户完成输入之前。我想了解我的代码为什么会失败,以及是否有办法让它像那样工作(自动更新视图)。
      • 我的模板中还有代码检查某些模型字段的值和满足条件时的隐藏区域,我不想重复。
      • 正如你所说,它失败了,因为浏览器在点击之前触发了更改事件。整个主干点不是重新渲染模型更改的视图。谁说你的?对于此类任务,您可以使用一些扩展名,例如 ModelBinder 或 Riverts.js。
      猜你喜欢
      • 1970-01-01
      • 2014-05-29
      • 1970-01-01
      • 2012-03-19
      • 1970-01-01
      • 1970-01-01
      • 2016-07-21
      • 2016-02-22
      • 2020-12-27
      相关资源
      最近更新 更多