3

简而言之:我正在寻找与绑定预处理等效的组件。

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

<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>

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

4

2 回答 2

2

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

  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;
   }
}

并使用以下小提琴对其进行测试:http: //jsfiddle.net/kevinvanlierde/7b4n9f9h/4/(在 JS 的顶部,将选项设置为 1 以测试 #2;和 2(默认)以测试 #3)。


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

是的,这是可能的,尽管我曾尝试从淘汰赛的角度争论这可能是不可取的。鉴于事件绑定实际上只是告诉 Knockout“将此函数注册到此事件”的语句,您可以click直接通过 JS 设置绑定,如下所示:

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();
});

在这个小提琴中测试它。(单击自定义元素的填充以查看它是绑定事件的自定义元素;更改颜色以查看“自定义元素上的动态绑定”)

于 2015-02-19T16:30:47.273 回答
2

如果我理解正确,您想在您的自定义组件被渲染的那一刻挂钩,并将行为添加到顶部元素而不是底层元素。

正如 RPN 在此评论中提到的那样,自定义元素上的生命周期事件还没有挂钩(在 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();

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

于 2015-02-21T15:15:07.803 回答