如何使用预定义的Text和Value选项定义自定义挖空'选项绑定'

时间:2013-11-15 14:09:57

标签: javascript knockout.js

我们环境中的典型场景是允许用户选择服务器提供的选项列表(终端,产品......),然后显示请求的数据。

服务器提供的选项是名称,ID格式,因此以下的淘汰构造经常使用:

<select data-bind="options: serverOptions, optionsText: 'Name', optionsValue: 'ID', value: selectedOption>

最好制作一个自定义的bindingHandler,名为&#39; NamedIdOptions&#39;在allBindingsAccessor()上指定optionsText和optionsValue,然后重定向到标准选项绑定处理程序。

<select data-bind="NamedIdOptions: serverOptions, value: selectedOption"></select>

以前,我已经制作了自己的绑定处理程序,它填充了我自己的选项 - 但是,我更喜欢使用选项绑定处理程序提供的框架。

我尝试了不同的方法而没有太大的成功 - 选项绑定使用allBindings [&#39; optionsValue&#39;]和allBindings [&#39; optionsText&#39;]来访问该值,似乎我无法设置这些。 (我想避免使用applyBindingsToNode方法并写下以下内容:

ko.bindingHandlers.NamedIdOptions = {
    init: function(element, valueAccessor, allBindingsAccessor, viewModel)
    {
        var allBindings = allBindingsAccessor();
        allBindings.*FORCESET*("optionsText", "Name");
        allBindings.*FORCESET*("optionsValue", "ID");

        retun ko.bindingHandlers.options.init.apply(this, arguments);
    },
    update: function (element, valueAccessor, allBindingsAccessor, viewModel)
    {
        retun ko.bindingHandlers.options.update.apply(this, arguments);
    }
}

但是,似乎我无法在allBindings上设置任何内容。

我不允许使用

allBindings['_ko_property_writers']['optionsText']("Name" );
allBindings['_ko_property_writers']['optionsValue']("ID" );

我真的更愿意避免在init构造中将applyBindingsToNode作为

Knockout - is it possible to combine standard select bindings with a custom binding?

现在有人想解决这个问题的简单方法吗?

4 个答案:

答案 0 :(得分:3)

您可以考虑使用ko.applyBindingAccessorsToNode。这就是我在KO 3.0中开始这样做的方式:

ko.bindingHandlers.NamedIdOptions = {
    init: function(element, valueAccessor, allBindingsAccessor)
    {
        var injectedBindingValues = {        
            options: valueAccessor,
            optionsValue: function () { return "ID" },
            optionsText: function () { return "Name" }
        };

        ko.applyBindingAccessorsToNode(element, injectedBindingValues);

        //tell Knockout that we have already handled binding the children of this element
        //
        return { controlsDescendantBindings: true };        
    }
}

您可以在this fiddle中看到它的实际效果。

注意:我通常使用从服务器(C#,JSON.NET)发送的JSON模式,从C#属性或数据库模式元数据中自动填充UI中的选项。我提炼了我的代码并对其进行了更改以匹配OP对问题的连续性所做的事情。但是,如果对JSON模式技术有任何兴趣,请点击我,我可以发布它。

答案 1 :(得分:2)

好的 - 我最后还是将节点绑定应用于节点:

ko.bindingHandlers.NamedIdOptions =
{
    init: function (element, valueAccessor, allBindingsAccessor, viewModel)
    {
       var allBindings = allBindingsAccessor();
       var newBindingOptions = { options: allBindings.NamedIdOptions, optionsText: "Name", optionsValue: "ID" };

       delete allBindings.NamedIdOptions;
       ko.utils.extend(newBindingOptions, allBindings);

       ko.applyBindingsToNode(element, newBindingOptions, viewModel);
    }
};

它似乎按预期工作 - 我对值和selectedOptions有点不确定 - 它们有'after'设置为选项。我想在绑定值之前对NamedIdOptions进行计划时我是安全的吗?

答案 2 :(得分:1)

转发呼叫时,你不能伪造整个allBindingsAccessor参数吗?

update: function (element, valueAccessor, allBindingsAccessor, viewModel)
{
    var allBindings = allBindingsAccessor(),
        fakeAllBindingsAccessor = function () {
            // I've used jQuery.extend here, you could also manually add the properties to the allBindings object
            return $.extend(true, allBindings, {
                optionsValue: 'ID',
                optionsText: 'Name'
            };
        };
    return ko.bindingHandlers.options.init.call(this, element, valueAccessor, fakeAllBindingsAccessor, viewModel);
}

编辑:添加了一些代码,将现有的allBindingsAccessor与手工假​​绑定结合起来

答案 3 :(得分:0)

我最终得到了以下解决方案,它也允许制作简单的依赖过滤器 - 它使用下划线,但这只是为了方便:

// NamedIdOptions - is basically equal to the options binding - except, optionsText="Name", and "optionsValue='ID'"
// The options can be filered - Specifying optionsFilter: {'FilterProp' : 'valueToBeMatched', 'FilterProp2' : VMpropToMatch, .. }
// Definig optionsFilterCallback, registers a callback which will be invoked with the matched elements
// which can be used to turn off elements etc.
ko.bindingHandlers.NamedIdOptions =
{
init: function (element, valueAccessor, allBindingsAccessor, viewModel)
{
    var allBindings = allBindingsAccessor(),
        appliedValueAccesor = valueAccessor(),
        shownOptions = appliedValueAccesor,
        unwrap = ko.utils.unwrapObservable;

    if (allBindings.optionsFilter)
    {
        shownOptions = ko.computed(function ()
        {
            // First  - find all items to be presented in the list
            var allItems = unwrap(appliedValueAccesor);

            // Extract items to match against
            // it is ensured that the computed observable dependts on all its sub properties
            // All items are matched by key into an array 
            // if the match is null, undefined, or an empty array, it is not included int the match
            var matchItems = {};
            _.each(_.keys(allBindings.optionsFilter), function (key)
            {
                var observedValues = unwrap(allBindings.optionsFilter[key]);
                if (!observedValues)
                    return;

                if (!_.isArray(observedValues))
                    observedValues = [observedValues];

                matchItems[key] = observedValues;
            });

            // Find items that match the items above - uses ko own routine to do so
            var matchedItems = _.filter(allItems, function (elm)
            {
                return _.all(_.keys(matchItems), function (key)
                {
                    var match = _.contains(matchItems[key], elm[key]);
                    return match;
                });
            });

            // if a callback is defined - call it with the matched items
            if (allBindings.optionsFilterCallback)
                allBindings.optionsFilterCallback(matchedItems);

            return matchedItems;
        }, this);
    }

    // Change the binding options - the already handled items should not be reapplied to the node
    // NOTE: it would be preferable to use 'ko.3.0->applyBindingAccessorsToNode' instead of the hack below
    // It assumes that the order of dictionaries are not changed - it works, but is not complient with the Ecmascript standard
    var newBindingOptions = { options: shownOptions, optionsText: "Name", optionsValue: "ID" };
    var bindingKeys = _.keys(allBindings);
    var handledItems = _.first(bindingKeys, _.indexOf(bindingKeys, "NamedIdOptions") + 1);
    _.each(handledItems, function (item)
    {
        delete allBindings[item];
    });

    _.extend(newBindingOptions, allBindings);

    ko.applyBindingsToNode(element, newBindingOptions, viewModel);
}
};