I'm using knockout.js 3.3.0 with jQuery mobile 1.4. The problem using knockout.js together with jQuery mobile is that the programmatically changes to the underlying viewmodel are not always reflected to the graphical user interface, due to the JQM refactoring of the html elements, or widgets. So, for example, the JQM selectmenu is synchronized from the user interface to the viewmodel, but not in the other way.
I'm trying to stick together the 3.3.0 KO 'options' binding with a custombinding to the current JQM actual version. There are already two possible solutions for the 'refresh' problem already proposed at SO for 2.x versions of KO: jqmSelect and jqmValue, as custombindings. I try this suggestions for a more recent KO+JQM combination, putting together all the answers/comments found at SO regarding this topic.
This is the js that i'm using to test:
$(document).ready(function () {
var jsonResultData = [{
"id": 6,
"name": "First item",
"pattern": "Cheetah"
}, {
"id": 2,
"name": "Second item",
"pattern": "Viper"
}, {
"id": 1,
"name": "Third item",
"pattern": "Scorpion"
}];
ko.applyBindings(new AdminViewModel(jsonResultData));
});
function Match(data) {
this.id = ko.observable(data.id);
this.pattern = ko.observable(data.pattern);
this.name = ko.observable(data.name);
}
function AdminViewModel(allData) {
var self = this;
self.matches = ko.observableArray([]);
self.matchesFromDb = $.map(allData, function (item) {
return new Match(item);
});
self.matches = self.matchesFromDb;
self.selectedMatchId = ko.observable(self.matches[0].id());
self.selectedMatch = ko.observable(self.matches[0]);
self.setSelectedMatchId = function (match) {
if (match.id() != self.selectedMatchId()) {
self.selectedMatchId(match.id());
self.selectedMatch(match);
}
};
self.patternValues = ko.observableArray(["Shark", "Viper", "Chameleon", "Cheetah", "Scorpion"]);
}
I made a fiddle to test the jqmValue custom binding, which is one of the latest soultions found at SO, but but i'm not able to get it to work:
ko.bindingHandlers.jqmValue = {
init: function (element, valueAccessor, allBindingsAccessor, viewModel) {
if (typeof ko.bindingHandlers.value.init !== 'undefined') {
ko.bindingHandlers.value.init(element, valueAccessor, allBindingsAccessor, viewModel);
}
},
update: function (element, valueAccessor, allBindingsAccessor, viewModel) {
var instance;
if (typeof ko.bindingHandlers.value.update !== 'undefined') {
ko.bindingHandlers.value.update(element, valueAccessor, allBindingsAccessor, viewModel);
}
instance = $.data(element, 'mobile-selectmenu');
if (instance) {
$(element).selectmenu('refresh', true);
}
}
};
(the original code for AdminViewModel as reference for KO 2.x thanks to pablo is working with the suggested change to invert value/options in the markup thanks to JohnEarles at google groups)
Here is the fiddle with actual KO and JQM versions where i try to include the best of all suggestions found regarding this topic:
but i'm still streching my hairs without success. Why in my test fiddle the changes to the viewmodel are not reflected to the JQM selectmenu? What is my error?
UPDATE: two-page fiddle to test also initialization: http://jsfiddle.net/nHNzL/50/
FINAL VERSION: I made 2 small fixes and 1 change: 1) isInstance shall be checked every time 2) removed the if (currentValue == value) 3) inverted the disabled attribute Moreover: i tested this custombinding in a ko foreach-loop, each select element needs to be child of an own container div.
ko.bindingHandlers.jqmSelectMenu = {
init: function (element, valueAccessor, allBindings) {
var options = ko.toJS(valueAccessor()),
valueObservable = allBindings.get("value"), valueSubscription,
optionsObservable = allBindings.get("options"), optionsSubscription;
var refresh = function () {
var $el = $(element);
var isInstance = !!$.data(element, 'mobile-selectmenu');
if (isInstance) {
$el.selectmenu('refresh', true);
} else {
/* instantiate the widget unless jqm has already done so */
$(element).selectmenu(options);
}
};
refresh();
/* hook up to the observables that make up the underlying <select> */
if (ko.isSubscribable(valueObservable)) {
valueSubscription = valueObservable.subscribe(refresh);
}
if (ko.isSubscribable(optionsObservable)) {
optionsSubscription = optionsObservable.subscribe(refresh);
}
/* properly dispose of widgets & subscriptions when element is removed */
ko.utils.domNodeDisposal.addDisposeCallback(element, function() {
$(element).selectmenu("destroy");
if (valueSubscription) valueSubscription.dispose();
if (optionsSubscription) optionsSubscription.dispose();
});
},
update: function (element, valueAccessor, allBindings) {
var options = ko.toJS(valueAccessor()),
$el = $(element);
/* update any widget options if necessary */
ko.utils.objectForEach(options, function (key, value) {
if (key === "enabled") {
$el.selectmenu(value ? "enable" : "disable");
} else {
$el.selectmenu("option", key, value);
}
});
}
};