Knockout:动态添加绑定到自定义元素

Knockout: dynamically add bindings to custom elements

简而言之:我正在寻找 binding preprocessing 的等效组件。

我正在尝试封装复杂的绑定,例如

<button data-bind="openModalOnClick: {template: '...', action: '...'}, css: {...}">
  delete all the things
</button>

在自定义元素中,例如

<confirmation-button>
  delete all the things
</confirmation-button>

为此,我想通过动态添加绑定将行为 直接附加到自定义元素

我知道我可以让我的组件将按钮作为模板插入,但是生成的标记

<confirmation-button>
  <button data-bind="openModalOnClick: {template: '...', action: '...'}, css: {...}">
    delete all the things
  </button>
</confirmation-button>

会是多余的。

理想情况下,我可以使用组件注册将所需的绑定动态添加到自定义元素。但是,(ab)为此使用 createViewModel 似乎不起作用:

  ko.components.register('confirmation-button', {
      viewModel: {
        createViewModel: function createViewModel(params, componentInfo) {
          var Vm;
          
          $(componentInfo.element).attr('data-bind', 'click: function() { confirm("Are you sure"); }');
          
          Vm = function Vm(params) { };
          
          return new Vm(params);
        }
      },
      template: '<!-- ko template: { nodes: $componentTemplateNodes } --><!-- /ko -->'
  });
confirmation-button {
  border: 1px solid black;
  padding: 1rem;
  cursor: pointer;
}
<script src="http://knockoutjs.com/downloads/knockout-3.3.0.js"></script>

<confirmation-button>do stuff</confirmation-button>

是否可以通过某种方式将动态绑定添加到自定义元素本身?

我尝试了不同的方法来达到预期的效果,并评估了他们的优点和缺点。不用假装 'the answer' 这可能对将来的参考有用。我测试了:

  1. ko.bindingHandlers.component.preprocess无法访问: 自定义元素、模板、组件和父视图模型。 访问: 绑定。
  2. 自定义 loadTemplate 组件加载器:无法访问: 自定义元素。 访问: 模板、父视图模型和组件视图模型(通过模板)
  3. ko.bindingProvider.instance.preprocessNode无法访问:组件视图模型、模板可以访问:自定义元素、绑定、父视图模型。

#3 看起来是三者中最合适的。给定以下代码:

ko.bindingProvider.instance.preprocessNode = function(node) {
   // access to current viewmodel
   var data = ko.dataFor(node), 
       // access to all parent viewmodels
       context = ko.contextFor(node),
       // useful to get current binding values
       component = ko.bindingProvider.instance.getBindings(node, context);
   if (node.nodeName === 'CUSTOM-BUTTON') { // only do if 'my-custom-element'
   // kind of 'raw' string extraction but does the job for demo
       var getMsg = node.getAttribute('params').split('msg:')[1], 
       msg = getMsg.slice(0,getMsg.indexOf(','));
       $(node).attr('data-bind','click: function() { confirm('+ msg +'())}');
   } else {
       return null;
   }
}

和以下 fiddle 来测试它:http://jsfiddle.net/kevinvanlierde/7b4n9f9h/4/(在 JS 的顶部,将选项设置为 1 以测试 #2;将选项设置为 2(默认)以测试 #3)。


(第一个答案)注意:虽然这部分实现了 OP 要求的“使容器元素有用”,但它附加事件 after 而不是 模板加载之前;留作参考

是的,这是可能的,尽管我已经尝试过 。鉴于事件绑定实际上只是告诉 Knockout“将此函数注册到此事件”的语句,您可以直接通过 JS 设置 click 绑定,如下所示:

function customButton(params, parent) {
    var self = this;
    this.msg = params.msg;
    this.label = params.label;
    // this is the same as the click binding
    parent.addEventListener('click', function(e) { 
        alert(self.msg()); alert(e.target.nodeName); 
    }, false);
}
var myComponent = {
    viewModel: { createViewModel: function(params, componentInfo) {
        var parent = componentInfo.element;
        return new customButton(params, parent);
    }},
    template: { element: 'custom-button-tmpl' }
}

对于 attrcss 绑定,它稍微复杂一些,但鉴于 computed 可观察对象只是每次更新其可观察对象时启动的函数,您可以,例如更改按钮上面 VM 中的背景如下:

//prop used as function, so name doesn't matter
this.background = ko.computed(function() { 
    parent.style.backgroundColor = params.bg();
});

this fiddle 中进行测试。 (点击自定义元素的padding可以看到是事件绑定的自定义元素;换个颜色可以看到'dynamic binding on custom elements')

如果我没理解错的话,您想在自定义组件呈现的那一刻挂钩并将行为添加到顶部元素而不是底层元素。

正如 RPN 在 this comment 中提到的那样,自定义元素上还没有生命周期事件的挂钩(在 3.2 版中)。基本上,您(滥用)使用 createViewModel 不起作用的原因是因为在呈现任何元素之前调用了该代码。

所以他在该评论中的建议也适用于您。目前,最优雅的方法是在顶级元素上进行自定义绑定。如果你想让它通用,你可以这样做:

<custom-element data-bind="render"></custom-element>

然后在您的 render 自定义数据绑定的 init 调用中,您可以获得自定义元素的名称并查找已注册要应用的任何 post 处理。这是一个(粗略的)示例 fiddle:http://jsfiddle.net/8r891g6b/ 这里是 javascript 以防万一:

ko.components.register('confirm-button', {
    viewModel: function (params) {
        params = params || {};
        this.text = params.text || '(no text passed in)';
    },
    template: '<button data-bind="text: text"></button>'
});

ko.bindingHandlers.render = {
    init: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
        ko.bindingHandlers.render[element.tagName.toLowerCase()](element);
    }
};

ko.bindingHandlers.render['confirm-button'] = function (element) {
    ko.utils.registerEventHandler(element, 'click', function (event) {
        if (!confirm('Are you sure?')) {
            event.preventDefault();
        }
    });
};

ko.applyBindings();

顺便说一句,这个例子有点不靠谱,因为按钮上的点击事件会先点击按钮,而不管确认处理程序如何。我只是坚持你的例子,但我希望主要思想很容易理解。