I’m currently building a mobile javascript application and I wanted to render a Yes/No as a flip switch using jQuery Mobile. To track the status of the variable and pass it to the logic of the application I’m using Knockout.js.

flipswitch

The two libraries play nice together if you update the variable only in one direction, from the UI to the viewmodel: in this case just do as if KO was not in the mix, and annotate the select element that is used by jquery mobile as base for the flip switch with the data-role="slider" attribute.

<select data-bind="options: ['Yes','No'], value: Partecipating" data-role="slider">
</select>

<script type="text/javascript">
viewModel = {
    Partecipating: ko.observable('Yes'),
};

ko.applyBindings(viewModel);
</script>

You can view the flip switch at work on the following jsfiddle: http://jsfiddle.net/simonech/4HPVm/3/

Why 2-way binding doesn’t work

If you also want to have 2-way binding, so that when changing the value of the variable the UI gets updated, this code is not enough. Let’s see why.

Knockout.js is a MVVM javascript library with two-way binding and automatically updates HTML components as soon as the variable bound to them changes.

JQuery Mobile “enhances” standard HTML components to make them mobile friendly, and this is done during the first rendering of an html page.

If you are a seasoned frontend developer you might have already understood why these two libraries cannot work well together: jquery mobile “processes” the DOM only once, at the beginning of the life of the page, so, “enhanced” HTML components cannot be updated by simply changing their backing variable because knockout.js doesn’t know how to update the new UI generated by jquery mobile.

KO custom binding to the rescue

Luckily Knockout.js is extensible and can be configured to “learn” how to update custom UI elements, by specifying custom bindings.

A custom binding defines how to initialize and how to update a given HTML element. In this post I’m showing how to create a custom binding specific for this problem: for more information and better explanation of all the details of custom bindings, please refers to the knockout.js documentation site.

First I’m showing you the code and then I’ll comment it. You can also view the working demo on a more comprehensive jsfiddle: http://jsfiddle.net/simonech/EkAmv/10/

ko.bindingHandlers.jqSlider = {
    init: function(element, valueAccessor, allBindingsAccessor) {
        var currentValue = valueAccessor();
        var bindings = allBindingsAccessor();
        if(bindings.options==undefined){
            $(element).slider(currentValue);
        }
        else{
            var called=0;
            bindings.optionsAfterRender = function(option, item){
                called++;
                if(called==2)
                    $(element).slider(currentValue);
            };
        }

    },
    update: function(element) {
        $( element ).slider( "refresh" );
    }
};

Let’s start with the update method of the binding: this is called every time the html component has to be updated, and simply forces the refresh of the flip switch element by calling jquery mobile API.

The init method is a bit more complicate as it handles two scenarios:

  • the first scenario is when the options for the select element are specified “normally” with the option tag (and the options binding is not specified): in this case the flip switch is created straight away but calling the .slider method.
  • the second scenario is when the options are also coming from the viewmodel and specified via the options binding. In this case I had to specify a callback (by setting the optionsAfterRender binding) that is executed after each option is rendered, and call the .slider method only after the last option has been rendered in the DOM.

Here the html element with the custom binding applied:

<select data-bind="options: ['Yes','No'], value: Partecipating, jqSlider: { mini: true }" >
</select>

As parameter of the custom jqSlider binding you can specify any of the parameter of the .slider method, like mini, theme and trackTheme.

Disabling jquery mobile default behavior on select elements

It’s not over yet: jquery mobile enhances every select element in the page, so we need to change the default selector used to find all select elements in the page. To do this we have to override the initSelector for the selectmenu component:

$( document ).on( "mobileinit", function() {
  $.mobile.selectmenu.prototype.options.initSelector = ".mobileSelect";
});

Remember that these lines have to be placed at the top of the page, even before the inclusion of the jquery mobile library

Complete demo

You can also view a demo on this jsfiddle: http://jsfiddle.net/simonech/EkAmv/10/