Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

KnockoutJS binding to object value (not a simple value) ... Hack fix included #532

Closed
APM3 opened this issue May 2, 2015 · 3 comments
Closed
Labels

Comments

@APM3
Copy link

APM3 commented May 2, 2015

I am using the knockout select binding where an entire object is bound as the value...not just a string.

bootstrap-multiselect did not like this because it uses the "value" to relate its dropdown list items back to the corresponding select option. In order for it to do this you have to have set the "optionsValue" attribute in your knockout binding so that the option has a "value" attribute. However, setting the "optionsValue" attribute then has the expected result of setting only that value in my selectedOptions observableArray...not the entire object.

Its quite possible that I am the only person that uses the functionality within knockout that lets you bind the options to entire objects, but just in case I'm not...here's the hack fix:

Created a two new bootstrap-multiselect options called "optionsKey" and "observableKey" so that it knows where to go for a value that it can relate to:

<select id="mySelect" data-bind="options: myOptions, selectedOptions: mySelectedOptions, optionsText: 'myDisplayText', optionsAfterRender: SetOptionKey, multiselect: { optionsKey: 'data-key', observableKey: 'Key' }" multiple="multiple"></select>

In my knockout view model I set the attribute referenced by "optionsKey" on each option:

myVM.SetOptionKey = function ( option, item ) {
    ko.applyBindingsToNode( option, { attr: { "data-key": item.Key } }, item );
};

Now I dive into the bootstrap-multiselect.js file. The first thing I had to do in here is at the top where it is adding the knockout init handler.

init: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
    ...
    if (allBindings.has('selectedOptions')) {
        var selectedOptions = allBindings.get('selectedOptions');
        if (ko.isObservable(selectedOptions)) {
            ko.computed({
                read: function () {
                    selectedOptions();

                    // Added to handle knockout binding to an object...not just a value
                    // multiselect needs the selected property on the options, else it wont show them on refresh
                    if ((config.optionsKey !== undefined) &&
                        (config.optionsKey !== null) &&
                        (config.optionsKey.length > 0) &&
                        (config.observableKey !== undefined) &&
                        (config.observableKey !== null) &&
                        (config.observableKey.length > 0)) {
                        var _optionsKey = config.optionsKey;
                        var _observableKey = config.observableKey;
                        ko.utils.arrayForEach(selectedOptions(), function (selectedOption) {
                            $("option[" + _optionsKey + "='" + selectedOption[_observableKey]() + "']", $element).prop('selected', true);
                        })
                    }

                    setTimeout(function () {
                        $element.multiselect('refresh');
                    }, 1);
                },
                disposeWhenNodeIsRemoved: element
            }).extend({ rateLimit: 100, notifyWhenChangesStop: true });
        }
    }
    ...

Next I had to modify the createOptionValue function in bootstrap-multiselect.js to first use this key as the value it sets in its list items:

createOptionValue: function (element) {
    ...

    var value = $element.val();
    // Added to handle knockout binding to an object...not just a value
    if ((this.options.optionsKey !== undefined) && (this.options.optionsKey !== null) && (this.options.optionsKey.length > 0)) {
        var key = $element.attr(this.options.optionsKey);
        if ((key !== undefined) && (key !== null) && (key.length > 0)) {
            value = key;
        }
    }

    ...
}

Lastly I had to modify getOptionByValue function in bootstrap-multiselect.js to find option elements based on this attribute instead of value:

getOptionByValue: function (value) {
    var valueToCompare = value.toString();

    // Added to handle knockout binding to an object...not just a value
    if ((this.options.optionsKey !== undefined) && (this.options.optionsKey !== null) && (this.options.optionsKey.length > 0)) {
        var opt = $("option[" + this.options.optionsKey + "='" + valueToCompare + "']", this.$select).first();
        if( (opt !== undefined) && (opt !== null) ) {
            return $(opt);
        }
    }

    var options = $('option', this.$select);

    for (var i = 0; i < options.length; i = i + 1) {
        var option = options[i];
        if (option.value === valueToCompare) {
            return $(option);
        }
    }
},
@davidstutz
Copy link
Owner

Thanks, will include this in the FAQ! I am sure this is helpful for other users, as well.

davidstutz added a commit that referenced this issue May 26, 2015
@bertwagner
Copy link

Thank you for this tip - I was trying to accomplish binding to select objects as well and this write up was very helpful. The only thing I had to do in addition to @APM3 's code is also populate the "value" attribute of my options in the SetOptionKey function:

self.SetOptionKey = function (option, item) { ko.applyBindingsToNode(option, { attr: { "data-key": item.Value } }, item); ko.applyBindingsToNode(option, { attr: { "value": item.Value } }, item); }

Thanks again for this write up.

@jmvtrinidad
Copy link

See gist for knockout binding that supported object. Just replace the actual binding from bootstrap-multiselect.js file. In this code you dont need to put into your vm to set option.

//Just like this one
myVM.SetOptionKey = function ( option, item ) {
    ko.applyBindingsToNode( option, { attr: { "data-key": item.Key } }, item );
};

I added preprocess to bindings to implicitly addoptionsAfterRender.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

4 participants