移动dom元素时的Knockout绑定错误

时间:2018-03-05 22:27:48

标签: knockout.js

我已经嵌套了自定义的敲除绑定。

顶级绑定扩展了绑定上下文并添加了它。

子绑定需要是子绑定,因为它使用添加到绑定上下文的数据。

Psuedo-code -

ko.bindingHandlers.map = {
    init: function(element, valueAccessor, allBindings, viewModel, bindingContext) {

     let map = new map({element: element});
     let innerBindingContext = bindingContext.extend({"map": map});
     ko.applyBindingsToDescendants(innerBindingContext, element);

     // tell KO *not* to bind the descendants itself, otherwise they will be bound twice
     return { controlsDescendantBindings: true };
    }
};

ko.bindingHandlers.overlay = {
    init: function(element, valueAccessor, allBindings, viewModel, bindingContext) {

     let map = bindingContext.map;
     let overlay = new overlay({element: element});
     map.addOverlay(overlay);

     // have tried 1. code below, 
     // 2. createChildContext,
     // 3. not returning anything, 
     // 4. ko.cleanNode(element); prior to binding,
     // all with the same error

     let innerBindingContext = bindingContext.extend({});
     ko.applyBindingsToDescendants(innerBindingContext, element);
     return { controlsDescendantBindings: true };
    }
};

var vm = {
  someVariable: ko.observable("test");
}
ko.applyBindings(vm);

HTML -

<div data-bind="map:{}">
  <div data-bind="overlay:{}">
    <span data-bind="text:someVariable"></span>
  </div>
</div>

无论我做什么,我都会收到错误 -

  

“您不能多次将绑定应用于同一元素。”

绑定实际上是利用了一个javascript库。该库将子绑定dom元素移动到HTML中的其他位置。我认为这是问题所在。

为什么由于移动了dom元素而导致此错误,我该如何解决?

1 个答案:

答案 0 :(得分:1)

每当通过调用applyBindings初始化knockout时,它会递归遍历目标元素或文档中的每个节点以查找并应用绑定。我认为可能发生的是时间问题,淘汰尝试在每个独特元素同时移动时应用绑定。这类似于在循环时修改数组的长度。

当自定义绑定返回{controlsDescendantBindings: true}时,它会从主绑定进程中删除所有子节点。 Knockout将阻止其递归更深入,并且只有手动完成绑定才会应用于这些元素。但是,自定义绑定附加到的根元素仍然是该进程的一部分,并且已经绑定(根据定义 - 首先触发自定义绑定)。因此,虽然这有助于它不能解决主要问题,即根元素被绑定,然后移动到DOM中的其他位置,以便敲除&#34;找到&#34;再次尝试再绑定它。

在应用第二遍之前用ko.cleanNode清除第一次传递绑定是一种诊断性攻击。理想情况下,您应该等待应用绑定,直到元素完成被其他插件移动为止。在允许插件移动元素之前允许敲除完成所有内容的情况下,反向也可能有效,但如果元素被真正删除然后重新添加而不是仅仅被移动,则可能导致其他问题。这取决于插件的工作方式。

修改: 如果您不能将插件的时间与绑定分开,另一种可能的解决方案是将映射绑定到包装器元素上。这样,插件所针对的元素所有都可以通过{controlsDescendantBindings: true}从绑定过程中删除。当插件移动目标节点时,它会将包装器留在后面,每个人都很高兴。 (只要地图绑定不需要对可观察对象的变化作出反应)

ko.bindingHandlers.map = {
  init: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
    let target = $(element).children();
    let map = new Map({element: target});
    let innerBindingContext = bindingContext.extend({"map": map });
    ko.applyBindingsToDescendants(innerBindingContext, target); 

    // tell KO *not* to bind the descendants itself, otherwise they will be bound twice
    return { controlsDescendantBindings: true };
  }
};