Knockout.js似乎在抨击我的jQuery事件处理程序,真是太粗鲁了

Knockout.js seems to be clobbering my jQuery event handlers, how rude

本文关键字:程序 事件处理 jQuery js 我的 Knockout      更新时间:2023-09-26

好吧,我已经试着解开这个烂摊子好几个小时了,但一无所获,就像狗在追尾巴一样。情况是这样的。

我的UI使用Knockout.js,它本身就很好用。然而,我正在尝试使用一些第三方代码,使下拉列表和复选框看起来都很漂亮。事实上,我甚至不确定这是第三方图书馆,还是我们的设计师写的东西。此代码隐藏真实复选框,并将其替换为通过CSS模拟复选框的伪<span />。跨度的click事件触发实际复选框的change事件:

// this code updates the fake UI
this._changeEvent = function() {
    self.isChecked = self.$input.is(':checked');
    self._updateHTML(false, true);
    jQuery(self).trigger('change');
};
// when the user clicks the fake checkbox, we trigger change on the real checkbox
this.$fake.on('click', function(e) {
    e.preventDefault();
    self.$input.click().trigger('change');
});
// Bind _changeEvent to the real checkbox
this.$input.change(this._changeEvent);

这实际上适用于Knockout.js,因为Knockout将监听该事件处理程序。换句话说,当用户单击fake复选框时,绑定的Knockout模型就会更新。然而,不起作用的是更新模型。如果我打电话:

model.SomeValue(!curValue); // SomeValue is bound to a checkbox, flip its value

模型会得到更新,但假UI不会更新。我已经将这个问题追溯到ko.bindingHandlers.checked.update中的代码,它执行以下操作:

// When bound to anything other value (not an array), the checkbox being checked represents the value being trueish
element.checked = value;

基本上,element.checked属性已设置,但不会触发任何事件。因此,_changeEvent函数永远不会被调用。因此,我实现了自己的ko.bindingHandlers.checked.update函数,它是内置函数的副本。理论上,这就是我所需要做的:

   ko.bindingHandlers.checked.update = function (element, valueAccessor)
   {
      var value = ko.utils.unwrapObservable(valueAccessor());
      if (element.type == "checkbox")
      {
         if (value instanceof Array)
         {
            // When bound to an array, the checkbox being checked represents its value being present in that array
            element.checked = ko.utils.arrayIndexOf(value, element.value) >= 0;
         }
         else
         {
            // When bound to anything other value (not an array), the checkbox being checked represents the value being trueish
            //element.checked = value;
            $(element).prop('checked', value).trigger('change'); // <--- this should work!
         }
      }
      else if (element.type == "radio")
      {
         element.checked = (element.value == value);
      }
   };

我没有设置element.checked,而是调用.prop('checked', value)并触发更改事件。然而,这并不奏效。以下是我目前所知道的:

  1. 如果我从等式中删除Knockout.js,$(element).prop('checked', value).trigger('change');就可以很好地工作。所以,knockout.js在某种程度上破坏了这个事件。它是否解除了对事件处理程序的绑定
  2. 我已经确认$(element)与伪复选框绑定代码中的this.$input相同。我可以在这个元素上设置其他expando属性,它们就会显示出来
  3. 我尝试了几种方法来调试Knockout.js和jQuery,看看事件是否仍然绑定,但这种方法并没有真正取得进展。我的直觉是Knockout.js以某种方式用自己的内部事件处理程序替换了change事件处理程序,并且删除了现有的绑定。我还没有找到证实这一点的方法

我的问题:主要是,我正在寻找这个问题的解决方案。Knockout.js是否删除应用模型之前存在的现有change事件?调试此代码并弄清楚到底发生了什么的下一步是什么?

与其覆盖基本检查的处理程序,不如创建自己的自定义绑定来更新UI。

下面是一个处理UI更新的模型和自定义绑定的示例:

