我想做什么,我认为要求您在foreach或模板绑定中扩充每个呈现项的各个bindingContexts。
可以通过实例化自定义模板引擎来扩展renderTemplateSource
中的bindingContext。但这看起来很糟糕。必须有更好的方法。
这是更好的方法?这是我的首要问题。
我还想自动将一些绑定添加到foreach绑定的匿名模板中的节点。这也是我可以使用我的自定义模板引擎,但afaik强制自动绑定以“数据绑定”属性字符串的形式添加。也非常时髦。
是否有更简洁的方法让您的自定义绑定潜入其他绑定,来自foreach的东西?这是我的第二个问题。
以下是我正在尝试做的更详细的解释。如果你能建议更酷更清洁的东西,我感谢你。
我正在尝试编写一个名为“uiList”的自定义绑定,以便这样使用:
<ul data-bind="uiList: people">
<li>
<span data-bind="text: name"></span>
...other stuff ...
</li>
<ul>
基本上是“foreach”的别名。但我也想要一些典型的列表 - 小部件 - 交互性,例如通过点击它来“选择”一个项目。并且一次只保留一个项目。
详细说明我的例子,我想让人们的名字像这样编辑:
<ul data-bind="uiList: people">
<li>
<span data-bind="text: name, visible: !$selected()"></span>
<input type="text" data-bind="value: name, visible: $selected()">
</li>
<ul>
换句话说,选择性,禁用性等信息,如整个列表的ui,而不是数据,应该以简洁的方式在绑定上下文中提供,如同上面的$selected
属性。
对于完整的功能,我的html可能如下所示:
<ul class="ui-list" data-bind="uiList: people">
<li class="ui-list-item" data-bind="click: function () {$selected(true)}, css: $selected() ? 'ui-state-selected' : ''">
<span data-bind="text: name, visible: !$selected()"></span>
<input type="text" data-bind="value: name, visible: $selected()">
</li>
<ul>
...但这就是我想让uiList自动化的东西。用户应该只需要像前面的例子一样编写。
以下是我的自定义绑定处理程序代码的外观:
var ExtendedForeachTemplateEngine = function (contextExtender, bindings) {
this.allowTemplateRewriting = false;
this.contextExtender = contextExtender;
//prep a data-bind="..." string from the given bindings to
//sneak in on rendered anon templates
this.bindingStr = [];
for (var bindingName in bindings) {
this.bindingStr.push(bindingName + ':' + bindings[bindingName]);
}
this.bindingStr = this.bindingStr.join(', ');
};
ExtendedForeachTemplateEngine.prototype = new ko.templateEngine();
ExtendedForeachTemplateEngine.prototype.renderTemplateSource = function (templateSource, bindingContext) {
//extend the binding context
this.contextExtender(bindingContext);
//sneak in bindings rendered anon templates. Only on the first
//Element node.
var nodes = templateSource.nodes().cloneNode(true).childNodes;
for (var i = 0; i < nodes.length; i ++) {
if (nodes[i].nodeType !== 1) continue;
var nodeBindings = nodes[i].getAttribute('data-bind');
if (nodeBindings) {
nodeBindings += ', ' + this.bindingStr;
} else {
nodeBindings = this.bindingStr;
}
nodes[i].setAttribute('data-bind', nodeBindings);
break;
}
return nodes;
}
/*
Takes data like a regular foreach. also takes a binding context-extender
callback function, and some bindings to add to each rendered template.
*/
ko.bindingHandlers['extendedForeach'] = {
init: function (el, va, al, vm, bc) {
var opts = va();
ko.applyBindingsToNode(el, {
template: {
foreach: opts.data,
templateEngine: new ExtendedForeachTemplateEngine(opts.contextExtender, opts.bindings)
}
}, bc);
}
};
/*
uiList
-----------
Classes:
- adds class ui-list to the bound element
- renders the inner html for each given item.
- inner html gets ui-list-item class
Selection
- click on one of the rendered item and it is selected.
- context property $selected holds selectedness.
- selected items have class ui-state-selected,
- Only one item can be selected at a time.
*/
ko.bindingHandlers['uiList'] = {
init: function (el, va, al, vm, bc) {
var items = va();
console.log(items);
var selectedItem = ko.observable(null);
ko.applyBindingsToNode(el, {
extendedForeach: {
data: items,
bindings: {
css: "'ui-list-item' + ($selected() ? ' ui-state-selected' : '')",
click: "function () { $selected(true); }"
},
contextExtender: function (context) {
context.$selected = ko.computed({
read: function () {
return selectedItem() === context.$index();
},
write: function (val) {
selectedItem(val ? context.$index() : null);
}
});
}
}
}, bc);
$(el).addClass('ui-list');
return {controlsDescendantBindings: true};
}
};
答案 0 :(得分:0)
您可以在不定制任何内容的情况下执行此操作。
在viewModel构造函数
中this.people = ko.observableArray(...);
// create holder for selected person
this.selectedPerson = ko.observable();
在HTML中,(click: $parent.selectedPerson
是可能的,因为selectedPerson
是一个函数,ko会在点击时调用$parent.selectedPerson($data)
<ul class="ui-list" data-bind="foreach: people">
<li class="ui-list-item" data-bind="click: $parent.selectedPerson,
css: { 'ui-state-selected': $parent.selectedPerson() === $data }">
<span data-bind="text: name, visible: $parent.selectedPerson() !== $data"></span>
<input type="text" data-bind="value: name, visible: $parent.selectedPerson() === $data">
</li>
<ul>
如果你想从HTML中隐藏那些额外的细节,而不是使用自定义的bindingHandler,更好的方法是在组件上使用knockout 3.2.0-beta新功能,它允许你封装内部模型(如selectedPerson)和HTML模板在一起。 http://blog.stevensanderson.com/2014/06/11/architecting-large-single-page-applications-with-knockout-js/
答案 1 :(得分:0)
创建可重用的viewmodel。这是简短的回答。
更长的一点是你需要一种很好的方法来为它注入模板,否则你需要在使用模型的任何地方都这样做。一种方法是使用像我的绑定约定库这样的库,这样它将根据视图模型自动呈现正确的模板/视图。
实施例,
https://github.com/AndersMalmgren/Knockout.BindingConventions/wiki/Template-convention
另一种方法是将可重用的viewmodel封装在自定义绑定中,并使用自定义模板源为其渲染模板。您可以查看我的组合框来获取
的示例https://github.com/AndersMalmgren/Knockout.Combobox/blob/master/src/knockout.combobox.js#L345