如何从knockoutjs模板中访问数据上下文的observable

时间:2012-11-07 01:28:52

标签: data-binding knockout.js

我需要从淘汰模板访问整个可观察数据上下文,而不仅仅是它的值。

在我正在开发的应用程序中,我经常使用大量元数据来帮助一般地渲染视图。在过去,我使视图模型属性变得复杂 - 将元数据和数据存储为子属性(值属性中的值):

ViewModel.AwesomeProperty = {
    value: ko.observable('Awesome Value'),
    label: 'My Awesome Label',
    template: 'awesomeTemplate',
    otherMetaData: 'etc'
}

我正在更改此元数据以成为可观察对象的属性(我相信Ryan Niemeyer在他的一篇博文或会话中描述)。我发现它更干净,更优雅,并且通常更易于维护且开销更少(特别是在序列化方面)。与上述示例等效的内容如下:

ViewModel.AwesomeProperty = ko.observable('Awesome Value');
ViewModel.AwesomeProperty.label = 'My Awesome Label';
ViewModel.AwesomeProperty.template = 'awesomeTemplate';
ViewModel.AwesomeProperty.otherMetaData = 'etc';

这样做的副作用是将ViewModel.AwesomeProperty传递给模板会将数据上下文设置为observable的值(在本例中为'Awesome Value'),使元数据无法从$ data中访问:

<script id="example" type="text/html">
    <!-- This won't work anymore -->
    <span data-bind="text: $data.label></span>
</script>
<div data-bind="template: {name: 'example', data: AwesomeProperty}"></div>

我现在的解决方法是将数据值包装在匿名对象中,如下所示:

<script id="example" type="text/html">
    <!-- Now it works again -->
    <span data-bind="text: data.label></span>
</script>
<div data-bind="template: {name: 'example', data: {data:AwesomeProperty}}"></div>

但这不够优雅且不理想。在存在大量自动生成的情况下,这不仅不方便,而且实际上是主要的障碍。我已经考虑过自定义绑定来包装模板绑定,但我希望有更好的解决方案。

这是一个真实世界的例子,我一直在为级联下拉工作。 This JSFiddle有效,但that JSFiddle没有。

提前致谢。

1 个答案:

答案 0 :(得分:1)

事实上,淘汰赛将在展开后始终使用该值。如果它恰好是一个可观察的,你将失去那些子属性。您必须将您的observable重新包装到另一个对象中,这样您就不会丢失它,就像您已经找到的那样。

一个很好的方法可以解决这个问题,就是为可编辑的(或任何更多派生类型)创建一个函数来完成这个重写。您可以将所有单独的元数据添加到此重新包装的对象上,也可以将它们打包到各自的单独对象中。你的代码可以再次优雅。

var buildSelection = function (choices, Parent) {
    return _(ko.observable()).extend({
        // add the metadata to a 'meta' object
        meta: {
            choices: choices,
            availableChoices: ko.computed(function () {
                if (!Parent) return choices;
                if (!Parent()) return [];
                return _(choices).where({ ParentID: Parent().ID });
            })
        }
    });
}

ko.subscribable.fn.templateData = function (metaName) {
    return {
        // access the value through 'value'
        value: this,
        // access the metadata through 'meta'
        meta: this[metaName || 'meta'] // meta property may be overridden
    };
}

然后在绑定中,调用此函数以创建重新包装的对象。只需记住在模板中调整绑定即可。

<script id="Selection" type="text/html">
    <select data-bind="
            options: meta.availableChoices,
            optionsText: 'Value',
            value: value,
            optionsCaption: 'Select One',
            enable: meta.availableChoices().length
    "></select>
</script>

<!-- ko template: { 'name': 'Selection', 'data': Level1.templateData() } --><!-- /ko -->
<!-- ko template: { 'name': 'Selection', 'data': Level2.templateData() } --><!-- /ko -->
<!-- ko template: { 'name': 'Selection', 'data': Level3.templateData() } --><!-- /ko -->

Updated fiddle