Thursday, October 10, 2013

jQuery UI Widgets and Backbone Views

If you're like me and love the Backbone way of doing JavaScript development, it's hard to switch modes of thought when switching to other frameworks. For example, I also like the jQuery UI way of doing things. I'm used to thinking in terms of granular widgets, and not necessarily views. But a widget can be a view, right? All we have to do is make the widget perform the same task that a view normally would, just present the data to the user, respond to user events, and respond to data events. Is it possible?

As it turns out, yes. The are some obvious differences in how views get the job done and how widgets do things. Views, for example, generally use a JavaScript template engine such as handlebars to render the HTML, then place that string in a DOM element on the page. Widgets, in contrast, don't rely on a template engine. Backbone doesn't either, but let's just accept the fact that this is the standard practice. Instead, widgets rely on the DOM component already existing. Once the widget is created, it sets out to enhance the element with new elements, styles, and behavior.

So really, it's the same result — different yet related means of getting us there. What we want when replacing the default Backbone view with a jQuery UI widget is the ability to listen for events on the model or collection that drives the widget. That's it. Here is some sample code that does just that.

(function( $ ) {
    
    var Capacity = Backbone.Model.extend({
        defaults: {
            cpu1: 50,
            cpu2: 50
        },
        allocated: function() {
            return ( this.get( "cpu1" ) +
                     this.get( "cpu2" ) ) / 2;
        }
    });
    
    var capacity = new Capacity();
    
    $(function() {
        
        $( "#cpu1" ).slider({
            value: capacity.get( "cpu1" ),
            change: function( e, ui ) {
                capacity.set( "cpu1", ui.value );
            }
        });
        
        $( "#cpu2" ).slider({
            value: capacity.get( "cpu2" ),
            change: function( e, ui ) {
                capacity.set( "cpu2", ui.value );
            }
        });
        
        $( "#allocated" ).progressbar({
            value: capacity.allocated(),
            create: function( e, ui ) {
                var self = this;
                capacity.on( "change", function( model ) {
                    $( self ).progressbar( 
                        "value",
                        model.allocated()
                    );
                });
            }
        });
        
    });
    
})( jQuery );

Here we have one model, shared among three widgets. The first two slider widgets read the initial value of the cpu1, and cpu2 properties. Bother sliders then change the value of their respective model properties on the change event. Lastly, we have a progressbar widget that uses the allocated() method for it's value. The value is initially set when the widget is first created. In the create event, we setup an event callback that listens to the capacity model for property value changes. Specifically, if either of the CPU fields change, the progress bar wants to know about it. It'll then call allocated() to get the updated value so it can show a change in progress.