Skip to content Skip to sidebar Skip to footer

How Do Define A Custom Knockout 'options Binding' With Predefined Text And Value Options

A typical scenario in our environment is to allow the user to select a list of options provided by the server (terminals, products, ..), and then present the requested data. The op

Solution 1:

You might consider using ko.applyBindingAccessorsToNode. This is how I've started doing it in 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 };        
    }
}

You can see it in action in this fiddle.

Note: I typically use JSON schema sent from the server (C#, JSON.NET) to automate populating options in the UI from C# attributes or DB schema metadata. I distilled my code and changed it to match what the OP's doing for continuity with the question. But if there is any interest in the JSON schema technique hit me up and I can post it.

Solution 2:

Okay - I ended up using apply bindings to node anyway:

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

And it seems to work out as expected - I am a bit unsure about the value and selectedOptions - which have 'after' set to options. I guess I am safe when the NamedIdOptions is plaved before the value binding?

Solution 3:

Can't you just fake the whole allBindingsAccessor parameter when forwarding the call?

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 objectreturn $.extend(true, allBindings, {
                optionsValue: 'ID',
                optionsText: 'Name'
            };
        };
    return ko.bindingHandlers.options.init.call(this, element, valueAccessor, fakeAllBindingsAccessor, viewModel);
}

Edit: added some more code to combine the existing allBindingsAccessor with the manual fake bindings

Solution 4:

I ended up with the following solution, which also allows to make simple dependendt filters - it uses underscore for that, but that is just a matter of convenience:

// 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 listvar 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 matchvar 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 sovar 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 itemsif (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 standardvar 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);
}
};

Post a Comment for "How Do Define A Custom Knockout 'options Binding' With Predefined Text And Value Options"