Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Knockout and jQuery Mobile: Binding data to select lists

I'm using both Knockout (version 2.0) and jQuery Mobile (version 1.0.1) in the same project. The problem is with binding data to select lists. jQuery Mobile presents select lists in a way where the seemingly selected value and the actual list are separate elements. This is fixed by executing

$(element).selectmenu('refresh', true);

after changing either the list or the selected value. Based on my experience, this is a dangerous situation as developers often forget to refresh select list.

To ease this, I wrote my own Knockout binding handler. The values are bound to the select list with following code:

<select name="selection" data-bind="jqmOptions: values, optionsValue: 'id', optionsText: 'name', value: selectedValue">
</select>

The implementation of jqmOptions:

ko.bindingHandlers.jqmOptions = {
    init: function (element, valueAccessor, allBindingsAccessor, viewModel) {
        if (typeof ko.bindingHandlers.options.init !== 'undefined') {
            ko.bindingHandlers.options.init(element, valueAccessor, allBindingsAccessor, viewModel);
        }
    },

    update: function (element, valueAccessor, allBindingsAccessor, viewModel) {
        if (typeof ko.bindingHandlers.options.update !== 'undefined') {
            ko.bindingHandlers.options.update(element, valueAccessor, allBindingsAccessor, viewModel);
        }

        var instance = $.data(element, 'selectmenu');
        if (instance) {
            $(element).selectmenu('refresh', true);
        }
    }
};

This uses the native options binding but in addition to that, it automatically refreshes select lists after changing the list values. There is a problem with this however when I'm changing the selected value. If I first set the list values, my jqmOptions refreshes the select list but at that point, the selected value is not yet set. I end up with a select list, which has all the correct values and internally the right option is selected, but jQuery Mobile still displays the default value as selected.

this.values(someArrayOfValues);
this.selectedValue(oneOfTheArrayValues);

Knockout doesn't allow me to first set the selected value and then setting the list values, because in this case there are no allowed values when I'm setting the selected value. Thus the selected value is always undefined.

Is there a way to write a Knockout custom binding which would refresh the select list element in both cases: when changing the list value and when changing the selected value?

Currently I solve this situation with following code:

this.values(someArrayOfValues);
this.selectedValue(oneOfTheArrayValues);
this.values(someArrayOfValues);

This is not very elegant solution however and I would like to solve it better.

like image 592
Ville Salonen Avatar asked Feb 21 '12 07:02

Ville Salonen


Video Answer


2 Answers

Just for clarity, the best solution now for KO 3.x would be:

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);
      }
    }
  };

And the matching HTML use:

<select data-bind="options: optionsList, optionsValue: 'Id', optionsText: 'Title', jqmValue: knockoutobservable"></select>
like image 71
skrile Avatar answered Oct 28 '22 20:10

skrile


I ended up solving myself. I wrote my own jqmValue binding:

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) {
        if (typeof ko.bindingHandlers.value.update !== 'undefined') {
            ko.bindingHandlers.value.update(element, valueAccessor, allBindingsAccessor, viewModel);
        }

        var instance = $.data(element, 'selectmenu');
        if (instance) {
            $(element).selectmenu('refresh', true);
        }
    }
};

The select list code is then changed to following:

I already tried implementing this yesterday before asking the question, but apparently I wrote it poorly then because it didn't work. However, now with a fresh pair of eyes I managed to implement it correctly so hopefully this answer solves the problem for other Knockout and jQuery Mobile users too.

like image 45
Ville Salonen Avatar answered Oct 28 '22 22:10

Ville Salonen