I am evaluating select2 v4.0 in relation to us upgrading from v3.5.x. we are using this with knockout v3.5.2. Our scenario is that we want to allow for user to select multiple options based on js objects coming from an ajax call. the selectedoptions binding should store the entire js object in the observablearray its bound to and not just the list of ids.
in my testing, here is what I discovered
This is a select2 with a predefined list of options. each option has a id, text and displayText property. the result is that the multipleSelectedObjects observable array ends up having an array of the entire js object which is what I would expect based on the fact that I didn't set an optionsValue argument
<select class="multipleSelect" data-bind="options: multipleOptions, selectedOptions: multipleSelectedObjects, optionsText: 'displayText', select2v4: multipleOptionsSetup" style="width: 200px">
This is a select2 with a predefined list of options. each option has a id, text and displayText property. the result is that the multipleSelectedValue observable array ends up having an array of ints representing the id of the js objects selected which is what I would expect based on the fact that I set the optionsValue argument
<select class="multipleSelect3" data-bind="options: multipleOptions, selectedOptions: multipleSelectedValue, optionsText: 'text', optionsValue: 'id', select2v4: multipleOptionsSetup" style="width: 200px">
This is a select2 that is using the ajax option to make a service call to return options. each option returned has a id, text and displayText property. the result is that the selectedItems observable array ends up having an array of ints representing the id of the js objects selected. This is not what I would expect. I tried setting just the optionsText property as I did with the first select2 above, but that didn't make a difference
<select id="select2Input" data-bind="selectedOptions: selectedItems, select2v4: selectSetup" style="width: 400px">
I think this might be a select2 issue, but im not sure. Our select2v4 binding handler is shown below. in the last scenario when knockout hits the selectedOptions section, the value is a string which makes me think select2 is not setup to pass the entire object when using ajax. anyone else experience this? is this a bug or by design?
// NOTE: this binding handler is made for select2 version 4.0
ko.bindingHandlers.select2v4 = {
init: function (element, valueAccessor, allBindingsAccessor) {
var bindingValue = ko.unwrap(valueAccessor());
var allBindings = allBindingsAccessor();
var valueDataChange;
// Observe external data changes; set data on change
if (ko.isObservable(allBindings.valueData)) { // special data binding
var onChange = false;
allBindings.valueData.subscribe(function (value) { // subscribe to external data changes
if (onChange) return; // ignore if on change to prevent recursion
$(element).select2("data", value, false); // set data explicitly; do not trigger change });
if (ko.isWritableObservable(allBindings.valueData)) {
valueDataChange = function () {
onChange = true; // suppress valueData subscription
allBindings.valueData($(element).select2("data"));
onChange = false;
};
$(element).on("change", valueDataChange); // update observable on data change
}
}
// Observe external value changes
else if (ko.isObservable(allBindings.value)) { // input or single select with observable value binding
allBindings.value.subscribe(function (value) { // subscribe to external value changes
if (typeof value === "string") { // optionsValue or tags specified
if (bindingValue.tags) { // tags specified
value = value.split(bindingValue.separator || ','); // split on value separator
}
$(element).val(value); // set val to allow select2 to resolve data from values; do not trigger change
}
else { // optionsValue not specified, value is complex data
$(element).select2("data", value, false); // set data explicitly; do not trigger change
}
});
}
// Observe external selection changes
else if (ko.isObservable(allBindings.selectedOptions)) { // multiselect with observable selection binding
allBindings.selectedOptions.subscribe(function (value) { // subscribe to external selection changes
if (value.length > 0 && typeof value[0] === "string") { // optionsValue specified
$(element).val(value); // set val to allow select2 to resolve data from values; do not trigger change
}
else { // optionsValue not specified, value is complex data
$(element).select2("data", value, false); // set data explicitly (only works if complex data object has 'id' property); do not trigger change
}
});
}
// Destroy select2 on element disposal
ko.utils.domNodeDisposal.addDisposeCallback(element, function () {
$(element).select2('destroy');
if (valueDataChange != null) {
$(element).off("change", valueDataChange);
}
});
// Apply select2 and initialize data; delay to allow other binding handlers to run
setTimeout(function () {
// Apply select2
$(element).select2(bindingValue);
// Initialize data
if ("valueData" in allBindings) {
$(element).select2("data", ko.unwrap(allBindings.valueData), false);
}
}, 0);
}
};
There were a number of big changes in how select2 v4 works internally compared to prior versions. One of them is how select2 is storing selected objects in the case of using ajax to load data (aka typeahead). A coworker of mine was able to figure out that in that case, the selected objects are stored in a readonly collection and do not properly notify when changes are made, plus the normal binding doesn't work as expected. As we discovered, binding to selectedOptions did not result in a list of objects, but of ids. He came up with the binding handler for select2 shown below that will deal with both regular select style binding and adds a new binding called selectData, which will get you a list of selected objects if you are using remote data loading with ajax. You only need the selectData binding if you are using ajax to load data and want to get a list of the selected objects instead of ids, otherwise you can either bind to value or selectedOptions and types of setup will work as expected.
<select id="select2Input" data-bind="select2Data: selectedItems, select2: selectSetup" style="width: 400px"></select>