Knockout 3/Select2 update issue
by Doug Hill
HTML
<script src="http://cdnjs.cloudflare.com/ajax/libs/knockout/3.1.0/knockout-min.js"></script>
<script src="http://ivaynberg.github.io/select2/select2-3.4.8/select2.js"></script>
<p>Try changing "one" to something else and tabbing away; notice that the select2 below does not update its selected item because it's not triggering the update properly.</p>
<p>If you point your browser's element inspector, you'll notice that the background [select] element's options and stuff are getting changed, but this isn't causing an update in the select2 because the trigger("change") bit isn't firing.</p>
<p>This did work in Knockout 2.x, probably because Knockout 2 reevaluates all bindings every time one of them changes, and it re-triggers the options binding when the options text values change. However, evaluating the options binding in the select2 binding's update part doesn't seem to fix it.</p>
<div data-bind="foreach: choices">
<div><input type="text" data-bind="value: choice" /></div>
</div>
<select data-bind="options: choices, optionsText: 'choice', optionsValue: 'choice', select2: {}"></select>
CSS
/*
Version: @@ver@@ Timestamp: @@timestamp@@
*/
.select2-container {
margin: 0;
position: relative;
display: inline-block;
/* inline-block for ie7 */
zoom: 1;
*display: inline;
vertical-align: middle;
}
.select2-container,
.select2-drop,
.select2-search,
.select2-search input {
/*
Force border-box so that % widths fit the parent
container without overlap because of margin/padding.
More Info : http://www.quirksmode.org/css/box.html
*/
-webkit-box-sizing: border-box; /* webkit */
-moz-box-sizing: border-box; /* firefox */
box-sizing: border-box; /* css3 */
}
.select2-container .select2-choice {
display: block;
height: 26px;
padding: 0 0 0 8px;
overflow: hidden;
position: relative;
border: 1px solid #aaa;
white-space: nowrap;
line-height: 26px;
color: #444;
text-decoration: none;
border-radius: 4px;
background-clip: padding-box;
-webkit-touch-callout: none;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
background-color: #fff;
background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, #eee), color-stop(0.5, #fff));
background-image: -webkit-linear-gradient(center bottom, #eee 0%, #fff 50%);
background-image: -moz-linear-gradient(center bottom, #eee 0%, #fff 50%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr = '#ffffff', endColorstr = '#eeeeee', GradientType = 0);
background-image: linear-gradient(to top, #eee 0%, #fff 50%);
}
.select2-container.select2-drop-above .select2-choice {
border-bottom-color: #aaa;
border-radius: 0 0 4px 4px;
background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, #eee), color-stop(0.9, #fff));
background-image: -webkit-linear-gradient(center bottom, #eee 0%, #fff 90%);
background-image: -moz-linear-gradient(center bottom, #eee...
JavaScript
ko.bindingHandlers["select2"] = {
after: ["options", "value", "selectedOptions"],
init: function (element, valueAccessor) {
$(element).select2(ko.unwrap(valueAccessor()));
ko.utils.domNodeDisposal.addDisposeCallback(element, function() {
$(element).select2("destroy");
});
},
update: function (element, valueAccessor, allBindingsAccessor) {
//trying various methods to register in interest in updating this, but these work with the options observable, not the individual value observables
//var allBindings = allBindingsAccessor();
//if (allBindings.options) { allBindings.options(); }
//if (allBindings.value) { allBindings.value(); }
//if (allBindings.selectedOptions) { allBindings.selectedOptions(); }
$(element).trigger("change");
}
};
(function () {
var updateOptions = ko.bindingHandlers["options"]["update"];
ko.bindingHandlers["options"]["update"] = function (element) {
var ret = updateOptions.apply(null, arguments);
var $el = $(element);
if ($el.data("select2")) { $el.trigger("change"); }
return ret;
}
})();
(function () {
var updateSelectedOptions = ko.bindingHandlers["selectedOptions"]["update"];
ko.bindingHandlers["selectedOptions"]["update"] = function (element) {
var ret = updateSelectedOptions.apply(null, arguments);
var $el = $(element);
if ($el.data("select2")) { $el.trigger("change"); }
return ret;
}
})();
function Choice(choice) {
this.choice = ko.observable(choice);
}
function ViewModel() {
this.choices = ko.observableArray([
new Choice("one"),
new Choice("two"),
new Choice("three")
]);
this.myChoice = ko.observable();
}
var model = new ViewModel();
ko.applyBindings(model);