Wednesday, December 11, 2013

Notifying Progressbars With Deferred Objects

Updating the progressbar widget is done by calling the value() method. First, you'll want to set the min and max option values if you're measuring the progress of something specific. By default, min and max are set to 0 and 100 respectively. So the actual call to update the progressbar is an easy one, what's challenging is figuring out when to do it. For example, you could have some application logic that performs some action, and you're not exactly sure how long it will take. So rather than trying to configure and use the progressbar widget inside of the application logic, it would be nice if there were an interface between the two.

This is where we can use the $.Deferred object to communicate the value to the progressbar — by notifying it. There are a couple approaches one might take to implementing such a mechanism, but extending the progressbar widget to accept a deferred object option is probably the most straightforward. Let's take a look at an example of how this is done.

_create: function() {
    this._super();
    var deferred = this.options.deferred;
    if ( deferred ) {
        deferred.progress( $.proxy( this, "_notify" ) );
    }
}

Inside our extension of the progressbar widget, we have the constructor — the _create() method. We call the original progressbar constructor using _super() — nothing changes there and so it should always get called first. Then we store the value of our new option — deferred — in the deferred variable. If a value was supplied to this option, we then setup a progress() handler on the deferred object. This is a proxy to our _notify() method, which we'll look at next.

_notify: function() {
    var value = this.value() || 0,
        max = this.options.max;
    if ( value >= max ) {
        return;
    }
    this.value( value + 1 );
}

This is the method that's called in response to deferred object notifications. So we get the current value, and the max option, and store them in local variables of the same name. If we've met or exceeded the maximum allowed value for the progressbar, we do nothing, otherwise, we keep incrementing the progressbar value by 1.

var dfd = new $.Deferred(),
    $progressbar = $( "#progressbar" ).progressbar({
        deferred: dfd 
    });

(function() {
    function update() {
        dfd.notify();
        setTimeout( update, 100 );   
    }
    setTimeout( update, 3000 );
})();

Finally, this is how we pass a $.Deferred instance to our modified progressbar widget. Once the deferred and progressbar objects have been created, we start up a process that continuously calls the dfd.notify() method, which updates the progressbar. Note that this will update the progress bar, and there is no direct interaction between the notify logic and the widget update.