Monday, February 6, 2012

jQuery UI Knockout Example

One thing jQuery UI lacks is a view model — a local representation of the application data.  The idea behind the view model is that it is synchronized with the actual application data on the server.  Conversely, the UI view is synchronized with the view model.  This greatly simplifies situations where the user interface needs updates in response to changing data sources and when the source needs to be updated in response to user action.

The Knockout framework fills this gap nicely in jQuery UI applications.  It wasn't exactly designed for jQuery UI, but it is generic enough to work with any framework.  Here is an example of how I was able to bind a view model with my jQuery UI widgets.

The Widgets
Here, we're creating a simple slider widget and a simple progress bar widget.  The idea is to have the progress bar updated with the slider's value when it changes. We're also adding a label on top of the progress bar to display the progress numerically.

        <link href="jquery-ui-theme.css" rel="stylesheet" type="text/css"/>
        <script src="jquery.min.js" type="text/javascript"></script>
        <script src="jquery-ui.min.js" type="text/javascript"></script>
        <script src='knockout.js' type='text/javascript'></script>
        <script src="example.js" type="text/javascript"></script>
        <div id="slider"></div>
        <div id="progressbar" data-bind="updateProgress: progress"></div>
        <span data-bind="text: progress" class="ui-widget"></span>

The markup used here to define the widgets is fairly straightforward.  Noteworthy, however, are the bindings we're attaching using the data-bind attribute.  The progress bar widget is using a special Knockout binding we'll get to in a moment. The div used to display the numerical progress value is using the text binding provided by default.  What these two elements have in common are the model attribute they reference within the binding — progress.  Let's take a look at the view model and it's bindings.

The View Model and Bindings
The widgets we've defined need to communicate with the view model.  We define that view model using Knockout.  But we also need to define a customized binding. Why?  Because Knockout's default bindings work well for more generic situations where we're injecting items from the view model into DOM elements on the page. For something more dynamic, like jQuery UI widgets, simply updating the DOM won't do.  We need a means to invoke the widget's API.

// Define a simple view model.
var model = {
    progress: ko.observable(0)

// Define a new knockout binding to update any progress bar.
ko.bindingHandlers.updateProgress = {
    update: function(element, valueAccessor) {
        var value = ko.utils.unwrapObservable(valueAccessor());
        $(element).progressbar('option', 'value', value);

    // Apply any bindings to the view model.
    // The slider widget updates the view model on change.
        change: function(event, ui){
            var value = $(this).slider('option', 'value');
    // The progressbar has the updateProgress binding applied to it.
    // The progresstext uses a text binding to read the progress value.
        of: $('#progressbar'),
        at: 'center center',
        my: 'center center'

There you have it — a simple interaction between jQuery UI widgets using Knockout underneath.  Our model isn't the focus here, obviously.  The interesting code is the updateProgress binding.  Since we've applied this binding to the progress bar, we've got a reference to the widget itself.  That means that this binding will work the same with any progress bar widget, which is handy.  The binding simply sets the progress bar value to that of the specified view model item — in this case, progress.

One final note about this example.  When we're creating the slider widget, we're giving it a callback for the change event.  This event is fired whenever the value of the slider is updated by the user.  All we need to do here is set the value of progress in the view model — this will update the value of both the progress bar and our custom label.  Nice and easy, isn't it?