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

时间:2015-02-19 12:25:45

标签: knockout.js knockout-3.0

简而言之:我正在寻找与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>

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

2 个答案:

答案 0 :(得分:2)

我尝试了不同的方法来达到预期的效果,并评估了他们的专业和反对意见。不假装有“答案”,这可能对将来的参考有用。我测试过:

  1. ko.bindingHandlers.component.preprocess无权访问:自定义元素,模板,组件&amp;父视图模型。 访问:绑定。
  2. 自定义loadTemplate组件加载器:无法访问:自定义元素。 访问:模板,父母和&amp;组件视图模型(通过模板)
  3. ko.bindingProvider.instance.preprocessNode无法访问:组件视图模型,模板访问:自定义元素,绑定,父视图模型。
  4. #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;
       }
    }
    

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


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

    是的,虽然我已经尝试argueing from a Knockout point of view that it might not be advisable,但这是可能的。鉴于事件绑定实际上只是告诉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中测试。 (单击自定义元素的填充以查看它是绑定事件的自定义元素;更改颜色以查看“对自定义元素的动态绑定”)

答案 1 :(得分:2)

如果我理解正确的话,你想要在渲染自定义组件时加入钩子,并将行为添加到顶层元素而不是底层元素。

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

因此,他对该评论的建议也适用于你。目前,最优雅的方法是在顶级元素上使用自定义绑定。如果你想把它变成通用的,你可以这样做:

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

然后在render自定义数据绑定的init调用中,您可以获取自定义元素的名称,并查找已注册要应用的任何后期处理。这是一个(粗略的)示例小提琴: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();

顺便说一下,这个例子有点不稳定,因为按钮上的点击事件会先按下按钮,无论确认处理程序如何都会发生。我只是坚持你的榜样,但我希望主要的想法很容易理解。