var Model = function () {
    this.checked = ko.observable(false);
};
ko.bindingHandlers.customCheckbox = {
    init: function (element) {
        // Create the custom checkbox here
        $(element).customInput();
    },
    update: function (element) {
        // Update the checkbox UI after the value in the model changes
        $(element).trigger('updateState');
    }
};

我将模型绑定到以下HTML:

<input type="checkbox" name="genre" id="check-1" value="action" data-bind="checked: checked, customCheckbox: checked" />

事实上,这就是我所需要做的。

以下是一个示例:http://jsfiddle.net/badsyntax/4Cy3y/

[编辑]-我想我匆匆看完了你的问题,没有抓住你问的关键。无论如何,我都会把答案留在这里。

也尝试触发和侦听自定义事件:

element.checked = value;
element.dispatchEvent(new Event("forcedChange"));

尝试将复选框对象创建为jQuery插件,并创建淘汰的自定义绑定将其绑定到视图模型。我已经建立了一个jsfiddle来做到这一点:http://jsfiddle.net/markhagers/zee4z/3/

$(function () {
    var viewModel = {
        truthyValue1: ko.observable(false),
        truthyValue2: ko.observable(true)
    };
    ko.applyBindings(viewModel);
});

(我不得不添加这个片段,否则我的回答不被接受,请参阅jsfiddle以获取完整的代码示例)。抱歉,它看起来有很多代码,但插件部分最初是一个独立的jquery插件,后来添加了淘汰绑定,其中的一些代码已经被淘汰绑定所淘汰。它可以放在自己的文件中,以减少混乱。创建自定义绑定的文档记录在淘汰网站上。

只是集思广益,但这个第三方控件是使用事件名称空间开发的吗?正如你所建议的,淘汰赛和这种控制在某种程度上正在为默认的"改变"事件而斗争。当我开发小部件和应用程序时,在使用jQuery绑定事件时,我总是使用事件名称空间。

使用"myWidget"命名空间的示例:

$('#element').on('change.myWidget', function(e) {
  // handle the event
}

不过我可能完全偏离了底线。

只是一个快速思考。您的设计人员似乎触发了以下行中的点击和更改事件:

self.$input.click().trigger('change');

难道不能尝试一下吗?也许它绑定到点击事件而不是更改事件?试试你的其他声明:

$(element).prop('checked', value).click().trigger('change'); // <--- this should work!

我所理解的是.click()的作用:它模仿鼠标点击,同时触发点击事件(Duh?)。

我知道有时(比如在收音机或复选框中),一些编码程序会将事件绑定到点击而不是更改,因为这更有可能在正确的时间触发(在IE7中,更改事件以一种非常奇怪的方式触发)。

只是一个想法,可能是一些尝试。希望它能有所帮助!

编辑:我在您的事件和自定义事件中看到了一个关于名称空间的答案。这也可以帮助你,希望这不是一个太大的变化。

/J。

听起来,您遇到的问题并不是由于淘汰和另一组代码中的事件冲突(例如,更改复选框的CSS),而是由于如何处理Javascript线程和线程中的事件的冲突。

Javascript在浏览器中作为一个线程处理:所有进程都按顺序运行,任何中断序列的事件都可能导致事件失败。如果您可以独立调用每个函数,但在脚本中同时包含这两个函数时会产生干扰,那么触发复选框CSS更改的onclick或onmousedown事件可能会干扰淘汰脚本。在这个"单线程"中,所有事件都可以影响另一个线程的执行,甚至鼠标的移动。

这个(http://ejohn.org/blog/how-javascript-timers-work/)这个例子演示了使用计时器的想法,但我认为您当前的情况可能也遇到了同样的问题。

为了解决这个问题,您可以编写一个简短的函数,例如:

函数doBoth(checkboxid){

$("#checkboxid").addclass("prettychecked");

$("#checkboxid").prop("checked",true);

}

重要的一点是,每个动作都是按顺序进行的,没有鼠标事件中断的可能性。

希望这能有所帮助!