Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

Groups support in the options binding #94

Closed
wants to merge 1 commit into from
@tiberiuana

Support for OPTGROUP tags as optionsGroup option of options binding (for issue #93).

I'm trying my hand at adding some functionality to KO, the optgroups seemed like a nice place to start. Any comments/guidance much appreciated.

@SteveSanderson

Hi Tiberiu - thanks very much for submitting this!

Since this is a fairly significant change involving quite a bit of code, and because one of the key design goals for KO now is to minimise growth of the core framework and keep additional functionality in external plugins, how would you feel about having your own separate binding (for example, called optionsGroups or similar), and have this code in there, instead of in the KO core?

You could then easily have the benefit of this working, without it expanding the core. If you make the code available on your GitHub account, then when anyone else requests this functionality we can point them to your page.

I do appreciate your willingness to contribute this. Thanks again for that! I hope you don't mind me making this suggestion.

@tiberiuana tiberiuana closed this
@tiberiuana

Thank you for the comment Steve!

OK, plugin it is then. I can expand the new binding to also include selecting the name and order of the groups (and anything else that might come up about options) and battle-test it in a project or two. On the longer term you'll always have the option of pulling it into the core to fully support SELECTs.

I'll let you know when I have something up.

@tdurand

Hi,

Do you have any example of using this custom binding?

Thanks

@tiberiuana

Hello Thibault,

Sorry, the custom binding isn't there yet. I will post a comment here and an example when it will be available.

@tdurand

Ok.

At least, can you give me a simple example of using this pull request? how to set optionGroup in my model?

I will integrate it in a custom binding after.

@tiberiuana

First, a word of warning (for the record): following the discussion above, I recommend waiting for the custom binding or writing one yourself. This will only work with the patch submitted here and is not (and won't be) supported by KnockoutJS.

This patch added the optionsGroup parameter to the options binding. The same way you can specify the text and value of each option as optionsText and, respectively, value, you can also specify an optionsGroup variable or function.

Following the example at http://knockoutjs.com/documentation/options-binding.html, your model would be like:

    var country = function(name, population, continent) {
        this.countryName = name;
        this.countryPopulation = population;    
        this.countryContinent = continent;
    };   

    var viewModel = {
        availableCountries : ko.observableArray([
            new country("UK", 65000000, "Europe"),
            new country("USA", 320000000, "North America"),
            new country("Sweden", 29000000, "Europe")
        ])
    };

And your markup would be like:

<select data-bind="options: availableCountries, 
                   optionsText: function(item) { 
                       return item.countryName + ' (pop: ' + item.countryPopulation + ')' 
                   }, 
                   optionsGroup: "countryContinent",
                   value: selectedCountry, 
                   optionsCaption: 'Choose...'"></select>

Hope this helps. If you do end up writing that custom binding, please do tell me about it.

@tdurand

Thanks, i think i will write a custom binding based on your work, i will tell you if i succeed ;)

@tdurand

Based on your work, i wrote a custom binding which works :


ko.bindingHandlers.optionsG = {
init: function (element,valueAccessor,allBindingsAccessor) {
if (element.tagName != "SELECT")
throw new Error("options binding applies only to SELECT elements");

    var previousSelectedValues = ko.utils.arrayMap(ko.utils.arrayFilter(element.childNodes, function (node) {
        return node.tagName && node.tagName == "OPTION" && node.selected;
    }), function (node) {
        return ko.selectExtensions.readValue(node) || node.innerText || node.textContent;
    });
    var previousScrollTop = element.scrollTop;

    var value = ko.utils.unwrapObservable(valueAccessor());
    var selectedValue = element.value;

    if (value) {
        var allBindings = allBindingsAccessor();
        if (typeof value.length != "number")
            value = [value];
        if (allBindings['optionsCaption']) {
            var option = document.createElement("OPTION");
            option.innerHTML = allBindings['optionsCaption'];
            ko.selectExtensions.writeValue(option, undefined);
            element.appendChild(option);
        }

        var optionsGroupNamesValue = allBindings['optionsGroupNames'];

        // Group values into optgroups
        var groupedOptions = [];
        var optionsGroupValue = allBindings['optionsGroup']; // undefined if not given
        for(var i=0, j=value.length; i < j; i++) {
            var optionsGroup = null;
            if (typeof optionsGroupValue == "function")
                optionsGroup = optionsGroupValue(value[i]);
            else if (typeof optionsGroupValue == "string") 
                optionsGroup = value[i][optionsGroupValue];
            else
                optionsGroup = "";
            if (typeof groupedOptions[optionsGroup] == "undefined")
                groupedOptions[optionsGroup] = [];
            groupedOptions[optionsGroup].push(value[i]);
        }

        // Create HTML elements
        for (var groupName in groupedOptions) {
            var optgroup = null;
            // Add an OPTGROUP for all groups except for ""
            if(groupName != "") {
                optgroup = document.createElement("OPTGROUP");
                optgroup.label = groupName;
                element.appendChild(optgroup);
            }

            // Create HTML elements for options within this group
            for (var i = 0, j = groupedOptions[groupName].length; i < j; i++) {
                var valueGroup = groupedOptions[groupName];
                var option = document.createElement("OPTION");
                var optionValue = typeof allBindings['optionsValue'] == "string" ? valueGroup[i][allBindings['optionsValue']] : valueGroup[groupName][i];

                // Pick some text to appear in the drop-down list for this data value
                var optionsTextValue = allBindings['optionsText'];
                if (typeof optionsTextValue == "function")
                    optionText = optionsTextValue(valueGroup[i]); // Given a function; run it against the data value
                else if (typeof optionsTextValue == "string")
                    optionText = valueGroup[i][optionsTextValue]; // Given a string; treat it as a property name on the data value
                else
                    optionText = optionValue; // Given no optionsText arg; use the data value itself

                optionValue = ko.utils.unwrapObservable(optionValue);
                optionText = ko.utils.unwrapObservable(optionText);
                ko.selectExtensions.writeValue(option, optionValue);
                //SPECIFIC FTS
                if(optionValue=="subject") {
                    option.setAttribute("data-bind","enable : !fts.tools.isThereASubjectWidget()");
                    ko.applyBindings(fts.main,option);
                }
                //END of specific FTS
                option.innerHTML = optionText.toString();

                if (optgroup != null)
                    optgroup.appendChild(option);
                else
                    element.appendChild(option);
            }
        }

        // IE6 doesn't like us to assign selection to OPTION nodes before they're added to the document.
        // That's why we first added them without selection. Now it's time to set the selection.
        var newOptions = element.getElementsByTagName("OPTION");
        var countSelectionsRetained = 0;
        for (var i = 0, j = newOptions.length; i < j; i++) {
            if (ko.utils.arrayIndexOf(previousSelectedValues, ko.selectExtensions.readValue(newOptions[i])) >= 0) {
                ko.utils.setOptionNodeSelectionState(newOptions[i], true);
                countSelectionsRetained++;
            }
        }

        if (previousScrollTop)
            element.scrollTop = previousScrollTop;
    }
}

};

And there is an example:

view like:

<select class="app-actions" data-bind="optionsG: availableAppActions, 
                                   optionsText: 'label',
                                   optionsValue: 'value',
                                   optionsGroup : 'group',
                                   value: selectedAppAction"></select>

model like:

var appaction = function(value, label, group) {
        this.value = value;
        this.label = label;    
        this.group = group;
    }; 
var viewModel = {
        availableAppActions : ko.observableArray([
            new appaction("Truc", "Un Truc", "Group1"),
            new appaction("Value", "Label", "Group1"),
            new appaction("ValueBis", "Label2", "Group2")
        ])
};
@jslaybaugh

Awesome @tdurand... that worked great for me also. However, to do a multi select with optgroups, the selectedOptions binding also needs to be re-written to recursively search subnodes. Here it is for anyone else who might need it:

    ko.bindingHandlers['selectedOptions'] = {
        getSelectedValuesFromSelectNode: function (selectNode)
        {
            var result = [];
            var nodes = selectNode.childNodes;
            for (var i = 0, j = nodes.length; i < j; i++)
            {
                var node = nodes[i];
                if ((node.tagName == "OPTGROUP") && node.childNodes != null)
                {
                    var subResult = this.getSelectedValuesFromSelectNode(node);
                    for (var k = 0; k < subResult.length; k++)
                    {
                        result.push(subResult[k]);
                    }
                }
                else
                {
                    if ((node.tagName == "OPTION") && node.selected)
                        result.push(ko.selectExtensions.readValue(node));
                }
            }
            return result;
        },
        setSelectedValuesFromSelectNode: function (selectNode, newValue)
        {
            var nodes = selectNode.childNodes;
            for (var i = 0, j = nodes.length; i < j; i++)
            {
                var node = nodes[i];
                if (node.tagName == "OPTGROUP" && node.childNodes != null)
                {
                    ko.bindingHandlers['selectedOptions'].setSelectedValuesFromSelectNode(node, newValue);
                }
                else
                {
                    if (node.tagName == "OPTION")
                        ko.utils.setOptionNodeSelectionState(node, ko.utils.arrayIndexOf(newValue, ko.selectExtensions.readValue(node)) >= 0);
                }
            }
        },
        'init': function (element, valueAccessor, allBindingsAccessor)
        {
            //log('init', element, valueAccessor, allBindingsAccessor);
            ko.utils.registerEventHandler(element, "change", function ()
            {
                var value = valueAccessor();
                if (ko.isWriteableObservable(value))
                    value(ko.bindingHandlers['selectedOptions'].getSelectedValuesFromSelectNode(this));
                else
                {
                    var allBindings = allBindingsAccessor();
                    if (allBindings['_ko_property_writers'] && allBindings['_ko_property_writers']['value'])
                        allBindings['_ko_property_writers']['value'](ko.bindingHandlers['selectedOptions'].getSelectedValuesFromSelectNode(this));
                }
            });
        },
        'update': function (element, valueAccessor)
        {
            //log('update', element, valueAccessor);
            if (element.tagName != "SELECT")
                throw new Error("values binding applies only to SELECT elements");

            var newValue = ko.utils.unwrapObservable(valueAccessor());
            if (newValue && typeof newValue.length == "number")
            {
                ko.bindingHandlers['selectedOptions'].setSelectedValuesFromSelectNode(element, newValue);
            }
        }
    };
@cyberprune

I am guessing this was for knockout v1, becuase it doesn't work anymore for version 2. This is the error:

Error: ko.utils.setOptionNodeSelectionState is not a function

This is becuase the function is now minified to be called ko.utils.La in the release version.

I haven't yet been able to get the combination of multi select and optgroup with the optionsG custom binding, maybe a job for tomorrow.

@jslaybaugh

@cyberprune here is the code that I have working with knockout v2.0 in production right now. Seems to work fine. I remember there were a few changes when upgrading to 2.0, but I can't remember exactly what they were, so here are both the groupedOptions binding AND the selectedOptions binding:

ko.bindingHandlers["groupedOptions"] = {
    init: function (element, valueAccessor, allBindingsAccessor)
    {
        if (element.tagName != "SELECT")
            throw new Error("options binding applies only to SELECT elements");

        var previousSelectedValues = ko.utils.arrayMap(ko.utils.arrayFilter(element.childNodes, function (node)
        {
            return node.tagName && node.tagName == "OPTION" && node.selected;
        }), function (node)
        {
            return ko.selectExtensions.readValue(node) || node.innerText || node.textContent;
        });
        var previousScrollTop = element.scrollTop;

        var value = ko.utils.unwrapObservable(valueAccessor());
        var selectedValue = element.value;

        if (value)
        {
            var allBindings = allBindingsAccessor();
            if (typeof value.length != "number")
                value = [value];
            if (allBindings['optionsCaption'])
            {
                var option = document.createElement("OPTION");
                option.innerHTML = allBindings['optionsCaption'];
                ko.selectExtensions.writeValue(option, undefined);
                element.appendChild(option);
            }

            var optionsGroupNamesValue = allBindings['optionsGroupNames'];

            // Group values into optgroups
            var groupedOptions = [];
            var optionsGroupValue = allBindings['optionsGroup']; // undefined if not given
            for (var i = 0, j = value.length; i < j; i++)
            {
                var optionsGroup = null;
                if (typeof optionsGroupValue == "function")
                    optionsGroup = optionsGroupValue(value[i]);
                else if (typeof optionsGroupValue == "string")
                    optionsGroup = value[i][optionsGroupValue];
                else
                    optionsGroup = "";
                if (typeof groupedOptions[optionsGroup] == "undefined")
                    groupedOptions[optionsGroup] = [];
                groupedOptions[optionsGroup].push(value[i]);
            }

            // Create HTML elements
            for (var groupName in groupedOptions)
            {
                var optgroup = null;
                // Add an OPTGROUP for all groups except for ""
                if (groupName != "")
                {
                    optgroup = document.createElement("OPTGROUP");
                    optgroup.label = groupName;
                    element.appendChild(optgroup);
                }

                // Create HTML elements for options within this group
                for (var i = 0, j = groupedOptions[groupName].length; i < j; i++)
                {
                    var valueGroup = groupedOptions[groupName];
                    var option = document.createElement("OPTION");
                    var optionValue = typeof allBindings['optionsValue'] == "string" ? valueGroup[i][allBindings['optionsValue']] : valueGroup[groupName][i];

                    // Pick some text to appear in the drop-down list for this data value
                    var optionsTextValue = allBindings['optionsText'];
                    if (typeof optionsTextValue == "function")
                        optionText = optionsTextValue(valueGroup[i]); // Given a function; run it against the data value
                    else if (typeof optionsTextValue == "string")
                        optionText = valueGroup[i][optionsTextValue]; // Given a string; treat it as a property name on the data value
                    else
                        optionText = optionValue; // Given no optionsText arg; use the data value itself

                    optionValue = ko.utils.unwrapObservable(optionValue);
                    optionText = ko.utils.unwrapObservable(optionText);
                    ko.selectExtensions.writeValue(option, optionValue);

                    option.innerHTML = optionText.toString();

                    if (optgroup != null)
                        optgroup.appendChild(option);
                    else
                        element.appendChild(option);
                }
            }

            // IE6 doesn't like us to assign selection to OPTION nodes before they're added to the document.
            // That's why we first added them without selection. Now it's time to set the selection.
            var newOptions = element.getElementsByTagName("OPTION");
            var countSelectionsRetained = 0;
            for (var i = 0, j = newOptions.length; i < j; i++)
            {
                if (ko.utils.arrayIndexOf(previousSelectedValues, ko.selectExtensions.readValue(newOptions[i])) >= 0)
                {
                    ko.utils.setOptionNodeSelectionState(newOptions[i], true);
                    countSelectionsRetained++;
                }
            }

            if (previousScrollTop)
                element.scrollTop = previousScrollTop;
        }
    }
};


ko.bindingHandlers['selectedOptions'] = {
    getSelectedValuesFromSelectNode: function (selectNode)
    {
        var result = [];
        var nodes = selectNode.childNodes;
        for (var i = 0, j = nodes.length; i < j; i++)
        {
            var node = nodes[i];
            if ((node.tagName == "OPTGROUP") && node.childNodes != null)
            {
                var subResult = this.getSelectedValuesFromSelectNode(node);
                for (var k = 0; k < subResult.length; k++)
                {
                    result.push(subResult[k]);
                }
            }
            else
            {
                if ((node.tagName == "OPTION") && node.selected)
                    result.push(ko.selectExtensions.readValue(node));
            }
        }
        return result;
    },
    setSelectedValuesFromSelectNode: function (selectNode, newValue)
    {
        var nodes = selectNode.childNodes;
        for (var i = 0, j = nodes.length; i < j; i++)
        {
            var node = nodes[i];
            if (node.tagName == "OPTGROUP" && node.childNodes != null)
            {
                ko.bindingHandlers['selectedOptions'].setSelectedValuesFromSelectNode(node, newValue);
            }
            else
            {
                if (node.tagName == "OPTION")
                    ko.utils.setOptionNodeSelectionState(node, ko.utils.arrayIndexOf(newValue, ko.selectExtensions.readValue(node)) >= 0);
            }
        }
    },
    'init': function (element, valueAccessor, allBindingsAccessor)
    {
        ko.utils.registerEventHandler(element, "change", function ()
        {
            var value = valueAccessor();
            if (ko.isWriteableObservable(value))
                value(ko.bindingHandlers['selectedOptions'].getSelectedValuesFromSelectNode(this));
            else
            {
                var allBindings = allBindingsAccessor();
                if (allBindings['_ko_property_writers'] && allBindings['_ko_property_writers']['value'])
                    allBindings['_ko_property_writers']['value'](ko.bindingHandlers['selectedOptions'].getSelectedValuesFromSelectNode(this));
            }
        });
    },
    'update': function (element, valueAccessor)
    {
        if (element.tagName != "SELECT")
            throw new Error("values binding applies only to SELECT elements");

        var newValue = ko.utils.unwrapObservable(valueAccessor());
        if (newValue && typeof newValue.length == "number")
        {
            ko.bindingHandlers['selectedOptions'].setSelectedValuesFromSelectNode(element, newValue);
        }
    }
};

and an example of usage:

<select data-bind="groupedOptions: Recipients, optionsText: 'FullName', optionsValue: 'Id', optionsGroup: 'Group', selectedOptions: NewTo" multiple="multiple"></select>
@cyberprune

Thanks so much for posting your updated code, that saved me lots of time porting it to v2. I have

I did notice a couple of issues with the code though:

  • I had to change the groupOptions bindingHandler function from init to update, otherwise it didn't bind. How are you binding your data? I have a response from an ajax call which I am binding to my observable: model.locations(result); My declaration in the view model is self.locations = ko.observableArray(); I originally was adding each element individually using model.locations.push(row), but that added multiple entries for one record (existing + new) for each row. I am changing the observable from an emtpy array to the populated one after ko.applyBindings(model) has been called.
  • setSelectedValuesFromSelectNode function - removed the OPTGROUP code, as it added the number of child nodes to the selected observable with the groups selected node. Do you have the ability to select a whole OPTGROUP in your application (therefore selecting the children)?
  • In my code, it isn't possible to push an element to the groupOptions observable, it doesn't recreate the options/optgroups, so the element will not show up.
ko.bindingHandlers["groupedOptions"] = {
    update: function (element, valueAccessor, allBindingsAccessor) {
        if (element.tagName != "SELECT")
            throw new Error("groupedOptions binding applies only to SELECT elements");

        var previousSelectedValues = ko.utils.arrayMap(ko.utils.arrayFilter(element.childNodes, function (node) {
            return node.tagName && node.tagName == "OPTION" && node.selected;
        }), function (node) {
            return ko.selectExtensions.readValue(node) || node.innerText || node.textContent;
        });
        var previousScrollTop = element.scrollTop;

        var value = ko.utils.unwrapObservable(valueAccessor());
        var selectedValue = element.value;

        if (value) {
            var allBindings = allBindingsAccessor();
            if (typeof value.length != "number")
                value = [value];
            if (allBindings['optionsCaption']) {
                var option = document.createElement("OPTION");
                option.innerHTML = allBindings['optionsCaption'];
                ko.selectExtensions.writeValue(option, undefined);
                element.appendChild(option);
            }

            var optionsGroupNamesValue = allBindings['optionsGroupNames'];

            // Group values into optgroups
            var groupedOptions = [];
            var optionsGroupValue = allBindings['optionsGroup']; // undefined if not given
            for (var i = 0, j = value.length; i < j; i++) {
                var optionsGroup = null;
                if (typeof optionsGroupValue == "function")
                    optionsGroup = optionsGroupValue(value[i]);
                else if (typeof optionsGroupValue == "string")
                    optionsGroup = value[i][optionsGroupValue];
                else
                    optionsGroup = "";
                if (typeof groupedOptions[optionsGroup] == "undefined")
                    groupedOptions[optionsGroup] = [];

                groupedOptions[optionsGroup].push(value[i]);
            }

            // Create HTML elements
            for (var groupName in groupedOptions) {
                var optgroup = null;
                // Add an OPTGROUP for all groups except for ""
                if (groupName != "") {
                    optgroup = document.createElement("OPTGROUP");
                    optgroup.label = groupName;
                    element.appendChild(optgroup);
                }

                // Create HTML elements for options within this group
                for (var i = 0, j = groupedOptions[groupName].length; i < j; i++) {
                    var valueGroup = groupedOptions[groupName];
                    var option = document.createElement("OPTION");
                    var optionValue = typeof allBindings['optionsValue'] == "string" ? valueGroup[i][allBindings['optionsValue']] : valueGroup[groupName][i];

                    // Pick some text to appear in the drop-down list for this data value
                    var optionsTextValue = allBindings['optionsText'];
                    if (typeof optionsTextValue == "function")
                        optionText = optionsTextValue(valueGroup[i]); // Given a function; run it against the data value
                    else if (typeof optionsTextValue == "string")
                        optionText = valueGroup[i][optionsTextValue]; // Given a string; treat it as a property name on the data value
                    else
                        optionText = optionValue; // Given no optionsText arg; use the data value itself

                    optionValue = ko.utils.unwrapObservable(optionValue);
                    optionText = ko.utils.unwrapObservable(optionText);
                    ko.selectExtensions.writeValue(option, optionValue);

                    option.innerHTML = optionText.toString();

                    if (optgroup != null)
                        optgroup.appendChild(option);
                    else
                        element.appendChild(option);
                }
            }

            // IE6 doesn't like us to assign selection to OPTION nodes before they're added to the document.
            // That's why we first added them without selection. Now it's time to set the selection.
            var newOptions = element.getElementsByTagName("OPTION");
            var countSelectionsRetained = 0;
            for (var i = 0, j = newOptions.length; i < j; i++) {
                if (ko.utils.arrayIndexOf(previousSelectedValues, ko.selectExtensions.readValue(newOptions[i])) >= 0) {
                    ko.utils.setOptionNodeSelectionState(newOptions[i], true);
                    countSelectionsRetained++;
                }
            }

            if (previousScrollTop)
                element.scrollTop = previousScrollTop;
        }
    }
};


ko.bindingHandlers['selectedOptions'] = {
    getSelectedValuesFromSelectNode: function (selectNode) {
        var result = [];
        var nodes = selectNode.childNodes;
        for (var i = 0, j = nodes.length; i < j; i++) {
            var node = nodes[i];
            if ((node.tagName == "OPTGROUP") && node.childNodes != null) {
                var subResult = this.getSelectedValuesFromSelectNode(node);
                for (var k = 0; k < subResult.length; k++) {
                    result.push(subResult[k]);
                }
            }
            else {
                if ((node.tagName == "OPTION") && node.selected)
                    result.push(ko.selectExtensions.readValue(node));
            }
        }
        return result;
    },
    setSelectedValuesFromSelectNode: function (selectNode, newValue) {
        var nodes = selectNode.childNodes;
        for (var i = 0, j = nodes.length; i < j; i++) {
            var node = nodes[i];
            if (node.tagName == "OPTION")
                ko.utils.La(node, ko.utils.arrayIndexOf(newValue, ko.selectExtensions.readValue(node)) >= 0);
        }
    },
    'init': function (element, valueAccessor, allBindingsAccessor) {
        ko.utils.registerEventHandler(element, "change", function () {
            var value = valueAccessor();
            if (ko.isWriteableObservable(value))
                value(ko.bindingHandlers['selectedOptions'].getSelectedValuesFromSelectNode(this));
            else {
                var allBindings = allBindingsAccessor();
                if (allBindings['_ko_property_writers'] && allBindings['_ko_property_writers']['value'])
                    allBindings['_ko_property_writers']['value'](ko.bindingHandlers['selectedOptions'].getSelectedValuesFromSelectNode(this));
            }
        });
    },
    'update': function (element, valueAccessor) {
        if (element.tagName != "SELECT")
            throw new Error("values binding applies only to SELECT elements");

        var newValue = ko.utils.unwrapObservable(valueAccessor());
        if (newValue && typeof newValue.length == "number") {
            ko.bindingHandlers['selectedOptions'].setSelectedValuesFromSelectNode(element, newValue);
        }
    }
};
@cyberprune

Here is my updated code, I have been able to get two way binding and updating working properly with Knockout V2.

ko.bindingHandlers["groupedOptions"] = {
    update: function (element, valueAccessor, allBindingsAccessor) {
        if (element.tagName != "SELECT")
            throw new Error("groupedOptions binding applies only to SELECT elements");

        var previousSelectedValues = [];
        for (var i = 0; i < element.childNodes.length; i++) {
            var node = element.childNodes[i];
            if (node.tagName == "OPTGROUP") {
                if (node.childNodes != undefined) {
                    for (var k = 0; k < node.childNodes.length; k++) {
                        var childNode = node.childNodes[k];
                        if (childNode.tagName && childNode.tagName && node.childNode == "OPTION" && node.selected) {
                            selected.push(ko.selectExtensions.readValue(childNode));
                        }
                    }
                }
            } else if (node.tagName && node.tagName == "OPTION" && node.selected) {
                selected.push(ko.selectExtensions.readValue(node));
            }
        }

        var previousScrollTop = element.scrollTop;

        var value = ko.utils.unwrapObservable(valueAccessor());

        // Clear existing elements
        element.innerHTML = "";

        if (value) {
            var allBindings = allBindingsAccessor();
            if (typeof value.length != "number")
                value = [value];
            if (allBindings['optionsCaption']) {
                var option = document.createElement("OPTION");
                option.innerHTML = allBindings['optionsCaption'];
                ko.selectExtensions.writeValue(option, undefined);
                element.appendChild(option);
            }

            var optionsGroupNamesValue = allBindings['optionsGroupNames'];

            // Group values into optgroups
            var groupedOptions = [];
            var optionsGroupValue = allBindings['optionsGroup']; // undefined if not given
            for (var i = 0, j = value.length; i < j; i++) {
                var optionsGroup = null;
                if (typeof optionsGroupValue == "function")
                    optionsGroup = optionsGroupValue(value[i]);
                else if (typeof optionsGroupValue == "string")
                    optionsGroup = value[i][optionsGroupValue];
                else
                    optionsGroup = "";
                if (typeof groupedOptions[optionsGroup] == "undefined")
                    groupedOptions[optionsGroup] = [];

                groupedOptions[optionsGroup].push(value[i]);
            }

            // Create HTML elements
            for (var groupName in groupedOptions) {
                var optgroup = null;
                // Add an OPTGROUP for all groups except for ""
                if (groupName != "") {
                    optgroup = document.createElement("OPTGROUP");
                    optgroup.label = groupName;
                    element.appendChild(optgroup);
                }

                // Create HTML elements for options within this group
                for (var i = 0, j = groupedOptions[groupName].length; i < j; i++) {
                    var valueGroup = groupedOptions[groupName];
                    var option = document.createElement("OPTION");
                    var optionValue = typeof allBindings['optionsValue'] == "string" ? valueGroup[i][allBindings['optionsValue']] : valueGroup[groupName][i];

                    // Pick some text to appear in the drop-down list for this data value
                    var optionsTextValue = allBindings['optionsText'];
                    if (typeof optionsTextValue == "function")
                        optionText = optionsTextValue(valueGroup[i]); // Given a function; run it against the data value
                    else if (typeof optionsTextValue == "string")
                        optionText = valueGroup[i][optionsTextValue]; // Given a string; treat it as a property name on the data value
                    else
                        optionText = optionValue; // Given no optionsText arg; use the data value itself

                    optionValue = ko.utils.unwrapObservable(optionValue);
                    optionText = ko.utils.unwrapObservable(optionText);
                    ko.selectExtensions.writeValue(option, optionValue);

                    option.innerHTML = optionText.toString();

                    if (optgroup != null)
                        optgroup.appendChild(option);
                    else
                        element.appendChild(option);
                }
            }

            // IE6 doesn't like us to assign selection to OPTION nodes before they're added to the document.
            // That's why we first added them without selection. Now it's time to set the selection.
            var newOptions = element.getElementsByTagName("OPTION");
            var countSelectionsRetained = 0;

            for (var i = 0, j = newOptions.length; i < j; i++) {
                if (ko.utils.arrayIndexOf(previousSelectedValues, ko.selectExtensions.readValue(newOptions[i])) >= 0) {
                    ko.utils.La(newOptions[i], true);
                    countSelectionsRetained++;
                }
            }

            if (previousScrollTop)
                element.scrollTop = previousScrollTop;
        }
    }
};


ko.bindingHandlers['selectedOptions'] = {
    getSelectedValuesFromSelectNode: function (selectNode) {
        var result = [];
        var nodes = selectNode.childNodes;
        for (var i = 0, j = nodes.length; i < j; i++) {
            var node = nodes[i];
            if ((node.tagName == "OPTGROUP") && node.childNodes != null) {
                var subResult = this.getSelectedValuesFromSelectNode(node);
                for (var k = 0; k < subResult.length; k++) {
                    result.push(subResult[k]);
                }
            }
            else {
                if ((node.tagName == "OPTION") && node.selected)
                    result.push(ko.selectExtensions.readValue(node));
            }
        }
        return result;
    },
    setSelectedValuesFromSelectNode: function (selectNode, newValue) {
        var nodes = selectNode.childNodes;
        for (var i = 0; i < nodes.length; i++) {
            var node = nodes[i];
            if (node.tagName == "OPTION") {
                ko.utils.La(node, ko.utils.arrayIndexOf(newValue, ko.selectExtensions.readValue(node)) >= 0);
            }
            else if (node.tagName == "OPTGROUP") {
                for (var k = 0; k < node.childNodes.length; k++) {
                    var childNode = node.childNodes[k];
                    if (childNode.tagName && childNode.tagName == "OPTION") {
                        ko.utils.La(childNode, ko.utils.arrayIndexOf(newValue, ko.selectExtensions.readValue(childNode)) >= 0);
                    }
                }
            }
        }
    },
    'init': function (element, valueAccessor, allBindingsAccessor) {
        ko.utils.registerEventHandler(element, "change", function () {
            var value = valueAccessor();
            if (ko.isWriteableObservable(value))
                value(ko.bindingHandlers['selectedOptions'].getSelectedValuesFromSelectNode(this));
            else {
                var allBindings = allBindingsAccessor();
                if (allBindings['_ko_property_writers'] && allBindings['_ko_property_writers']['value'])
                    allBindings['_ko_property_writers']['value'](ko.bindingHandlers['selectedOptions'].getSelectedValuesFromSelectNode(this));
            }
        });
    },
    'update': function (element, valueAccessor) {
        if (element.tagName != "SELECT")
            throw new Error("values binding applies only to SELECT elements");

        var newValue = ko.utils.unwrapObservable(valueAccessor());
        if (newValue && typeof newValue.length == "number") {
            ko.bindingHandlers['selectedOptions'].setSelectedValuesFromSelectNode(element, newValue);
        }
    }
};
@jfstgermain

FYI if anyone has problems running this, minified / obfuscated functions shouldn't be used since their naming is unpredictable from version to version. Better include their implementations in your code eg:
ko.utils.La should be replaced by call to setOptionNodeSelectionState declared in your file (taken from ko 2.1.0):

       function setOptionNodeSelectionState(optionNode, isSelected) {
            // IE6 sometimes throws "unknown error" if you try to write to .selected directly, whereas Firefox struggles with setAttribute. Pick one based on browser.
            if (navigator.userAgent.indexOf("MSIE 6") >= 0)
                optionNode.setAttribute("selected", isSelected);
            else
                optionNode.selected = isSelected;
        }
@nickelser

I'm not sure how that version above was supposed to work; as it is (disregarding the problem of using some minified names), it will immediately deselect any options, as the logic to check if they are selected has a couple typos in it. In any case, this version works :)

(function () {
  ko.bindingHandlers["groupedOptions"] = {
    update: function (element, valueAccessor, allBindingsAccessor) {
        if (element.tagName != "SELECT")
            throw new Error("groupedOptions binding applies only to SELECT elements");

        var previousSelectedValues = [];
        for (var i = 0; i < element.childNodes.length; i++) {
            var node = element.childNodes[i];
            if (node.tagName == "OPTGROUP") {
                if (node.childNodes != undefined) {
                    for (var k = 0; k < node.childNodes.length; k++) {
                        var childNode = node.childNodes[k];
                        if (childNode.tagName && childNode.tagName && childNode.tagName == "OPTION" && childNode.selected) {
                            previousSelectedValues.push(ko.selectExtensions.readValue(childNode));
                        }
                    }
                }
            } else if (node.tagName && node.tagName == "OPTION" && node.selected) {
                previousSelectedValues.push(ko.selectExtensions.readValue(node));
            }
        }

        var previousScrollTop = element.scrollTop;

        var value = ko.utils.unwrapObservable(valueAccessor());

        // Clear existing elements
        element.innerHTML = "";

        if (value) {
            var allBindings = allBindingsAccessor();
            if (typeof value.length != "number")
                value = [value];
            if (allBindings['optionsCaption']) {
                var option = document.createElement("OPTION");
                option.innerHTML = allBindings['optionsCaption'];
                ko.selectExtensions.writeValue(option, undefined);
                element.appendChild(option);
            }

            var optionsGroupNamesValue = allBindings['optionsGroupNames'];

            // Group values into optgroups
            var groupedOptions = [];
            var optionsGroupValue = allBindings['optionsGroup']; // undefined if not given
            for (var i = 0, j = value.length; i < j; i++) {
                var optionsGroup = null;
                if (typeof optionsGroupValue == "function")
                    optionsGroup = optionsGroupValue(value[i]);
                else if (typeof optionsGroupValue == "string")
                    optionsGroup = value[i][optionsGroupValue];
                else
                    optionsGroup = "";
                if (typeof groupedOptions[optionsGroup] == "undefined")
                    groupedOptions[optionsGroup] = [];

                groupedOptions[optionsGroup].push(value[i]);
            }

            // Create HTML elements
            for (var groupName in groupedOptions) {
                var optgroup = null;
                // Add an OPTGROUP for all groups except for ""
                if (groupName != "") {
                    optgroup = document.createElement("OPTGROUP");
                    optgroup.label = groupName;
                    element.appendChild(optgroup);
                }

                // Create HTML elements for options within this group
                for (var i = 0, j = groupedOptions[groupName].length; i < j; i++) {
                    var valueGroup = groupedOptions[groupName];
                    var option = document.createElement("OPTION");
                    var optionValue = typeof allBindings['optionsValue'] == "string" ? valueGroup[i][allBindings['optionsValue']] : valueGroup[groupName][i];

                    // Pick some text to appear in the drop-down list for this data value
                    var optionsTextValue = allBindings['optionsText'];
                    if (typeof optionsTextValue == "function")
                        optionText = optionsTextValue(valueGroup[i]); // Given a function; run it against the data value
                    else if (typeof optionsTextValue == "string")
                        optionText = valueGroup[i][optionsTextValue]; // Given a string; treat it as a property name on the data value
                    else
                        optionText = optionValue; // Given no optionsText arg; use the data value itself

                    optionValue = ko.utils.unwrapObservable(optionValue);
                    optionText = ko.utils.unwrapObservable(optionText);
                    ko.selectExtensions.writeValue(option, optionValue);

                    option.innerHTML = optionText.toString();

                    if (optgroup != null)
                        optgroup.appendChild(option);
                    else
                        element.appendChild(option);
                }
            }

            // IE6 doesn't like us to assign selection to OPTION nodes before they're added to the document.
            // That's why we first added them without selection. Now it's time to set the selection.
            var newOptions = element.getElementsByTagName("OPTION");
            var countSelectionsRetained = 0;

            for (var i = 0, j = newOptions.length; i < j; i++) {
                if (ko.utils.arrayIndexOf(previousSelectedValues, ko.selectExtensions.readValue(newOptions[i])) >= 0) {
                    ko.utils.setOptionNodeSelectionState(newOptions[i], true);
                    countSelectionsRetained++;
                }
            }

            if (previousScrollTop)
                element.scrollTop = previousScrollTop;
        }
    }
};

ko.bindingHandlers['selectedOptions'] = {
    getSelectedValuesFromSelectNode: function (selectNode) {
        var result = [];
        var nodes = selectNode.childNodes;
        for (var i = 0, j = nodes.length; i < j; i++) {
            var node = nodes[i];
            if ((node.tagName == "OPTGROUP") && node.childNodes != null) {
                var subResult = this.getSelectedValuesFromSelectNode(node);
                for (var k = 0; k < subResult.length; k++) {
                    result.push(subResult[k]);
                }
            }
            else {
                if ((node.tagName == "OPTION") && node.selected)
                    result.push(ko.selectExtensions.readValue(node));
            }
        }
        return result;
    },
    setSelectedValuesFromSelectNode: function (selectNode, newValue) {
        var nodes = selectNode.childNodes;
        for (var i = 0; i < nodes.length; i++) {
            var node = nodes[i];
            if (node.tagName == "OPTION") {
                ko.utils.setOptionNodeSelectionState(node, ko.utils.arrayIndexOf(newValue, ko.selectExtensions.readValue(node)) >= 0);
            }
            else if (node.tagName == "OPTGROUP") {
                for (var k = 0; k < node.childNodes.length; k++) {
                    var childNode = node.childNodes[k];
                    if (childNode.tagName && childNode.tagName == "OPTION") {
                        ko.utils.setOptionNodeSelectionState(childNode, ko.utils.arrayIndexOf(newValue, ko.selectExtensions.readValue(childNode)) >= 0);
                    }
                }
            }
        }
    },
    'init': function (element, valueAccessor, allBindingsAccessor) {
        ko.utils.registerEventHandler(element, "change", function () {
            var value = valueAccessor();
            if (ko.isWriteableObservable(value))
                value(ko.bindingHandlers['selectedOptions'].getSelectedValuesFromSelectNode(this));
            else {
                var allBindings = allBindingsAccessor();
                if (allBindings['_ko_property_writers'] && allBindings['_ko_property_writers']['value'])
                    allBindings['_ko_property_writers']['value'](ko.bindingHandlers['selectedOptions'].getSelectedValuesFromSelectNode(this));
            }
        });
    },
    'update': function (element, valueAccessor) {
        if (element.tagName != "SELECT")
            throw new Error("values binding applies only to SELECT elements");

        var newValue = ko.utils.unwrapObservable(valueAccessor());
        if (newValue && typeof newValue.length == "number") {
            ko.bindingHandlers['selectedOptions'].setSelectedValuesFromSelectNode(element, newValue);
        }
    }
  };
})();
@dvtoever

Please pay attention in case you are using Internet Explorer 8. Sometimes you need to fill some gaps because the Array has no indexOf method in IE8. It is usually done something like:

if (!Array.prototype.indexOf) {
    Array.prototype.indexOf = function(obj, start) {
        for ( var i = (start || 0), j = this.length; i < j; i++) {
            if (this[i] === obj) {
                return i;
            }
        }

        return -1;
    };
}

However, if you do this, the following lines of code will not work:

// Create HTML elements
for (var groupName in groupedOptions) { ... }

If you change it to:

// Create HTML elements
for (var groupName in groupedOptions) {
  if(groupedOptions.hasOwnProperty(groupName)) { ... }

It will skip the 'element' called 'indexOf'.

@DR9885

Is there any documentation on it's usage?

@somnathsaha

The below code works fine with knockout v2.1 but giving error with knockout v2.3 in line ko.utils.emptyDomNode(element);

ko.bindingHandlers['options'] = {
    'update': function (element, valueAccessor, allBindingsAccessor) {
        //debugger;
        if (element.tagName != "SELECT")
            throw new Error("options binding applies only to SELECT elements");

        var previousSelectedValues = ko.utils.arrayMap(ko.utils.arrayFilter(element.childNodes, function (node) {
            return node.tagName && node.tagName == "OPTION" && node.selected;
        }), function (node) {
            return ko.selectExtensions.readValue(node) || node.innerText || node.textContent;
        });
        var previousScrollTop = element.scrollTop;

        var value = ko.utils.unwrapObservable(valueAccessor());
        var selectedValue = element.value;
        ko.utils.emptyDomNode(element);

        if (value) {
            var allBindings = allBindingsAccessor();
            if (typeof value.length != "number")
                value = [value];
            if (allBindings['optionsCaption']) {
                var option = document.createElement("OPTION");
                option.innerHTML = allBindings['optionsCaption'];
                ko.selectExtensions.writeValue(option, undefined);
                element.appendChild(option);
            }

            var optionsGroupNamesValue = allBindings['optionsGroupNames'];

            // Group values into optgroups
            var groupedOptions = [];
            var optionsGroupValue = allBindings['optionsGroup']; // undefined if not given
            for (var i = 0, j = value.length; i < j; i++) {
                var optionsGroup = null;
                if (typeof optionsGroupValue == "function")
                    optionsGroup = optionsGroupValue(value[i]);
                else if (typeof optionsGroupValue == "string")
                    optionsGroup = value[i][optionsGroupValue];
                else
                    optionsGroup = "";
                if (typeof groupedOptions[optionsGroup] == "undefined")
                    groupedOptions[optionsGroup] = [];
                groupedOptions[optionsGroup].push(value[i]);
            }

            // Create HTML elements
            for (var groupName in groupedOptions) {
                var optgroup = null;
                // Add an OPTGROUP for all groups except for ""
                if (groupName != "") {
                    optgroup = document.createElement("OPTGROUP");
                    optgroup.label = groupName;
                    element.appendChild(optgroup);
                }

                // Create HTML elements for options within this group
                for (var i = 0, j = groupedOptions[groupName].length; i < j; i++) {
                    var valueGroup = groupedOptions[groupName];
                    var option = document.createElement("OPTION");
                    var optionValue = typeof allBindings['optionsValue'] == "string" ? valueGroup[i][allBindings['optionsValue']] : valueGroup[groupName][i];

                    // Pick some text to appear in the drop-down list for this data value
                    var optionsTextValue = allBindings['optionsText'];
                    if (typeof optionsTextValue == "function")
                        optionText = optionsTextValue(valueGroup[i]); // Given a function; run it against the data value
                    else if (typeof optionsTextValue == "string")
                        optionText = valueGroup[i][optionsTextValue]; // Given a string; treat it as a property name on the data value
                    else
                        optionText = optionValue; // Given no optionsText arg; use the data value itself

                    optionValue = ko.utils.unwrapObservable(optionValue);
                    optionText = ko.utils.unwrapObservable(optionText);
                    ko.selectExtensions.writeValue(option, optionValue);
                    option.innerHTML = optionText.toString();

                    if (optgroup != null)
                        optgroup.appendChild(option);
                    else
                        element.appendChild(option);
                }
            }

            // IE6 doesn't like us to assign selection to OPTION nodes before they're added to the document.
            // That's why we first added them without selection. Now it's time to set the selection.
            var newOptions = element.getElementsByTagName("OPTION");
            var countSelectionsRetained = 0;
            for (var i = 0, j = newOptions.length; i < j; i++) {
                if (ko.utils.arrayIndexOf(previousSelectedValues, ko.selectExtensions.readValue(newOptions[i])) >= 0) {
                    ko.utils.setOptionNodeSelectionState(newOptions[i], true);
                    countSelectionsRetained++;
                }
            }

            if (previousScrollTop)
                element.scrollTop = previousScrollTop;
        }
    }
};
@davhdavh

"Since this is a fairly significant change involving quite a bit of code"...
Not really, it is <20 lines of additional code...

in ko.bindingHandlers.options.update add this function:

function optionGroupForArrayItem(arrayEntry) {
if (arrayEntry === captionPlaceholder)
return optionForArrayItem.apply(this, arguments);

var optgroup = document.createElement("optgroup");
var optGroupItems = applyToObject(arrayEntry, allBindings.get('optgroupChildren'), []);

ko.utils.setDomNodeChildrenFromArrayMapping(optgroup, optGroupItems, optionForArrayItem);
// Apply some text to the optgroup element
var optgroupText = applyToObject(arrayEntry, allBindings.get('optgroupText'), arrayEntry);
optgroup.setAttribute('label', optgroupText.toString());

return [optgroup];
}

and change this line:

ko.utils.setDomNodeChildrenFromArrayMapping(element, filteredArray, optionForArrayItem, null, callback);
to
ko.utils.setDomNodeChildrenFromArrayMapping(element, filteredArray, allBindings.get('optgroupChildren') ? optionGroupForArrayItem : optionForArrayItem, null, callback);

Testdata:

test= [
{
text: 'hest1',
children: [
{
id: '1',
text: 'child1',
},
{
id: '2',
text: 'child2',
},
{
id: '3',
text: 'child3',
},
],
},
{
text: 'hest2',
children: [
{
id: '4',
text: 'child4',
},
{
id: '5',
text: 'child5',
},
],
}
]
selected2 = ko.observableArray([]);

http://jsfiddle.net/S2sXZ/

@okas

Hi,

@davhdavh's solution works well, although I put ko.utils.unwrapObservable() calls in there to cover observable viewmodel. Changed method below:

function optionGroupForArrayItem(arrayEntry) {
    if (arrayEntry === captionPlaceholder) {
        return optionForArrayItem.apply(this, arguments);
    }

    var optgroup = document.createElement("optgroup");
    var optGroupItems = applyToObject(arrayEntry, allBindings.get('optgroupChildren'), []);

    ko.utils.setDomNodeChildrenFromArrayMapping(optgroup, ko.utils.unwrapObservable(optGroupItems), optionForArrayItem);
    // Apply some text to the optgroup element
    var optgroupText = applyToObject(arrayEntry, allBindings.get('optgroupText'), arrayEntry);
    optgroup.setAttribute('label', ko.utils.unwrapObservable(optgroupText));

    return [optgroup];
}
@DenisPitcher

Thanks a ton for all who contributed to this. to add to the solution put forth by @davhdavh and @okas, if you want to support decendant properties using dot notation then update the apply to update function as follows:

        function applyToObject(object, predicate, defaultValue) {
            var predicateType = typeof predicate;
            if (predicateType == "function") // Given a function; run it against the data value
            {
                return predicate(object);
            }
            else if (predicateType == "string") // Given a string; treat it as a property name on the data value
            {
                if (predicate.indexOf('.') == -1) {
                    return object[predicate];
                }
                var arr = predicate.split('.');
                while (arr.length && (object = object[arr.shift()]));
                return object;

            }
            else // Given no optionsText arg; use the data value itself
            {
                return defaultValue;
            }
        }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
This page is out of date. Refresh to see the latest.
View
77 build/output/knockout-latest.debug.js
@@ -2,7 +2,7 @@
// (c) Steven Sanderson - http://knockoutjs.com/
// License: MIT (http://www.opensource.org/licenses/mit-license.php)
-(function(window,undefined){
+(function(window,undefined){
var ko = window["ko"] = {};
// Google Closure Compiler helpers (used only to make the minified file smaller)
ko.exportSymbol = function(publicPath, object) {
@@ -1415,24 +1415,60 @@ ko.bindingHandlers['options'] = {
ko.selectExtensions.writeValue(option, undefined);
element.appendChild(option);
}
- for (var i = 0, j = value.length; i < j; i++) {
- var option = document.createElement("OPTION");
- var optionValue = typeof allBindings['optionsValue'] == "string" ? value[i][allBindings['optionsValue']] : value[i];
-
- // Pick some text to appear in the drop-down list for this data value
- var optionsTextValue = allBindings['optionsText'];
- if (typeof optionsTextValue == "function")
- optionText = optionsTextValue(value[i]); // Given a function; run it against the data value
- else if (typeof optionsTextValue == "string")
- optionText = value[i][optionsTextValue]; // Given a string; treat it as a property name on the data value
+
+ var optionsGroupNamesValue = allBindings['optionsGroupNames'];
+
+ // Group values into optgroups
+ var groupedOptions = [];
+ var optionsGroupValue = allBindings['optionsGroup']; // undefined if not given
+ for(var i=0, j=value.length; i < j; i++) {
+ var optionsGroup = null;
+ if (typeof optionsGroupValue == "function")
+ optionsGroup = optionsGroupValue(value[i]);
+ else if (typeof optionsGroupValue == "string")
+ optionsGroup = value[i][optionsGroupValue];
else
- optionText = optionValue; // Given no optionsText arg; use the data value itself
-
- optionValue = ko.utils.unwrapObservable(optionValue);
- optionText = ko.utils.unwrapObservable(optionText);
- ko.selectExtensions.writeValue(option, optionValue);
- option.innerHTML = optionText.toString();
- element.appendChild(option);
+ optionsGroup = "";
+ if (typeof groupedOptions[optionsGroup] == "undefined")
+ groupedOptions[optionsGroup] = [];
+ groupedOptions[optionsGroup].push(value[i]);
+ }
+
+ // Create HTML elements
+ for (var groupName in groupedOptions) {
+ var optgroup = null;
+ // Add an OPTGROUP for all groups except for ""
+ if(groupName != "") {
+ optgroup = document.createElement("OPTGROUP");
+ optgroup.label = groupName;
+ element.appendChild(optgroup);
+ }
+
+ // Create HTML elements for options within this group
+ for (var i = 0, j = groupedOptions[groupName].length; i < j; i++) {
+ var valueGroup = groupedOptions[groupName];
+ var option = document.createElement("OPTION");
+ var optionValue = typeof allBindings['optionsValue'] == "string" ? valueGroup[i][allBindings['optionsValue']] : valueGroup[groupName][i];
+
+ // Pick some text to appear in the drop-down list for this data value
+ var optionsTextValue = allBindings['optionsText'];
+ if (typeof optionsTextValue == "function")
+ optionText = optionsTextValue(valueGroup[i]); // Given a function; run it against the data value
+ else if (typeof optionsTextValue == "string")
+ optionText = valueGroup[i][optionsTextValue]; // Given a string; treat it as a property name on the data value
+ else
+ optionText = optionValue; // Given no optionsText arg; use the data value itself
+
+ optionValue = ko.utils.unwrapObservable(optionValue);
+ optionText = ko.utils.unwrapObservable(optionText);
+ ko.selectExtensions.writeValue(option, optionValue);
+ option.innerHTML = optionText.toString();
+
+ if (optgroup != null)
+ optgroup.appendChild(option);
+ else
+ element.appendChild(option);
+ }
}
// IE6 doesn't like us to assign selection to OPTION nodes before they're added to the document.
@@ -1445,7 +1481,7 @@ ko.bindingHandlers['options'] = {
countSelectionsRetained++;
}
}
-
+
if (previousScrollTop)
element.scrollTop = previousScrollTop;
}
@@ -1629,6 +1665,7 @@ ko.bindingHandlers['attr'] = {
}
}
};
+
ko.templateEngine = function () {
this['renderTemplate'] = function (templateName, data, options) {
throw "Override renderTemplate in your ko.templateEngine subclass";
@@ -2141,4 +2178,4 @@ ko.jqueryTmplTemplateEngine.prototype = new ko.templateEngine();
// Use this one by default
ko.setTemplateEngine(new ko.jqueryTmplTemplateEngine());
-ko.exportSymbol('ko.jqueryTmplTemplateEngine', ko.jqueryTmplTemplateEngine);})(window);
+ko.exportSymbol('ko.jqueryTmplTemplateEngine', ko.jqueryTmplTemplateEngine);})(window);
View
133 build/output/knockout-latest.js
@@ -2,72 +2,73 @@
// (c) Steven Sanderson - http://knockoutjs.com/
// License: MIT (http://www.opensource.org/licenses/mit-license.php)
-(function(window,undefined){
-function c(d){throw d;}var n=void 0,o=null,p=window.ko={};p.b=function(d,e){for(var b=d.split("."),a=window,f=0;f<b.length-1;f++)a=a[b[f]];a[b[b.length-1]]=e};p.i=function(d,e,b){d[e]=b};
-p.a=new function(){function d(a,f){if(a.tagName!="INPUT"||!a.type)return!1;if(f.toLowerCase()!="click")return!1;var b=a.type.toLowerCase();return b=="checkbox"||b=="radio"}var e=/^(\s|\u00A0)+|(\s|\u00A0)+$/g,b={click:1,dblclick:1,mousedown:1,mouseup:1,mousemove:1,mouseover:1,mouseout:1,mouseenter:1,mouseleave:1};return{ba:["authenticity_token",/^__RequestVerificationToken(_.*)?$/],h:function(a,f){for(var b=0,d=a.length;b<d;b++)f(a[b])},g:function(a,f){if(typeof a.indexOf=="function")return a.indexOf(f);
-for(var b=0,d=a.length;b<d;b++)if(a[b]===f)return b;return-1},xa:function(a,f,b){for(var d=0,e=a.length;d<e;d++)if(f.call(b,a[d]))return a[d];return o},M:function(a,f){var b=p.a.g(a,f);b>=0&&a.splice(b,1)},$:function(a){for(var a=a||[],f=[],b=0,d=a.length;b<d;b++)p.a.g(f,a[b])<0&&f.push(a[b]);return f},L:function(a,f){for(var a=a||[],b=[],d=0,e=a.length;d<e;d++)b.push(f(a[d]));return b},K:function(a,f){for(var a=a||[],b=[],d=0,e=a.length;d<e;d++)f(a[d])&&b.push(a[d]);return b},z:function(a,f){for(var b=
-0,d=f.length;b<d;b++)a.push(f[b])},aa:function(a){for(;a.firstChild;)p.removeNode(a.firstChild)},Wa:function(a,f){p.a.aa(a);f&&p.a.h(f,function(f){a.appendChild(f)})},ka:function(a,f){var b=a.nodeType?[a]:a;if(b.length>0){for(var d=b[0],e=d.parentNode,j=0,k=f.length;j<k;j++)e.insertBefore(f[j],d);j=0;for(k=b.length;j<k;j++)p.removeNode(b[j])}},ma:function(a,f){navigator.userAgent.indexOf("MSIE 6")>=0?a.setAttribute("selected",f):a.selected=f},ca:function(a,f){if(!a||a.nodeType!=1)return[];var b=[];
-a.getAttribute(f)!==o&&b.push(a);for(var d=a.getElementsByTagName("*"),e=0,j=d.length;e<j;e++)d[e].getAttribute(f)!==o&&b.push(d[e]);return b},m:function(a){return(a||"").replace(e,"")},Za:function(a,b){for(var d=[],e=(a||"").split(b),i=0,j=e.length;i<j;i++){var k=p.a.m(e[i]);k!==""&&d.push(k)}return d},Xa:function(a,b){a=a||"";if(b.length>a.length)return!1;return a.substring(0,b.length)===b},Ha:function(a,b){if(b===n)return(new Function("return "+a))();return(new Function("sc","with(sc) { return ("+
-a+") }"))(b)},Fa:function(a,b){if(b.compareDocumentPosition)return(b.compareDocumentPosition(a)&16)==16;for(;a!=o;){if(a==b)return!0;a=a.parentNode}return!1},O:function(a){return p.a.Fa(a,document)},t:function(a,b,e){if(typeof jQuery!="undefined"){if(d(a,b))var g=e,e=function(a,b){var f=this.checked;if(b)this.checked=b.Aa!==!0;g.call(this,a);this.checked=f};jQuery(a).bind(b,e)}else typeof a.addEventListener=="function"?a.addEventListener(b,e,!1):typeof a.attachEvent!="undefined"?a.attachEvent("on"+
-b,function(b){e.call(a,b)}):c(Error("Browser doesn't support addEventListener or attachEvent"))},qa:function(a,f){(!a||!a.nodeType)&&c(Error("element must be a DOM node when calling triggerEvent"));if(typeof jQuery!="undefined"){var e=[];d(a,f)&&e.push({Aa:a.checked});jQuery(a).trigger(f,e)}else if(typeof document.createEvent=="function")typeof a.dispatchEvent=="function"?(e=document.createEvent(f in b?"MouseEvents":"HTMLEvents"),e.initEvent(f,!0,!0,window,0,0,0,0,0,!1,!1,!1,!1,0,a),a.dispatchEvent(e)):
-c(Error("The supplied element doesn't support dispatchEvent"));else if(typeof a.fireEvent!="undefined"){if(f=="click"&&a.tagName=="INPUT"&&(a.type.toLowerCase()=="checkbox"||a.type.toLowerCase()=="radio"))a.checked=a.checked!==!0;a.fireEvent("on"+f)}else c(Error("Browser doesn't support triggering events"))},d:function(a){return p.C(a)?a():a},Ea:function(a,b){return p.a.g((a.className||"").split(/\s+/),b)>=0},pa:function(a,b,d){var e=p.a.Ea(a,b);if(d&&!e)a.className=(a.className||"")+" "+b;else if(e&&
-!d){for(var d=(a.className||"").split(/\s+/),e="",i=0;i<d.length;i++)d[i]!=b&&(e+=d[i]+" ");a.className=p.a.m(e)}},Ta:function(a,b){for(var a=p.a.d(a),b=p.a.d(b),d=[],e=a;e<=b;e++)d.push(e);return d},ga:function(a){for(var b=[],d=a.length-1;d>=0;d--)b.push(a[d]);return b},Q:/MSIE 6/i.test(navigator.userAgent),Ma:/MSIE 7/i.test(navigator.userAgent),da:function(a,b){for(var d=p.a.ga(a.getElementsByTagName("INPUT")).concat(p.a.ga(a.getElementsByTagName("TEXTAREA"))),e=typeof b=="string"?function(a){return a.name===
-b}:function(a){return b.test(a.name)},i=[],j=d.length-1;j>=0;j--)e(d[j])&&i.push(d[j]);return i},F:function(a){if(typeof a=="string"&&(a=p.a.m(a))){if(window.JSON&&window.JSON.parse)return window.JSON.parse(a);return(new Function("return "+a))()}return o},V:function(a){(typeof JSON=="undefined"||typeof JSON.stringify=="undefined")&&c(Error("Cannot find JSON.stringify(). Some browsers (e.g., IE < 8) don't support it natively, but you can overcome this by adding a script reference to json2.js, downloadable from http://www.json.org/json2.js"));
-return JSON.stringify(p.a.d(a))},Sa:function(a,b,d){var d=d||{},e=d.params||{},i=d.includeFields||this.ba,j=a;if(typeof a=="object"&&a.tagName=="FORM")for(var j=a.action,k=i.length-1;k>=0;k--)for(var m=p.a.da(a,i[k]),q=m.length-1;q>=0;q--)e[m[q].name]=m[q].value;var b=p.a.d(b),l=document.createElement("FORM");l.style.display="none";l.action=j;l.method="post";for(var s in b)a=document.createElement("INPUT"),a.name=s,a.value=p.a.V(p.a.d(b[s])),l.appendChild(a);for(s in e)a=document.createElement("INPUT"),
-a.name=s,a.value=e[s],l.appendChild(a);document.body.appendChild(l);d.submitter?d.submitter(l):l.submit();setTimeout(function(){l.parentNode.removeChild(l)},0)}}};p.b("ko.utils",p.a);p.b("ko.utils.arrayForEach",p.a.h);p.b("ko.utils.arrayFirst",p.a.xa);p.b("ko.utils.arrayFilter",p.a.K);p.b("ko.utils.arrayGetDistinctValues",p.a.$);p.b("ko.utils.arrayIndexOf",p.a.g);p.b("ko.utils.arrayMap",p.a.L);p.b("ko.utils.arrayPushAll",p.a.z);p.b("ko.utils.arrayRemoveItem",p.a.M);
+(function(window,undefined){
+function d(c){throw c;}var n=void 0,o=null,p=window.ko={};p.b=function(c,e){for(var b=c.split("."),a=window,f=0;f<b.length-1;f++)a=a[b[f]];a[b[b.length-1]]=e};p.i=function(c,e,b){c[e]=b};
+p.a=new function(){function c(a,f){if(a.tagName!="INPUT"||!a.type)return!1;if(f.toLowerCase()!="click")return!1;var b=a.type.toLowerCase();return b=="checkbox"||b=="radio"}var e=/^(\s|\u00A0)+|(\s|\u00A0)+$/g,b={click:1,dblclick:1,mousedown:1,mouseup:1,mousemove:1,mouseover:1,mouseout:1,mouseenter:1,mouseleave:1};return{ba:["authenticity_token",/^__RequestVerificationToken(_.*)?$/],h:function(a,f){for(var b=0,c=a.length;b<c;b++)f(a[b])},g:function(a,f){if(typeof a.indexOf=="function")return a.indexOf(f);
+for(var b=0,c=a.length;b<c;b++)if(a[b]===f)return b;return-1},xa:function(a,f,b){for(var c=0,e=a.length;c<e;c++)if(f.call(b,a[c]))return a[c];return o},M:function(a,f){var b=p.a.g(a,f);b>=0&&a.splice(b,1)},$:function(a){for(var a=a||[],f=[],b=0,c=a.length;b<c;b++)p.a.g(f,a[b])<0&&f.push(a[b]);return f},L:function(a,f){for(var a=a||[],b=[],c=0,e=a.length;c<e;c++)b.push(f(a[c]));return b},K:function(a,f){for(var a=a||[],b=[],c=0,e=a.length;c<e;c++)f(a[c])&&b.push(a[c]);return b},z:function(a,f){for(var b=
+0,c=f.length;b<c;b++)a.push(f[b])},aa:function(a){for(;a.firstChild;)p.removeNode(a.firstChild)},Wa:function(a,b){p.a.aa(a);b&&p.a.h(b,function(b){a.appendChild(b)})},ka:function(a,b){var c=a.nodeType?[a]:a;if(c.length>0){for(var e=c[0],h=e.parentNode,j=0,k=b.length;j<k;j++)h.insertBefore(b[j],e);j=0;for(k=c.length;j<k;j++)p.removeNode(c[j])}},ma:function(a,b){navigator.userAgent.indexOf("MSIE 6")>=0?a.setAttribute("selected",b):a.selected=b},ca:function(a,b){if(!a||a.nodeType!=1)return[];var c=[];
+a.getAttribute(b)!==o&&c.push(a);for(var e=a.getElementsByTagName("*"),h=0,j=e.length;h<j;h++)e[h].getAttribute(b)!==o&&c.push(e[h]);return c},m:function(a){return(a||"").replace(e,"")},Za:function(a,b){for(var c=[],e=(a||"").split(b),h=0,j=e.length;h<j;h++){var k=p.a.m(e[h]);k!==""&&c.push(k)}return c},Xa:function(a,b){a=a||"";if(b.length>a.length)return!1;return a.substring(0,b.length)===b},Ha:function(a,b){if(b===n)return(new Function("return "+a))();return(new Function("sc","with(sc) { return ("+
+a+") }"))(b)},Fa:function(a,b){if(b.compareDocumentPosition)return(b.compareDocumentPosition(a)&16)==16;for(;a!=o;){if(a==b)return!0;a=a.parentNode}return!1},O:function(a){return p.a.Fa(a,document)},t:function(a,b,e){if(typeof jQuery!="undefined"){if(c(a,b))var g=e,e=function(a,b){var f=this.checked;if(b)this.checked=b.Aa!==!0;g.call(this,a);this.checked=f};jQuery(a).bind(b,e)}else typeof a.addEventListener=="function"?a.addEventListener(b,e,!1):typeof a.attachEvent!="undefined"?a.attachEvent("on"+
+b,function(b){e.call(a,b)}):d(Error("Browser doesn't support addEventListener or attachEvent"))},qa:function(a,f){(!a||!a.nodeType)&&d(Error("element must be a DOM node when calling triggerEvent"));if(typeof jQuery!="undefined"){var e=[];c(a,f)&&e.push({Aa:a.checked});jQuery(a).trigger(f,e)}else if(typeof document.createEvent=="function")typeof a.dispatchEvent=="function"?(e=document.createEvent(f in b?"MouseEvents":"HTMLEvents"),e.initEvent(f,!0,!0,window,0,0,0,0,0,!1,!1,!1,!1,0,a),a.dispatchEvent(e)):
+d(Error("The supplied element doesn't support dispatchEvent"));else if(typeof a.fireEvent!="undefined"){if(f=="click"&&a.tagName=="INPUT"&&(a.type.toLowerCase()=="checkbox"||a.type.toLowerCase()=="radio"))a.checked=a.checked!==!0;a.fireEvent("on"+f)}else d(Error("Browser doesn't support triggering events"))},d:function(a){return p.C(a)?a():a},Ea:function(a,b){return p.a.g((a.className||"").split(/\s+/),b)>=0},pa:function(a,b,c){var e=p.a.Ea(a,b);if(c&&!e)a.className=(a.className||"")+" "+b;else if(e&&
+!c){for(var c=(a.className||"").split(/\s+/),e="",h=0;h<c.length;h++)c[h]!=b&&(e+=c[h]+" ");a.className=p.a.m(e)}},Ta:function(a,b){for(var a=p.a.d(a),b=p.a.d(b),c=[],e=a;e<=b;e++)c.push(e);return c},ga:function(a){for(var b=[],c=a.length-1;c>=0;c--)b.push(a[c]);return b},Q:/MSIE 6/i.test(navigator.userAgent),Ma:/MSIE 7/i.test(navigator.userAgent),da:function(a,b){for(var c=p.a.ga(a.getElementsByTagName("INPUT")).concat(p.a.ga(a.getElementsByTagName("TEXTAREA"))),e=typeof b=="string"?function(a){return a.name===
+b}:function(a){return b.test(a.name)},h=[],j=c.length-1;j>=0;j--)e(c[j])&&h.push(c[j]);return h},F:function(a){if(typeof a=="string"&&(a=p.a.m(a))){if(window.JSON&&window.JSON.parse)return window.JSON.parse(a);return(new Function("return "+a))()}return o},V:function(a){(typeof JSON=="undefined"||typeof JSON.stringify=="undefined")&&d(Error("Cannot find JSON.stringify(). Some browsers (e.g., IE < 8) don't support it natively, but you can overcome this by adding a script reference to json2.js, downloadable from http://www.json.org/json2.js"));
+return JSON.stringify(p.a.d(a))},Sa:function(a,b,c){var c=c||{},e=c.params||{},h=c.includeFields||this.ba,j=a;if(typeof a=="object"&&a.tagName=="FORM")for(var j=a.action,k=h.length-1;k>=0;k--)for(var l=p.a.da(a,h[k]),q=l.length-1;q>=0;q--)e[l[q].name]=l[q].value;var b=p.a.d(b),m=document.createElement("FORM");m.style.display="none";m.action=j;m.method="post";for(var s in b)a=document.createElement("INPUT"),a.name=s,a.value=p.a.V(p.a.d(b[s])),m.appendChild(a);for(s in e)a=document.createElement("INPUT"),
+a.name=s,a.value=e[s],m.appendChild(a);document.body.appendChild(m);c.submitter?c.submitter(m):m.submit();setTimeout(function(){m.parentNode.removeChild(m)},0)}}};p.b("ko.utils",p.a);p.b("ko.utils.arrayForEach",p.a.h);p.b("ko.utils.arrayFirst",p.a.xa);p.b("ko.utils.arrayFilter",p.a.K);p.b("ko.utils.arrayGetDistinctValues",p.a.$);p.b("ko.utils.arrayIndexOf",p.a.g);p.b("ko.utils.arrayMap",p.a.L);p.b("ko.utils.arrayPushAll",p.a.z);p.b("ko.utils.arrayRemoveItem",p.a.M);
p.b("ko.utils.fieldsIncludedWithJsonPost",p.a.ba);p.b("ko.utils.getElementsHavingAttribute",p.a.ca);p.b("ko.utils.getFormFields",p.a.da);p.b("ko.utils.postJson",p.a.Sa);p.b("ko.utils.parseJson",p.a.F);p.b("ko.utils.registerEventHandler",p.a.t);p.b("ko.utils.stringifyJson",p.a.V);p.b("ko.utils.range",p.a.Ta);p.b("ko.utils.toggleDomNodeCssClass",p.a.pa);p.b("ko.utils.triggerEvent",p.a.qa);p.b("ko.utils.unwrapObservable",p.a.d);
-Function.prototype.bind||(Function.prototype.bind=function(d){var e=this,b=Array.prototype.slice.call(arguments),d=b.shift();return function(){return e.apply(d,b.concat(Array.prototype.slice.call(arguments)))}});
-p.a.e=new function(){var d=0,e="__ko__"+(new Date).getTime(),b={};return{get:function(a,b){var d=p.a.e.getAll(a,!1);return d===n?n:d[b]},set:function(a,b,d){d===n&&p.a.e.getAll(a,!1)===n||(p.a.e.getAll(a,!0)[b]=d)},getAll:function(a,f){var h=a[e];if(!h){if(!f)return;h=a[e]="ko"+d++;b[h]={}}return b[h]},clear:function(a){var d=a[e];d&&(delete b[d],a[e]=o)}}};
-p.a.p=new function(){function d(a,d){var e=p.a.e.get(a,b);e===n&&d&&(e=[],p.a.e.set(a,b,e));return e}function e(a){var b=d(a,!1);if(b)for(var b=b.slice(0),e=0;e<b.length;e++)b[e](a);p.a.e.clear(a);typeof jQuery=="function"&&typeof jQuery.cleanData=="function"&&jQuery.cleanData([a])}var b="__ko_domNodeDisposal__"+(new Date).getTime();return{Z:function(a,b){typeof b!="function"&&c(Error("Callback must be a function"));d(a,!0).push(b)},ja:function(a,e){var h=d(a,!1);h&&(p.a.M(h,e),h.length==0&&p.a.e.set(a,
-b,n))},u:function(a){if(!(a.nodeType!=1&&a.nodeType!=9)){e(a);var b=[];p.a.z(b,a.getElementsByTagName("*"));for(var a=0,d=b.length;a<d;a++)e(b[a])}},removeNode:function(a){p.u(a);a.parentNode&&a.parentNode.removeChild(a)}}};p.u=p.a.p.u;p.removeNode=p.a.p.removeNode;p.b("ko.cleanNode",p.u);p.b("ko.removeNode",p.removeNode);p.b("ko.utils.domNodeDisposal",p.a.p);p.b("ko.utils.domNodeDisposal.addDisposeCallback",p.a.p.Z);p.b("ko.utils.domNodeDisposal.removeDisposeCallback",p.a.p.ja);
-p.k=function(){function d(){return((1+Math.random())*4294967296|0).toString(16).substring(1)}function e(a,b){if(a)if(a.nodeType==8){var d=p.k.ha(a.nodeValue);d!=o&&b.push({Da:a,Pa:d})}else if(a.nodeType==1)for(var d=0,g=a.childNodes,i=g.length;d<i;d++)e(g[d],b)}var b={};return{S:function(a){typeof a!="function"&&c(Error("You can only pass a function to ko.memoization.memoize()"));var e=d()+d();b[e]=a;return"<\!--[ko_memo:"+e+"]--\>"},ra:function(a,d){var e=b[a];e===n&&c(Error("Couldn't find any memo with ID "+
-a+". Perhaps it's already been unmemoized."));try{return e.apply(o,d||[]),!0}finally{delete b[a]}},sa:function(a,b){var d=[];e(a,d);for(var g=0,i=d.length;g<i;g++){var j=d[g].Da,k=[j];b&&p.a.z(k,b);p.k.ra(d[g].Pa,k);j.nodeValue="";j.parentNode&&j.parentNode.removeChild(j)}},ha:function(a){return(a=a.match(/^\[ko_memo\:(.*?)\]$/))?a[1]:o}}}();p.b("ko.memoization",p.k);p.b("ko.memoization.memoize",p.k.S);p.b("ko.memoization.unmemoize",p.k.ra);p.b("ko.memoization.parseMemoText",p.k.ha);
-p.b("ko.memoization.unmemoizeDomNodeAndDescendants",p.k.sa);p.Ya=function(d,e){this.za=d;this.n=function(){this.La=!0;e()}.bind(this);p.i(this,"dispose",this.n)};p.W=function(){var d=[];this.X=function(e,b){var a=b?e.bind(b):e,f=new p.Ya(a,function(){p.a.M(d,f)});d.push(f);return f};this.w=function(e){p.a.h(d.slice(0),function(b){b&&b.La!==!0&&b.za(e)})};this.Ja=function(){return d.length};p.i(this,"subscribe",this.X);p.i(this,"notifySubscribers",this.w);p.i(this,"getSubscriptionsCount",this.Ja)};
-p.fa=function(d){return typeof d.X=="function"&&typeof d.w=="function"};p.b("ko.subscribable",p.W);p.b("ko.isSubscribable",p.fa);p.A=function(){var d=[];return{ya:function(){d.push([])},end:function(){return d.pop()},ia:function(e){p.fa(e)||c("Only subscribable things can act as dependencies");d.length>0&&d[d.length-1].push(e)}}}();var v={undefined:!0,"boolean":!0,number:!0,string:!0};function w(d,e){return d===o||typeof d in v?d===e:!1}
-p.s=function(d){function e(){if(arguments.length>0){if(!e.equalityComparer||!e.equalityComparer(b,arguments[0]))b=arguments[0],e.w(b);return this}else return p.A.ia(e),b}var b=d;e.o=p.s;e.H=function(){e.w(b)};e.equalityComparer=w;p.W.call(e);p.i(e,"valueHasMutated",e.H);return e};p.C=function(d){if(d===o||d===n||d.o===n)return!1;if(d.o===p.s)return!0;return p.C(d.o)};p.D=function(d){if(typeof d=="function"&&d.o===p.s)return!0;if(typeof d=="function"&&d.o===p.j&&d.Ka)return!0;return!1};
+Function.prototype.bind||(Function.prototype.bind=function(c){var e=this,b=Array.prototype.slice.call(arguments),c=b.shift();return function(){return e.apply(c,b.concat(Array.prototype.slice.call(arguments)))}});
+p.a.e=new function(){var c=0,e="__ko__"+(new Date).getTime(),b={};return{get:function(a,b){var c=p.a.e.getAll(a,!1);return c===n?n:c[b]},set:function(a,b,c){c===n&&p.a.e.getAll(a,!1)===n||(p.a.e.getAll(a,!0)[b]=c)},getAll:function(a,f){var i=a[e];if(!i){if(!f)return;i=a[e]="ko"+c++;b[i]={}}return b[i]},clear:function(a){var c=a[e];c&&(delete b[c],a[e]=o)}}};
+p.a.p=new function(){function c(a,c){var e=p.a.e.get(a,b);e===n&&c&&(e=[],p.a.e.set(a,b,e));return e}function e(a){var b=c(a,!1);if(b)for(var b=b.slice(0),e=0;e<b.length;e++)b[e](a);p.a.e.clear(a);typeof jQuery=="function"&&typeof jQuery.cleanData=="function"&&jQuery.cleanData([a])}var b="__ko_domNodeDisposal__"+(new Date).getTime();return{Z:function(a,b){typeof b!="function"&&d(Error("Callback must be a function"));c(a,!0).push(b)},ja:function(a,e){var i=c(a,!1);i&&(p.a.M(i,e),i.length==0&&p.a.e.set(a,
+b,n))},u:function(a){if(!(a.nodeType!=1&&a.nodeType!=9)){e(a);var b=[];p.a.z(b,a.getElementsByTagName("*"));for(var a=0,c=b.length;a<c;a++)e(b[a])}},removeNode:function(a){p.u(a);a.parentNode&&a.parentNode.removeChild(a)}}};p.u=p.a.p.u;p.removeNode=p.a.p.removeNode;p.b("ko.cleanNode",p.u);p.b("ko.removeNode",p.removeNode);p.b("ko.utils.domNodeDisposal",p.a.p);p.b("ko.utils.domNodeDisposal.addDisposeCallback",p.a.p.Z);p.b("ko.utils.domNodeDisposal.removeDisposeCallback",p.a.p.ja);
+p.k=function(){function c(){return((1+Math.random())*4294967296|0).toString(16).substring(1)}function e(a,b){if(a)if(a.nodeType==8){var c=p.k.ha(a.nodeValue);c!=o&&b.push({Da:a,Pa:c})}else if(a.nodeType==1)for(var c=0,g=a.childNodes,h=g.length;c<h;c++)e(g[c],b)}var b={};return{S:function(a){typeof a!="function"&&d(Error("You can only pass a function to ko.memoization.memoize()"));var e=c()+c();b[e]=a;return"<\!--[ko_memo:"+e+"]--\>"},ra:function(a,c){var e=b[a];e===n&&d(Error("Couldn't find any memo with ID "+
+a+". Perhaps it's already been unmemoized."));try{return e.apply(o,c||[]),!0}finally{delete b[a]}},sa:function(a,b){var c=[];e(a,c);for(var g=0,h=c.length;g<h;g++){var j=c[g].Da,k=[j];b&&p.a.z(k,b);p.k.ra(c[g].Pa,k);j.nodeValue="";j.parentNode&&j.parentNode.removeChild(j)}},ha:function(a){return(a=a.match(/^\[ko_memo\:(.*?)\]$/))?a[1]:o}}}();p.b("ko.memoization",p.k);p.b("ko.memoization.memoize",p.k.S);p.b("ko.memoization.unmemoize",p.k.ra);p.b("ko.memoization.parseMemoText",p.k.ha);
+p.b("ko.memoization.unmemoizeDomNodeAndDescendants",p.k.sa);p.Ya=function(c,e){this.za=c;this.n=function(){this.La=!0;e()}.bind(this);p.i(this,"dispose",this.n)};p.W=function(){var c=[];this.X=function(e,b){var a=b?e.bind(b):e,f=new p.Ya(a,function(){p.a.M(c,f)});c.push(f);return f};this.w=function(e){p.a.h(c.slice(0),function(b){b&&b.La!==!0&&b.za(e)})};this.Ja=function(){return c.length};p.i(this,"subscribe",this.X);p.i(this,"notifySubscribers",this.w);p.i(this,"getSubscriptionsCount",this.Ja)};
+p.fa=function(c){return typeof c.X=="function"&&typeof c.w=="function"};p.b("ko.subscribable",p.W);p.b("ko.isSubscribable",p.fa);p.A=function(){var c=[];return{ya:function(){c.push([])},end:function(){return c.pop()},ia:function(e){p.fa(e)||d("Only subscribable things can act as dependencies");c.length>0&&c[c.length-1].push(e)}}}();var v={undefined:!0,"boolean":!0,number:!0,string:!0};function w(c,e){return c===o||typeof c in v?c===e:!1}
+p.s=function(c){function e(){if(arguments.length>0){if(!e.equalityComparer||!e.equalityComparer(b,arguments[0]))b=arguments[0],e.w(b);return this}else return p.A.ia(e),b}var b=c;e.o=p.s;e.H=function(){e.w(b)};e.equalityComparer=w;p.W.call(e);p.i(e,"valueHasMutated",e.H);return e};p.C=function(c){if(c===o||c===n||c.o===n)return!1;if(c.o===p.s)return!0;return p.C(c.o)};p.D=function(c){if(typeof c=="function"&&c.o===p.s)return!0;if(typeof c=="function"&&c.o===p.j&&c.Ka)return!0;return!1};
p.b("ko.observable",p.s);p.b("ko.isObservable",p.C);p.b("ko.isWriteableObservable",p.D);
-p.Ra=function(d){arguments.length==0&&(d=[]);d!==o&&d!==n&&!("length"in d)&&c(new "The argument passed when initializing an observable array must be an array, or null, or undefined.");var e=new p.s(d);p.a.h(["pop","push","reverse","shift","sort","splice","unshift"],function(b){e[b]=function(){var a=e(),a=a[b].apply(a,arguments);e.H();return a}});p.a.h(["slice"],function(b){e[b]=function(){var a=e();return a[b].apply(a,arguments)}});e.remove=function(b){for(var a=e(),d=[],h=[],g=typeof b=="function"?
-b:function(a){return a===b},i=0,j=a.length;i<j;i++){var k=a[i];g(k)?h.push(k):d.push(k)}e(d);return h};e.Ua=function(b){if(b===n){var a=e();e([]);return a}if(!b)return[];return e.remove(function(a){return p.a.g(b,a)>=0})};e.N=function(b){for(var a=e(),d=typeof b=="function"?b:function(a){return a===b},h=a.length-1;h>=0;h--)d(a[h])&&(a[h]._destroy=!0);e.H()};e.Ca=function(b){if(b===n)return e.N(function(){return!0});if(!b)return[];return e.N(function(a){return p.a.g(b,a)>=0})};e.indexOf=function(b){var a=
-e();return p.a.g(a,b)};e.replace=function(b,a){var d=e.indexOf(b);d>=0&&(e()[d]=a,e.H())};p.i(e,"remove",e.remove);p.i(e,"removeAll",e.Ua);p.i(e,"destroy",e.N);p.i(e,"destroyAll",e.Ca);p.i(e,"indexOf",e.indexOf);return e};p.b("ko.observableArray",p.Ra);
-p.j=function(d,e,b){function a(){p.a.h(l,function(a){a.n()});l=[]}function f(b){a();p.a.h(b,function(a){l.push(a.X(h))})}function h(){if(j&&typeof b.disposeWhen=="function"&&b.disposeWhen())g.n();else{try{p.A.ya(),i=b.owner?b.read.call(b.owner):b.read()}finally{var a=p.a.$(p.A.end());f(a)}g.w(i);j=!0}}function g(){if(arguments.length>0)if(typeof b.write==="function"){var a=arguments[0];b.owner?b.write.call(b.owner,a):b.write(a)}else c("Cannot write a value to a dependentObservable unless you specify a 'write' option. If you wish to read the current value, don't pass any parameters.");
-else return j||h(),p.A.ia(g),i}var i,j=!1;d&&typeof d=="object"?b=d:(b=b||{},b.read=d||b.read,b.owner=e||b.owner);typeof b.read!="function"&&c("Pass a function that returns the value of the dependentObservable");var k=typeof b.disposeWhenNodeIsRemoved=="object"?b.disposeWhenNodeIsRemoved:o,m=o;if(k){m=function(){g.n()};p.a.p.Z(k,m);var q=b.disposeWhen;b.disposeWhen=function(){return!p.a.O(k)||typeof q=="function"&&q()}}var l=[];g.o=p.j;g.Ia=function(){return l.length};g.Ka=typeof b.write==="function";
-g.n=function(){k&&p.a.p.ja(k,m);a()};p.W.call(g);b.deferEvaluation!==!0&&h();p.i(g,"dispose",g.n);p.i(g,"getDependenciesCount",g.Ia);return g};p.j.o=p.s;p.b("ko.dependentObservable",p.j);
-(function(){function d(a,f,h){h=h||new b;a=f(a);if(!(typeof a=="object"&&a!==o&&a!==n))return a;var g=a instanceof Array?[]:{};h.save(a,g);e(a,function(b){var e=f(a[b]);switch(typeof e){case "boolean":case "number":case "string":case "function":g[b]=e;break;case "object":case "undefined":var k=h.get(e);g[b]=k!==n?k:d(e,f,h)}});return g}function e(a,b){if(a instanceof Array)for(var d=0;d<a.length;d++)b(d);else for(d in a)b(d)}function b(){var a=[],b=[];this.save=function(d,e){var i=p.a.g(a,d);i>=0?
-b[i]=e:(a.push(d),b.push(e))};this.get=function(d){d=p.a.g(a,d);return d>=0?b[d]:n}}p.oa=function(a){arguments.length==0&&c(Error("When calling ko.toJS, pass the object you want to convert."));return d(a,function(a){for(var b=0;p.C(a)&&b<10;b++)a=a();return a})};p.toJSON=function(a){a=p.oa(a);return p.a.V(a)}})();p.b("ko.toJS",p.oa);p.b("ko.toJSON",p.toJSON);
-p.f={l:function(d){if(d.tagName=="OPTION"){if(d.__ko__hasDomDataOptionValue__===!0)return p.a.e.get(d,p.c.options.T);return d.getAttribute("value")}else return d.tagName=="SELECT"?d.selectedIndex>=0?p.f.l(d.options[d.selectedIndex]):n:d.value},I:function(d,e){if(d.tagName=="OPTION")switch(typeof e){case "string":case "number":p.a.e.set(d,p.c.options.T,n);"__ko__hasDomDataOptionValue__"in d&&delete d.__ko__hasDomDataOptionValue__;d.value=e;break;default:p.a.e.set(d,p.c.options.T,e),d.__ko__hasDomDataOptionValue__=
-!0,d.value=""}else if(d.tagName=="SELECT")for(var b=d.options.length-1;b>=0;b--){if(p.f.l(d.options[b])==e){d.selectedIndex=b;break}}else{if(e===o||e===n)e="";d.value=e}}};p.b("ko.selectExtensions",p.f);p.b("ko.selectExtensions.readValue",p.f.l);p.b("ko.selectExtensions.writeValue",p.f.I);
-p.r=function(){function d(a,b){return a.replace(e,function(a,d){return b[d]})}var e=/\[ko_token_(\d+)\]/g,b=/^[\_$a-z][\_$a-z0-9]*(\[.*?\])*(\.[\_$a-z][\_$a-z0-9]*(\[.*?\])*)*$/i,a=["true","false"];return{F:function(a){a=p.a.m(a);if(a.length<3)return{};for(var b=[],e=o,i,j=a.charAt(0)=="{"?1:0;j<a.length;j++){var k=a.charAt(j);if(e===o)switch(k){case '"':case "'":case "/":e=j;i=k;break;case "{":e=j;i="}";break;case "[":e=j,i="]"}else if(k==i){k=a.substring(e,j+1);b.push(k);var m="[ko_token_"+(b.length-
-1)+"]",a=a.substring(0,e)+m+a.substring(j+1);j-=k.length-m.length;e=o}}e={};a=a.split(",");i=0;for(j=a.length;i<j;i++){var m=a[i],q=m.indexOf(":");q>0&&q<m.length-1&&(k=p.a.m(m.substring(0,q)),m=p.a.m(m.substring(q+1)),k.charAt(0)=="{"&&(k=k.substring(1)),m.charAt(m.length-1)=="}"&&(m=m.substring(0,m.length-1)),k=p.a.m(d(k,b)),m=p.a.m(d(m,b)),e[k]=m)}return e},P:function(d){var e=p.r.F(d),g=[],i;for(i in e){var j=e[i],k;k=j;k=p.a.g(a,p.a.m(k).toLowerCase())>=0?!1:k.match(b)!==o;k&&(g.length>0&&g.push(", "),
-g.push(i+" : function(__ko_value) { "+j+" = __ko_value; }"))}g.length>0&&(d=d+", '_ko_property_writers' : { "+g.join("")+" } ");return d}}}();p.b("ko.jsonExpressionRewriting",p.r);p.b("ko.jsonExpressionRewriting.parseJson",p.r.F);p.b("ko.jsonExpressionRewriting.insertPropertyAccessorsIntoJson",p.r.P);p.c={};
-p.J=function(d,e,b,a){function f(a){return function(){return i[a]}}function h(){return i}var g=!0,a=a||"data-bind",i;new p.j(function(){var j;if(!(j=typeof e=="function"?e():e)){var k=d.getAttribute(a);try{var m=" { "+p.r.P(k)+" } ";j=p.a.Ha(m,b===o?window:b)}catch(q){c(Error("Unable to parse binding attribute.\nMessage: "+q+";\nAttribute value: "+k))}}i=j;if(g)for(var l in i)p.c[l]&&typeof p.c[l].init=="function"&&(0,p.c[l].init)(d,f(l),h,b);for(l in i)p.c[l]&&typeof p.c[l].update=="function"&&(0,p.c[l].update)(d,
-f(l),h,b)},o,{disposeWhenNodeIsRemoved:d});g=!1};p.ua=function(d,e){e&&e.nodeType==n&&c(Error("ko.applyBindings: first parameter should be your view model; second parameter should be a DOM node (note: this is a breaking change since KO version 1.05)"));var e=e||window.document.body,b=p.a.ca(e,"data-bind");p.a.h(b,function(a){p.J(a,o,d)})};p.b("ko.bindingHandlers",p.c);p.b("ko.applyBindings",p.ua);p.b("ko.applyBindingsToNode",p.J);
-p.a.h(["click"],function(d){p.c[d]={init:function(e,b,a,f){return p.c.event.init.call(this,e,function(){var a={};a[d]=b();return a},a,f)}}});p.c.event={init:function(d,e,b,a){var f=e()||{},h;for(h in f)(function(){var f=h;typeof f=="string"&&p.a.t(d,f,function(d){var h,k=e()[f],m=b();try{h=k.apply(a,arguments)}finally{if(h!==!0)d.preventDefault?d.preventDefault():d.returnValue=!1}if(m[f+"Bubble"]===!1)d.cancelBubble=!0,d.stopPropagation&&d.stopPropagation()})})()}};
-p.c.submit={init:function(d,e,b,a){typeof e()!="function"&&c(Error("The value for a submit binding must be a function to invoke on submit"));p.a.t(d,"submit",function(b){var h,g=e();try{h=g.call(a,d)}finally{if(h!==!0)b.preventDefault?b.preventDefault():b.returnValue=!1}})}};p.c.visible={update:function(d,e){var b=p.a.d(e()),a=d.style.display!="none";if(b&&!a)d.style.display="";else if(!b&&a)d.style.display="none"}};
-p.c.enable={update:function(d,e){var b=p.a.d(e());if(b&&d.disabled)d.removeAttribute("disabled");else if(!b&&!d.disabled)d.disabled=!0}};p.c.disable={update:function(d,e){p.c.enable.update(d,function(){return!p.a.d(e())})}};
-p.c.value={init:function(d,e,b){var a=b().valueUpdate||"change",f=!1;p.a.Xa(a,"after")&&(f=!0,a=a.substring(5));var h=f?function(a){setTimeout(a,0)}:function(a){a()};p.a.t(d,a,function(){h(function(){var a=e(),f=p.f.l(d);p.D(a)?a(f):(a=b(),a._ko_property_writers&&a._ko_property_writers.value&&a._ko_property_writers.value(f))})})},update:function(d,e){var b=p.a.d(e()),a=p.f.l(d),f=b!=a;b===0&&a!==0&&a!=="0"&&(f=!0);f&&(a=function(){p.f.I(d,b)},a(),d.tagName=="SELECT"&&setTimeout(a,0));d.tagName=="SELECT"&&
-(a=p.f.l(d),a!==b&&p.a.qa(d,"change"))}};
-p.c.options={update:function(d,e,b){d.tagName!="SELECT"&&c(Error("options binding applies only to SELECT elements"));var a=p.a.L(p.a.K(d.childNodes,function(a){return a.tagName&&a.tagName=="OPTION"&&a.selected}),function(a){return p.f.l(a)||a.innerText||a.textContent}),f=d.scrollTop,h=p.a.d(e());p.a.aa(d);if(h){var g=b();typeof h.length!="number"&&(h=[h]);if(g.optionsCaption){var i=document.createElement("OPTION");i.innerHTML=g.optionsCaption;p.f.I(i,n);d.appendChild(i)}b=0;for(e=h.length;b<e;b++){var i=
-document.createElement("OPTION"),j=typeof g.optionsValue=="string"?h[b][g.optionsValue]:h[b],k=g.optionsText;optionText=typeof k=="function"?k(h[b]):typeof k=="string"?h[b][k]:j;j=p.a.d(j);optionText=p.a.d(optionText);p.f.I(i,j);i.innerHTML=optionText.toString();d.appendChild(i)}h=d.getElementsByTagName("OPTION");b=g=0;for(e=h.length;b<e;b++)p.a.g(a,p.f.l(h[b]))>=0&&(p.a.ma(h[b],!0),g++);if(f)d.scrollTop=f}}};p.c.options.T="__ko.bindingHandlers.options.optionValueDomData__";
-p.c.selectedOptions={ea:function(d){for(var e=[],d=d.childNodes,b=0,a=d.length;b<a;b++){var f=d[b];f.tagName=="OPTION"&&f.selected&&e.push(p.f.l(f))}return e},init:function(d,e,b){p.a.t(d,"change",function(){var a=e();p.D(a)?a(p.c.selectedOptions.ea(this)):(a=b(),a._ko_property_writers&&a._ko_property_writers.value&&a._ko_property_writers.value(p.c.selectedOptions.ea(this)))})},update:function(d,e){d.tagName!="SELECT"&&c(Error("values binding applies only to SELECT elements"));var b=p.a.d(e());if(b&&
-typeof b.length=="number")for(var a=d.childNodes,f=0,h=a.length;f<h;f++){var g=a[f];g.tagName=="OPTION"&&p.a.ma(g,p.a.g(b,p.f.l(g))>=0)}}};p.c.text={update:function(d,e){var b=p.a.d(e());if(b===o||b===n)b="";typeof d.innerText=="string"?d.innerText=b:d.textContent=b}};p.c.html={update:function(d,e){var b=p.a.d(e());if(b===o||b===n)b="";d.innerHTML=b}};p.c.css={update:function(d,e){var b=p.a.d(e()||{}),a;for(a in b)if(typeof a=="string"){var f=p.a.d(b[a]);p.a.pa(d,a,f)}}};
-p.c.style={update:function(d,e){var b=p.a.d(e()||{}),a;for(a in b)if(typeof a=="string"){var f=p.a.d(b[a]);d.style[a]=f||""}}};p.c.uniqueName={init:function(d,e){if(e())d.name="ko_unique_"+ ++p.c.uniqueName.Ba,p.a.Q&&d.mergeAttributes(document.createElement("<input name='"+d.name+"'/>"),!1)}};p.c.uniqueName.Ba=0;
-p.c.checked={init:function(d,e,b){p.a.t(d,"click",function(){var a;if(d.type=="checkbox")a=d.checked;else if(d.type=="radio"&&d.checked)a=d.value;else return;var f=e();d.type=="checkbox"&&p.a.d(f)instanceof Array?(a=p.a.g(p.a.d(f),d.value),d.checked&&a<0?f.push(d.value):!d.checked&&a>=0&&f.splice(a,1)):p.D(f)?f()!==a&&f(a):(f=b(),f._ko_property_writers&&f._ko_property_writers.checked&&f._ko_property_writers.checked(a))});d.type=="radio"&&!d.name&&p.c.uniqueName.init(d,function(){return!0})},update:function(d,
-e){var b=p.a.d(e());if(d.type=="checkbox")d.checked=b instanceof Array?p.a.g(b,d.value)>=0:b,b&&p.a.Q&&d.mergeAttributes(document.createElement("<input type='checkbox' checked='checked' />"),!1);else if(d.type=="radio")d.checked=d.value==b,d.value==b&&(p.a.Q||p.a.Ma)&&d.mergeAttributes(document.createElement("<input type='radio' checked='checked' />"),!1)}};
-p.c.attr={update:function(d,e){var b=p.a.d(e())||{},a;for(a in b)if(typeof a=="string"){var f=p.a.d(b[a]);f===!1||f===o||f===n?d.removeAttribute(a):d.setAttribute(a,f.toString())}}};
-p.Y=function(){this.renderTemplate=function(){c("Override renderTemplate in your ko.templateEngine subclass")};this.isTemplateRewritten=function(){c("Override isTemplateRewritten in your ko.templateEngine subclass")};this.rewriteTemplate=function(){c("Override rewriteTemplate in your ko.templateEngine subclass")};this.createJavaScriptEvaluatorBlock=function(){c("Override createJavaScriptEvaluatorBlock in your ko.templateEngine subclass")}};p.b("ko.templateEngine",p.Y);
-p.G=function(){var d=/(<[a-z]+\d*(\s+(?!data-bind=)[a-z0-9]+(=(\"[^\"]*\"|\'[^\']*\'))?)*\s+)data-bind=(["'])([\s\S]*?)\5/gi;return{Ga:function(d,b){b.isTemplateRewritten(d)||b.rewriteTemplate(d,function(a){return p.G.Qa(a,b)})},Qa:function(e,b){return e.replace(d,function(a,d,e,g,i,j,k){a=p.r.P(k);return b.createJavaScriptEvaluatorBlock("ko.templateRewriting.applyMemoizedBindingsToNextSibling(function() { return (function() { return { "+a+" } })() })")+d})},va:function(d){return p.k.S(function(b,
-a){b.nextSibling&&p.J(b.nextSibling,d,a)})}}}();p.b("ko.templateRewriting",p.G);p.b("ko.templateRewriting.applyMemoizedBindingsToNextSibling",p.G.va);
-(function(){function d(b,a,d,h,g){var i=p.a.d(h),g=g||{},j=g.templateEngine||e;p.G.Ga(d,j);d=j.renderTemplate(d,i,g);(typeof d.length!="number"||d.length>0&&typeof d[0].nodeType!="number")&&c("Template engine must return an array of DOM nodes");d&&p.a.h(d,function(a){p.k.sa(a,[h])});switch(a){case "replaceChildren":p.a.Wa(b,d);break;case "replaceNode":p.a.ka(b,d);break;case "ignoreTargetNode":break;default:c(Error("Unknown renderMode: "+a))}g.afterRender&&g.afterRender(d,h);return d}var e;p.na=function(b){b!=
-n&&!(b instanceof p.Y)&&c("templateEngine must inherit from ko.templateEngine");e=b};p.U=function(b,a,f,h,g){f=f||{};(f.templateEngine||e)==n&&c("Set a template engine before calling renderTemplate");g=g||"replaceChildren";if(h){var i=h.nodeType?h:h.length>0?h[0]:o;return new p.j(function(){var e=typeof b=="function"?b(a):b,e=d(h,g,e,a,f);g=="replaceNode"&&(h=e,i=h.nodeType?h:h.length>0?h[0]:o)},o,{disposeWhen:function(){return!i||!p.a.O(i)},disposeWhenNodeIsRemoved:i&&g=="replaceNode"?i.parentNode:
-i})}else return p.k.S(function(d){p.U(b,a,f,d,"replaceNode")})};p.Va=function(b,a,e,h){return new p.j(function(){var g=p.a.d(a)||[];typeof g.length=="undefined"&&(g=[g]);g=p.a.K(g,function(a){return e.includeDestroyed||!a._destroy});p.a.la(h,g,function(a){var g=typeof b=="function"?b(a):b;return d(o,"ignoreTargetNode",g,a,e)},e)},o,{disposeWhenNodeIsRemoved:h})};p.c.template={update:function(b,a,d,e){a=p.a.d(a());d=typeof a=="string"?a:a.name;if(typeof a.foreach!="undefined")e=p.Va(d,a.foreach||[],
-{templateOptions:a.templateOptions,afterAdd:a.afterAdd,beforeRemove:a.beforeRemove,includeDestroyed:a.includeDestroyed,afterRender:a.afterRender},b);else var g=a.data,e=p.U(d,typeof g=="undefined"?e:g,{templateOptions:a.templateOptions,afterRender:a.afterRender},b);(a=p.a.e.get(b,"__ko__templateSubscriptionDomDataKey__"))&&typeof a.n=="function"&&a.n();p.a.e.set(b,"__ko__templateSubscriptionDomDataKey__",e)}}})();p.b("ko.setTemplateEngine",p.na);p.b("ko.renderTemplate",p.U);
-p.a.v=function(d,e,b){if(b===n)return p.a.v(d,e,1)||p.a.v(d,e,10)||p.a.v(d,e,Number.MAX_VALUE);else{for(var d=d||[],e=e||[],a=d,f=e,h=[],g=0;g<=f.length;g++)h[g]=[];for(var g=0,i=Math.min(a.length,b);g<=i;g++)h[0][g]=g;g=1;for(i=Math.min(f.length,b);g<=i;g++)h[g][0]=g;for(var i=a.length,j,k=f.length,g=1;g<=i;g++){var m=Math.min(k,g+b);for(j=Math.max(1,g-b);j<=m;j++)h[j][g]=a[g-1]===f[j-1]?h[j-1][g-1]:Math.min(h[j-1][g]===n?Number.MAX_VALUE:h[j-1][g]+1,h[j][g-1]===n?Number.MAX_VALUE:h[j][g-1]+1)}b=
-d.length;a=e.length;f=[];g=h[a][b];if(g===n)h=o;else{for(;b>0||a>0;){i=h[a][b];j=a>0?h[a-1][b]:g+1;k=b>0?h[a][b-1]:g+1;m=a>0&&b>0?h[a-1][b-1]:g+1;if(j===n||j<i-1)j=g+1;if(k===n||k<i-1)k=g+1;m<i-1&&(m=g+1);j<=k&&j<m?(f.push({status:"added",value:e[a-1]}),a--):(k<j&&k<m?f.push({status:"deleted",value:d[b-1]}):(f.push({status:"retained",value:d[b-1]}),a--),b--)}h=f.reverse()}return h}};p.b("ko.utils.compareArrays",p.a.v);
-(function(){function d(d,b,a){var f=[],d=p.j(function(){var d=b(a)||[];f.length>0&&p.a.ka(f,d);f.splice(0,f.length);p.a.z(f,d)},o,{disposeWhenNodeIsRemoved:d,disposeWhen:function(){return f.length==0||!p.a.O(f[0])}});return{Oa:f,j:d}}p.a.la=function(e,b,a,f){for(var b=b||[],f=f||{},h=p.a.e.get(e,"setDomNodeChildrenFromArrayMapping_lastMappingResult")===n,g=p.a.e.get(e,"setDomNodeChildrenFromArrayMapping_lastMappingResult")||[],i=p.a.L(g,function(a){return a.wa}),j=p.a.v(i,b),b=[],k=0,m=[],i=[],q=
-o,l=0,s=j.length;l<s;l++)switch(j[l].status){case "retained":var r=g[k];b.push(r);r.B.length>0&&(q=r.B[r.B.length-1]);k++;break;case "deleted":g[k].j.n();p.a.h(g[k].B,function(a){m.push({element:a,index:l,value:j[l].value});q=a});k++;break;case "added":var t=d(e,a,j[l].value),r=t.Oa;b.push({wa:j[l].value,B:r,j:t.j});for(var t=0,x=r.length;t<x;t++){var u=r[t];i.push({element:u,index:l,value:j[l].value});q==o?e.firstChild?e.insertBefore(u,e.firstChild):e.appendChild(u):q.nextSibling?e.insertBefore(u,
-q.nextSibling):e.appendChild(u);q=u}}p.a.h(m,function(a){p.u(a.element)});a=!1;if(!h){if(f.afterAdd)for(l=0;l<i.length;l++)f.afterAdd(i[l].element,i[l].index,i[l].value);if(f.beforeRemove){for(l=0;l<m.length;l++)f.beforeRemove(m[l].element,m[l].index,m[l].value);a=!0}}a||p.a.h(m,function(a){a.element.parentNode&&a.element.parentNode.removeChild(a.element)});p.a.e.set(e,"setDomNodeChildrenFromArrayMapping_lastMappingResult",b)}})();p.b("ko.utils.setDomNodeChildrenFromArrayMapping",p.a.la);
-p.R=function(){this.q=function(){if(typeof jQuery=="undefined"||!jQuery.tmpl)return 0;if(jQuery.tmpl.tag){if(jQuery.tmpl.tag.tmpl&&jQuery.tmpl.tag.tmpl.open&&jQuery.tmpl.tag.tmpl.open.toString().indexOf("__")>=0)return 3;return 2}return 1}();this.getTemplateNode=function(d){var b=document.getElementById(d);b==o&&c(Error("Cannot find template with ID="+d));return b};var d=RegExp("__ko_apos__","g");this.renderTemplate=function(e,b,a){a=a||{};this.q==0&&c(Error("jquery.tmpl not detected.\nTo use KO's default template engine, reference jQuery and jquery.tmpl. See Knockout installation documentation for more details."));
-if(this.q==1)return e='<script type="text/html">'+this.getTemplateNode(e).text+"<\/script>",b=jQuery.tmpl(e,b)[0].text.replace(d,"'"),jQuery.clean([b],document);if(!(e in jQuery.template)){var f=this.getTemplateNode(e).text;jQuery.template(e,f)}b=[b];b=jQuery.tmpl(e,b,a.templateOptions);b.appendTo(document.createElement("div"));jQuery.fragments={};return b};this.isTemplateRewritten=function(d){if(d in jQuery.template)return!0;return this.getTemplateNode(d).Na===!0};this.rewriteTemplate=function(d,
-b){var a=this.getTemplateNode(d),f=b(a.text);this.q==1&&(f=p.a.m(f),f=f.replace(/([\s\S]*?)(\${[\s\S]*?}|{{[\=a-z][\s\S]*?}}|$)/g,function(a,b,d){return b.replace(/\'/g,"__ko_apos__")+d}));a.text=f;a.Na=!0};this.createJavaScriptEvaluatorBlock=function(d){if(this.q==1)return"{{= "+d+"}}";return"{{ko_code ((function() { return "+d+" })()) }}"};this.ta=function(d,b){document.write("<script type='text/html' id='"+d+"'>"+b+"<\/script>")};p.i(this,"addTemplate",this.ta);this.q>1&&(jQuery.tmpl.tag.ko_code=
+p.Ra=function(c){arguments.length==0&&(c=[]);c!==o&&c!==n&&!("length"in c)&&d(new "The argument passed when initializing an observable array must be an array, or null, or undefined.");var e=new p.s(c);p.a.h(["pop","push","reverse","shift","sort","splice","unshift"],function(b){e[b]=function(){var a=e(),a=a[b].apply(a,arguments);e.H();return a}});p.a.h(["slice"],function(b){e[b]=function(){var a=e();return a[b].apply(a,arguments)}});e.remove=function(b){for(var a=e(),c=[],i=[],g=typeof b=="function"?
+b:function(a){return a===b},h=0,j=a.length;h<j;h++){var k=a[h];g(k)?i.push(k):c.push(k)}e(c);return i};e.Ua=function(b){if(b===n){var a=e();e([]);return a}if(!b)return[];return e.remove(function(a){return p.a.g(b,a)>=0})};e.N=function(b){for(var a=e(),c=typeof b=="function"?b:function(a){return a===b},i=a.length-1;i>=0;i--)c(a[i])&&(a[i]._destroy=!0);e.H()};e.Ca=function(b){if(b===n)return e.N(function(){return!0});if(!b)return[];return e.N(function(a){return p.a.g(b,a)>=0})};e.indexOf=function(b){var a=
+e();return p.a.g(a,b)};e.replace=function(b,a){var c=e.indexOf(b);c>=0&&(e()[c]=a,e.H())};p.i(e,"remove",e.remove);p.i(e,"removeAll",e.Ua);p.i(e,"destroy",e.N);p.i(e,"destroyAll",e.Ca);p.i(e,"indexOf",e.indexOf);return e};p.b("ko.observableArray",p.Ra);
+p.j=function(c,e,b){function a(){p.a.h(m,function(a){a.n()});m=[]}function f(b){a();p.a.h(b,function(a){m.push(a.X(i))})}function i(){if(j&&typeof b.disposeWhen=="function"&&b.disposeWhen())g.n();else{try{p.A.ya(),h=b.owner?b.read.call(b.owner):b.read()}finally{var a=p.a.$(p.A.end());f(a)}g.w(h);j=!0}}function g(){if(arguments.length>0)if(typeof b.write==="function"){var a=arguments[0];b.owner?b.write.call(b.owner,a):b.write(a)}else d("Cannot write a value to a dependentObservable unless you specify a 'write' option. If you wish to read the current value, don't pass any parameters.");
+else return j||i(),p.A.ia(g),h}var h,j=!1;c&&typeof c=="object"?b=c:(b=b||{},b.read=c||b.read,b.owner=e||b.owner);typeof b.read!="function"&&d("Pass a function that returns the value of the dependentObservable");var k=typeof b.disposeWhenNodeIsRemoved=="object"?b.disposeWhenNodeIsRemoved:o,l=o;if(k){l=function(){g.n()};p.a.p.Z(k,l);var q=b.disposeWhen;b.disposeWhen=function(){return!p.a.O(k)||typeof q=="function"&&q()}}var m=[];g.o=p.j;g.Ia=function(){return m.length};g.Ka=typeof b.write==="function";
+g.n=function(){k&&p.a.p.ja(k,l);a()};p.W.call(g);b.deferEvaluation!==!0&&i();p.i(g,"dispose",g.n);p.i(g,"getDependenciesCount",g.Ia);return g};p.j.o=p.s;p.b("ko.dependentObservable",p.j);
+(function(){function c(a,f,i){i=i||new b;a=f(a);if(!(typeof a=="object"&&a!==o&&a!==n))return a;var g=a instanceof Array?[]:{};i.save(a,g);e(a,function(b){var e=f(a[b]);switch(typeof e){case "boolean":case "number":case "string":case "function":g[b]=e;break;case "object":case "undefined":var k=i.get(e);g[b]=k!==n?k:c(e,f,i)}});return g}function e(a,b){if(a instanceof Array)for(var c=0;c<a.length;c++)b(c);else for(c in a)b(c)}function b(){var a=[],b=[];this.save=function(c,e){var h=p.a.g(a,c);h>=0?
+b[h]=e:(a.push(c),b.push(e))};this.get=function(c){c=p.a.g(a,c);return c>=0?b[c]:n}}p.oa=function(a){arguments.length==0&&d(Error("When calling ko.toJS, pass the object you want to convert."));return c(a,function(a){for(var b=0;p.C(a)&&b<10;b++)a=a();return a})};p.toJSON=function(a){a=p.oa(a);return p.a.V(a)}})();p.b("ko.toJS",p.oa);p.b("ko.toJSON",p.toJSON);
+p.f={l:function(c){if(c.tagName=="OPTION"){if(c.__ko__hasDomDataOptionValue__===!0)return p.a.e.get(c,p.c.options.T);return c.getAttribute("value")}else return c.tagName=="SELECT"?c.selectedIndex>=0?p.f.l(c.options[c.selectedIndex]):n:c.value},I:function(c,e){if(c.tagName=="OPTION")switch(typeof e){case "string":case "number":p.a.e.set(c,p.c.options.T,n);"__ko__hasDomDataOptionValue__"in c&&delete c.__ko__hasDomDataOptionValue__;c.value=e;break;default:p.a.e.set(c,p.c.options.T,e),c.__ko__hasDomDataOptionValue__=
+!0,c.value=""}else if(c.tagName=="SELECT")for(var b=c.options.length-1;b>=0;b--){if(p.f.l(c.options[b])==e){c.selectedIndex=b;break}}else{if(e===o||e===n)e="";c.value=e}}};p.b("ko.selectExtensions",p.f);p.b("ko.selectExtensions.readValue",p.f.l);p.b("ko.selectExtensions.writeValue",p.f.I);
+p.r=function(){function c(a,b){return a.replace(e,function(a,c){return b[c]})}var e=/\[ko_token_(\d+)\]/g,b=/^[\_$a-z][\_$a-z0-9]*(\[.*?\])*(\.[\_$a-z][\_$a-z0-9]*(\[.*?\])*)*$/i,a=["true","false"];return{F:function(a){a=p.a.m(a);if(a.length<3)return{};for(var b=[],e=o,h,j=a.charAt(0)=="{"?1:0;j<a.length;j++){var k=a.charAt(j);if(e===o)switch(k){case '"':case "'":case "/":e=j;h=k;break;case "{":e=j;h="}";break;case "[":e=j,h="]"}else if(k==h){k=a.substring(e,j+1);b.push(k);var l="[ko_token_"+(b.length-
+1)+"]",a=a.substring(0,e)+l+a.substring(j+1);j-=k.length-l.length;e=o}}e={};a=a.split(",");h=0;for(j=a.length;h<j;h++){var l=a[h],q=l.indexOf(":");q>0&&q<l.length-1&&(k=p.a.m(l.substring(0,q)),l=p.a.m(l.substring(q+1)),k.charAt(0)=="{"&&(k=k.substring(1)),l.charAt(l.length-1)=="}"&&(l=l.substring(0,l.length-1)),k=p.a.m(c(k,b)),l=p.a.m(c(l,b)),e[k]=l)}return e},P:function(c){var e=p.r.F(c),g=[],h;for(h in e){var j=e[h],k;k=j;k=p.a.g(a,p.a.m(k).toLowerCase())>=0?!1:k.match(b)!==o;k&&(g.length>0&&g.push(", "),
+g.push(h+" : function(__ko_value) { "+j+" = __ko_value; }"))}g.length>0&&(c=c+", '_ko_property_writers' : { "+g.join("")+" } ");return c}}}();p.b("ko.jsonExpressionRewriting",p.r);p.b("ko.jsonExpressionRewriting.parseJson",p.r.F);p.b("ko.jsonExpressionRewriting.insertPropertyAccessorsIntoJson",p.r.P);p.c={};
+p.J=function(c,e,b,a){function f(a){return function(){return h[a]}}function i(){return h}var g=!0,a=a||"data-bind",h;new p.j(function(){var j;if(!(j=typeof e=="function"?e():e)){var k=c.getAttribute(a);try{var l=" { "+p.r.P(k)+" } ";j=p.a.Ha(l,b===o?window:b)}catch(q){d(Error("Unable to parse binding attribute.\nMessage: "+q+";\nAttribute value: "+k))}}h=j;if(g)for(var m in h)p.c[m]&&typeof p.c[m].init=="function"&&(0,p.c[m].init)(c,f(m),i,b);for(m in h)p.c[m]&&typeof p.c[m].update=="function"&&(0,p.c[m].update)(c,
+f(m),i,b)},o,{disposeWhenNodeIsRemoved:c});g=!1};p.ua=function(c,e){e&&e.nodeType==n&&d(Error("ko.applyBindings: first parameter should be your view model; second parameter should be a DOM node (note: this is a breaking change since KO version 1.05)"));var e=e||window.document.body,b=p.a.ca(e,"data-bind");p.a.h(b,function(a){p.J(a,o,c)})};p.b("ko.bindingHandlers",p.c);p.b("ko.applyBindings",p.ua);p.b("ko.applyBindingsToNode",p.J);
+p.a.h(["click"],function(c){p.c[c]={init:function(e,b,a,f){return p.c.event.init.call(this,e,function(){var a={};a[c]=b();return a},a,f)}}});p.c.event={init:function(c,e,b,a){var f=e()||{},i;for(i in f)(function(){var f=i;typeof f=="string"&&p.a.t(c,f,function(c){var i,k=e()[f],l=b();try{i=k.apply(a,arguments)}finally{if(i!==!0)c.preventDefault?c.preventDefault():c.returnValue=!1}if(l[f+"Bubble"]===!1)c.cancelBubble=!0,c.stopPropagation&&c.stopPropagation()})})()}};
+p.c.submit={init:function(c,e,b,a){typeof e()!="function"&&d(Error("The value for a submit binding must be a function to invoke on submit"));p.a.t(c,"submit",function(b){var i,g=e();try{i=g.call(a,c)}finally{if(i!==!0)b.preventDefault?b.preventDefault():b.returnValue=!1}})}};p.c.visible={update:function(c,e){var b=p.a.d(e()),a=c.style.display!="none";if(b&&!a)c.style.display="";else if(!b&&a)c.style.display="none"}};
+p.c.enable={update:function(c,e){var b=p.a.d(e());if(b&&c.disabled)c.removeAttribute("disabled");else if(!b&&!c.disabled)c.disabled=!0}};p.c.disable={update:function(c,e){p.c.enable.update(c,function(){return!p.a.d(e())})}};
+p.c.value={init:function(c,e,b){var a=b().valueUpdate||"change",f=!1;p.a.Xa(a,"after")&&(f=!0,a=a.substring(5));var i=f?function(a){setTimeout(a,0)}:function(a){a()};p.a.t(c,a,function(){i(function(){var a=e(),f=p.f.l(c);p.D(a)?a(f):(a=b(),a._ko_property_writers&&a._ko_property_writers.value&&a._ko_property_writers.value(f))})})},update:function(c,e){var b=p.a.d(e()),a=p.f.l(c),f=b!=a;b===0&&a!==0&&a!=="0"&&(f=!0);f&&(a=function(){p.f.I(c,b)},a(),c.tagName=="SELECT"&&setTimeout(a,0));c.tagName=="SELECT"&&
+(a=p.f.l(c),a!==b&&p.a.qa(c,"change"))}};
+p.c.options={update:function(c,e,b){c.tagName!="SELECT"&&d(Error("options binding applies only to SELECT elements"));var a=p.a.L(p.a.K(c.childNodes,function(a){return a.tagName&&a.tagName=="OPTION"&&a.selected}),function(a){return p.f.l(a)||a.innerText||a.textContent}),f=c.scrollTop,i=p.a.d(e());p.a.aa(c);if(i){var g=b();typeof i.length!="number"&&(i=[i]);if(g.optionsCaption){var h=document.createElement("OPTION");h.innerHTML=g.optionsCaption;p.f.I(h,n);c.appendChild(h)}for(var j=[],h=g.optionsGroup,
+b=0,e=i.length;b<e;b++){var k=o,k=typeof h=="function"?h(i[b]):typeof h=="string"?i[b][h]:"";typeof j[k]=="undefined"&&(j[k]=[]);j[k].push(i[b])}for(var l in j){i=o;if(l!="")i=document.createElement("OPTGROUP"),i.label=l,c.appendChild(i);b=0;for(e=j[l].length;b<e;b++){var k=j[l],h=document.createElement("OPTION"),q=typeof g.optionsValue=="string"?k[b][g.optionsValue]:k[l][b],m=g.optionsText;optionText=typeof m=="function"?m(k[b]):typeof m=="string"?k[b][m]:q;q=p.a.d(q);optionText=p.a.d(optionText);
+p.f.I(h,q);h.innerHTML=optionText.toString();i!=o?i.appendChild(h):c.appendChild(h)}}l=c.getElementsByTagName("OPTION");b=g=0;for(e=l.length;b<e;b++)p.a.g(a,p.f.l(l[b]))>=0&&(p.a.ma(l[b],!0),g++);if(f)c.scrollTop=f}}};p.c.options.T="__ko.bindingHandlers.options.optionValueDomData__";
+p.c.selectedOptions={ea:function(c){for(var e=[],c=c.childNodes,b=0,a=c.length;b<a;b++){var f=c[b];f.tagName=="OPTION"&&f.selected&&e.push(p.f.l(f))}return e},init:function(c,e,b){p.a.t(c,"change",function(){var a=e();p.D(a)?a(p.c.selectedOptions.ea(this)):(a=b(),a._ko_property_writers&&a._ko_property_writers.value&&a._ko_property_writers.value(p.c.selectedOptions.ea(this)))})},update:function(c,e){c.tagName!="SELECT"&&d(Error("values binding applies only to SELECT elements"));var b=p.a.d(e());if(b&&
+typeof b.length=="number")for(var a=c.childNodes,f=0,i=a.length;f<i;f++){var g=a[f];g.tagName=="OPTION"&&p.a.ma(g,p.a.g(b,p.f.l(g))>=0)}}};p.c.text={update:function(c,e){var b=p.a.d(e());if(b===o||b===n)b="";typeof c.innerText=="string"?c.innerText=b:c.textContent=b}};p.c.html={update:function(c,e){var b=p.a.d(e());if(b===o||b===n)b="";c.innerHTML=b}};p.c.css={update:function(c,e){var b=p.a.d(e()||{}),a;for(a in b)if(typeof a=="string"){var f=p.a.d(b[a]);p.a.pa(c,a,f)}}};
+p.c.style={update:function(c,e){var b=p.a.d(e()||{}),a;for(a in b)if(typeof a=="string"){var f=p.a.d(b[a]);c.style[a]=f||""}}};p.c.uniqueName={init:function(c,e){if(e())c.name="ko_unique_"+ ++p.c.uniqueName.Ba,p.a.Q&&c.mergeAttributes(document.createElement("<input name='"+c.name+"'/>"),!1)}};p.c.uniqueName.Ba=0;
+p.c.checked={init:function(c,e,b){p.a.t(c,"click",function(){var a;if(c.type=="checkbox")a=c.checked;else if(c.type=="radio"&&c.checked)a=c.value;else return;var f=e();c.type=="checkbox"&&p.a.d(f)instanceof Array?(a=p.a.g(p.a.d(f),c.value),c.checked&&a<0?f.push(c.value):!c.checked&&a>=0&&f.splice(a,1)):p.D(f)?f()!==a&&f(a):(f=b(),f._ko_property_writers&&f._ko_property_writers.checked&&f._ko_property_writers.checked(a))});c.type=="radio"&&!c.name&&p.c.uniqueName.init(c,function(){return!0})},update:function(c,
+e){var b=p.a.d(e());if(c.type=="checkbox")c.checked=b instanceof Array?p.a.g(b,c.value)>=0:b,b&&p.a.Q&&c.mergeAttributes(document.createElement("<input type='checkbox' checked='checked' />"),!1);else if(c.type=="radio")c.checked=c.value==b,c.value==b&&(p.a.Q||p.a.Ma)&&c.mergeAttributes(document.createElement("<input type='radio' checked='checked' />"),!1)}};
+p.c.attr={update:function(c,e){var b=p.a.d(e())||{},a;for(a in b)if(typeof a=="string"){var f=p.a.d(b[a]);f===!1||f===o||f===n?c.removeAttribute(a):c.setAttribute(a,f.toString())}}};
+p.Y=function(){this.renderTemplate=function(){d("Override renderTemplate in your ko.templateEngine subclass")};this.isTemplateRewritten=function(){d("Override isTemplateRewritten in your ko.templateEngine subclass")};this.rewriteTemplate=function(){d("Override rewriteTemplate in your ko.templateEngine subclass")};this.createJavaScriptEvaluatorBlock=function(){d("Override createJavaScriptEvaluatorBlock in your ko.templateEngine subclass")}};p.b("ko.templateEngine",p.Y);
+p.G=function(){var c=/(<[a-z]+\d*(\s+(?!data-bind=)[a-z0-9]+(=(\"[^\"]*\"|\'[^\']*\'))?)*\s+)data-bind=(["'])([\s\S]*?)\5/gi;return{Ga:function(c,b){b.isTemplateRewritten(c)||b.rewriteTemplate(c,function(a){return p.G.Qa(a,b)})},Qa:function(e,b){return e.replace(c,function(a,c,e,g,h,j,k){a=p.r.P(k);return b.createJavaScriptEvaluatorBlock("ko.templateRewriting.applyMemoizedBindingsToNextSibling(function() { return (function() { return { "+a+" } })() })")+c})},va:function(c){return p.k.S(function(b,
+a){b.nextSibling&&p.J(b.nextSibling,c,a)})}}}();p.b("ko.templateRewriting",p.G);p.b("ko.templateRewriting.applyMemoizedBindingsToNextSibling",p.G.va);
+(function(){function c(b,a,c,i,g){var h=p.a.d(i),g=g||{},j=g.templateEngine||e;p.G.Ga(c,j);c=j.renderTemplate(c,h,g);(typeof c.length!="number"||c.length>0&&typeof c[0].nodeType!="number")&&d("Template engine must return an array of DOM nodes");c&&p.a.h(c,function(a){p.k.sa(a,[i])});switch(a){case "replaceChildren":p.a.Wa(b,c);break;case "replaceNode":p.a.ka(b,c);break;case "ignoreTargetNode":break;default:d(Error("Unknown renderMode: "+a))}g.afterRender&&g.afterRender(c,i);return c}var e;p.na=function(b){b!=
+n&&!(b instanceof p.Y)&&d("templateEngine must inherit from ko.templateEngine");e=b};p.U=function(b,a,f,i,g){f=f||{};(f.templateEngine||e)==n&&d("Set a template engine before calling renderTemplate");g=g||"replaceChildren";if(i){var h=i.nodeType?i:i.length>0?i[0]:o;return new p.j(function(){var e=typeof b=="function"?b(a):b,e=c(i,g,e,a,f);g=="replaceNode"&&(i=e,h=i.nodeType?i:i.length>0?i[0]:o)},o,{disposeWhen:function(){return!h||!p.a.O(h)},disposeWhenNodeIsRemoved:h&&g=="replaceNode"?h.parentNode:
+h})}else return p.k.S(function(c){p.U(b,a,f,c,"replaceNode")})};p.Va=function(b,a,e,i){return new p.j(function(){var g=p.a.d(a)||[];typeof g.length=="undefined"&&(g=[g]);g=p.a.K(g,function(a){return e.includeDestroyed||!a._destroy});p.a.la(i,g,function(a){var g=typeof b=="function"?b(a):b;return c(o,"ignoreTargetNode",g,a,e)},e)},o,{disposeWhenNodeIsRemoved:i})};p.c.template={update:function(b,a,c,e){a=p.a.d(a());c=typeof a=="string"?a:a.name;if(typeof a.foreach!="undefined")e=p.Va(c,a.foreach||[],
+{templateOptions:a.templateOptions,afterAdd:a.afterAdd,beforeRemove:a.beforeRemove,includeDestroyed:a.includeDestroyed,afterRender:a.afterRender},b);else var g=a.data,e=p.U(c,typeof g=="undefined"?e:g,{templateOptions:a.templateOptions,afterRender:a.afterRender},b);(a=p.a.e.get(b,"__ko__templateSubscriptionDomDataKey__"))&&typeof a.n=="function"&&a.n();p.a.e.set(b,"__ko__templateSubscriptionDomDataKey__",e)}}})();p.b("ko.setTemplateEngine",p.na);p.b("ko.renderTemplate",p.U);
+p.a.v=function(c,e,b){if(b===n)return p.a.v(c,e,1)||p.a.v(c,e,10)||p.a.v(c,e,Number.MAX_VALUE);else{for(var c=c||[],e=e||[],a=c,f=e,i=[],g=0;g<=f.length;g++)i[g]=[];for(var g=0,h=Math.min(a.length,b);g<=h;g++)i[0][g]=g;g=1;for(h=Math.min(f.length,b);g<=h;g++)i[g][0]=g;for(var h=a.length,j,k=f.length,g=1;g<=h;g++){var l=Math.min(k,g+b);for(j=Math.max(1,g-b);j<=l;j++)i[j][g]=a[g-1]===f[j-1]?i[j-1][g-1]:Math.min(i[j-1][g]===n?Number.MAX_VALUE:i[j-1][g]+1,i[j][g-1]===n?Number.MAX_VALUE:i[j][g-1]+1)}b=
+c.length;a=e.length;f=[];g=i[a][b];if(g===n)i=o;else{for(;b>0||a>0;){h=i[a][b];j=a>0?i[a-1][b]:g+1;k=b>0?i[a][b-1]:g+1;l=a>0&&b>0?i[a-1][b-1]:g+1;if(j===n||j<h-1)j=g+1;if(k===n||k<h-1)k=g+1;l<h-1&&(l=g+1);j<=k&&j<l?(f.push({status:"added",value:e[a-1]}),a--):(k<j&&k<l?f.push({status:"deleted",value:c[b-1]}):(f.push({status:"retained",value:c[b-1]}),a--),b--)}i=f.reverse()}return i}};p.b("ko.utils.compareArrays",p.a.v);
+(function(){function c(c,b,a){var f=[],c=p.j(function(){var c=b(a)||[];f.length>0&&p.a.ka(f,c);f.splice(0,f.length);p.a.z(f,c)},o,{disposeWhenNodeIsRemoved:c,disposeWhen:function(){return f.length==0||!p.a.O(f[0])}});return{Oa:f,j:c}}p.a.la=function(e,b,a,f){for(var b=b||[],f=f||{},i=p.a.e.get(e,"setDomNodeChildrenFromArrayMapping_lastMappingResult")===n,g=p.a.e.get(e,"setDomNodeChildrenFromArrayMapping_lastMappingResult")||[],h=p.a.L(g,function(a){return a.wa}),j=p.a.v(h,b),b=[],k=0,l=[],h=[],q=
+o,m=0,s=j.length;m<s;m++)switch(j[m].status){case "retained":var r=g[k];b.push(r);r.B.length>0&&(q=r.B[r.B.length-1]);k++;break;case "deleted":g[k].j.n();p.a.h(g[k].B,function(a){l.push({element:a,index:m,value:j[m].value});q=a});k++;break;case "added":var t=c(e,a,j[m].value),r=t.Oa;b.push({wa:j[m].value,B:r,j:t.j});for(var t=0,x=r.length;t<x;t++){var u=r[t];h.push({element:u,index:m,value:j[m].value});q==o?e.firstChild?e.insertBefore(u,e.firstChild):e.appendChild(u):q.nextSibling?e.insertBefore(u,
+q.nextSibling):e.appendChild(u);q=u}}p.a.h(l,function(a){p.u(a.element)});a=!1;if(!i){if(f.afterAdd)for(m=0;m<h.length;m++)f.afterAdd(h[m].element,h[m].index,h[m].value);if(f.beforeRemove){for(m=0;m<l.length;m++)f.beforeRemove(l[m].element,l[m].index,l[m].value);a=!0}}a||p.a.h(l,function(a){a.element.parentNode&&a.element.parentNode.removeChild(a.element)});p.a.e.set(e,"setDomNodeChildrenFromArrayMapping_lastMappingResult",b)}})();p.b("ko.utils.setDomNodeChildrenFromArrayMapping",p.a.la);
+p.R=function(){this.q=function(){if(typeof jQuery=="undefined"||!jQuery.tmpl)return 0;if(jQuery.tmpl.tag){if(jQuery.tmpl.tag.tmpl&&jQuery.tmpl.tag.tmpl.open&&jQuery.tmpl.tag.tmpl.open.toString().indexOf("__")>=0)return 3;return 2}return 1}();this.getTemplateNode=function(c){var b=document.getElementById(c);b==o&&d(Error("Cannot find template with ID="+c));return b};var c=RegExp("__ko_apos__","g");this.renderTemplate=function(e,b,a){a=a||{};this.q==0&&d(Error("jquery.tmpl not detected.\nTo use KO's default template engine, reference jQuery and jquery.tmpl. See Knockout installation documentation for more details."));
+if(this.q==1)return e='<script type="text/html">'+this.getTemplateNode(e).text+"<\/script>",b=jQuery.tmpl(e,b)[0].text.replace(c,"'"),jQuery.clean([b],document);if(!(e in jQuery.template)){var f=this.getTemplateNode(e).text;jQuery.template(e,f)}b=[b];b=jQuery.tmpl(e,b,a.templateOptions);b.appendTo(document.createElement("div"));jQuery.fragments={};return b};this.isTemplateRewritten=function(c){if(c in jQuery.template)return!0;return this.getTemplateNode(c).Na===!0};this.rewriteTemplate=function(c,
+b){var a=this.getTemplateNode(c),f=b(a.text);this.q==1&&(f=p.a.m(f),f=f.replace(/([\s\S]*?)(\${[\s\S]*?}|{{[\=a-z][\s\S]*?}}|$)/g,function(a,b,c){return b.replace(/\'/g,"__ko_apos__")+c}));a.text=f;a.Na=!0};this.createJavaScriptEvaluatorBlock=function(c){if(this.q==1)return"{{= "+c+"}}";return"{{ko_code ((function() { return "+c+" })()) }}"};this.ta=function(c,b){document.write("<script type='text/html' id='"+c+"'>"+b+"<\/script>")};p.i(this,"addTemplate",this.ta);this.q>1&&(jQuery.tmpl.tag.ko_code=
{open:(this.q<3?"_":"__")+".push($1 || '');"})};p.R.prototype=new p.Y;p.na(new p.R);p.b("ko.jqueryTmplTemplateEngine",p.R);
-})(window);
+})(window);
View
74 src/binding/defaultBindings.js
@@ -185,24 +185,60 @@ ko.bindingHandlers['options'] = {
ko.selectExtensions.writeValue(option, undefined);
element.appendChild(option);
}
- for (var i = 0, j = value.length; i < j; i++) {
- var option = document.createElement("OPTION");
- var optionValue = typeof allBindings['optionsValue'] == "string" ? value[i][allBindings['optionsValue']] : value[i];
-
- // Pick some text to appear in the drop-down list for this data value
- var optionsTextValue = allBindings['optionsText'];
- if (typeof optionsTextValue == "function")
- optionText = optionsTextValue(value[i]); // Given a function; run it against the data value
- else if (typeof optionsTextValue == "string")
- optionText = value[i][optionsTextValue]; // Given a string; treat it as a property name on the data value
+
+ var optionsGroupNamesValue = allBindings['optionsGroupNames'];
+
+ // Group values into optgroups
+ var groupedOptions = [];
+ var optionsGroupValue = allBindings['optionsGroup']; // undefined if not given
+ for(var i=0, j=value.length; i < j; i++) {
+ var optionsGroup = null;
+ if (typeof optionsGroupValue == "function")
+ optionsGroup = optionsGroupValue(value[i]);
+ else if (typeof optionsGroupValue == "string")
+ optionsGroup = value[i][optionsGroupValue];
else
- optionText = optionValue; // Given no optionsText arg; use the data value itself
-
- optionValue = ko.utils.unwrapObservable(optionValue);
- optionText = ko.utils.unwrapObservable(optionText);
- ko.selectExtensions.writeValue(option, optionValue);
- option.innerHTML = optionText.toString();
- element.appendChild(option);
+ optionsGroup = "";
+ if (typeof groupedOptions[optionsGroup] == "undefined")
+ groupedOptions[optionsGroup] = [];
+ groupedOptions[optionsGroup].push(value[i]);
+ }
+
+ // Create HTML elements
+ for (var groupName in groupedOptions) {
+ var optgroup = null;
+ // Add an OPTGROUP for all groups except for ""
+ if(groupName != "") {
+ optgroup = document.createElement("OPTGROUP");
+ optgroup.label = groupName;
+ element.appendChild(optgroup);
+ }
+
+ // Create HTML elements for options within this group
+ for (var i = 0, j = groupedOptions[groupName].length; i < j; i++) {
+ var valueGroup = groupedOptions[groupName];
+ var option = document.createElement("OPTION");
+ var optionValue = typeof allBindings['optionsValue'] == "string" ? valueGroup[i][allBindings['optionsValue']] : valueGroup[groupName][i];
+
+ // Pick some text to appear in the drop-down list for this data value
+ var optionsTextValue = allBindings['optionsText'];
+ if (typeof optionsTextValue == "function")
+ optionText = optionsTextValue(valueGroup[i]); // Given a function; run it against the data value
+ else if (typeof optionsTextValue == "string")
+ optionText = valueGroup[i][optionsTextValue]; // Given a string; treat it as a property name on the data value
+ else
+ optionText = optionValue; // Given no optionsText arg; use the data value itself
+
+ optionValue = ko.utils.unwrapObservable(optionValue);
+ optionText = ko.utils.unwrapObservable(optionText);
+ ko.selectExtensions.writeValue(option, optionValue);
+ option.innerHTML = optionText.toString();
+
+ if (optgroup != null)
+ optgroup.appendChild(option);
+ else
+ element.appendChild(option);
+ }
}
// IE6 doesn't like us to assign selection to OPTION nodes before they're added to the document.
@@ -215,7 +251,7 @@ ko.bindingHandlers['options'] = {
countSelectionsRetained++;
}
}
-
+
if (previousScrollTop)
element.scrollTop = previousScrollTop;
}
@@ -398,4 +434,4 @@ ko.bindingHandlers['attr'] = {
}
}
}
-};
+};
Something went wrong with that request. Please try again.