Tuesday, June 7, 2011

jQuery UI Progressbar Effects

The jQuery UI progressbar is a nice way to show the completion of a task.  With each step, the progressbar is updated and subsequently, the user receives visual feedback.  The default implementation of the progressbar simply increases the width of the completed area with each updated.  So, for example, if the completion is at 30% and we update with another 10%, the width will be set to 40%.  It would be nice if we could make each progression a little fancier, perhaps with a little animation?

Thankfully, jQuery UI gives us the foundation with which we can extend the default behavior of any widget, including the progressbar.  The jQuery core gives us the tools necessary to do simple yet elegant CSS animations.  Here is what I came up with - first the HTML:

<html>
    <head>
        
        <title>jQuery UI Progressbar Effects</title>
        
        <link type="text/css" href="jqueryuitheme.css" rel="stylesheet"/>
        <script type="text/javascript" src="jqueryui.min.js"></script>
        <script type="text/javascript" src="jqueryui.min.js"></script>
        <script type="text/javascript" src="progresseffects.js"></script>
        
    </head>
    <body>
        
        <div id="progressbar"></div>
                    
    </body>
</html>

And here is what progresseffects.js looks like:

//Create the new progeffects widget by extending  the progressbar.
$.widget('ui.progeffects', $.extend({}, $.ui.progressbar.prototype, {
 
    //We need to redefine the progressbar options and add a duration.
    options: {
        value: 0,
        max: 100,
        duration: 250
    },
    
    //This is used to keep track of the refresh frequency.
    refreshed: 0,
      
    //Initialize our widget by initializing the base progressbar.
    _init: function() {
        $.ui.progressbar.prototype._init.call(this);
    },
    
    //Redefined in our widget to provide animations.
    _refreshValue: function() {
        
        //Some variables we need.
        var value = this.value();
        var percentage = this._percentage();
        var time = new Date().getTime();
        var duration = this.options.duration;
        var max = this.options.max;
        
        //This is part of the default implementation, but still required.
        if ( this.oldValue !== value ) {
            this.oldValue = value;
            this._trigger('change');
        }
        
        //Here, we're making sure the progressbar isn't refreshed more
        //often than the animation duration will allow.
        if (time - this.refreshed < duration && value < max) {
            return;
        }
        
        //Store the time for the next refresh.
        this.refreshed = time;

        //Perform the refresh, using an animation.
        this.valueDiv
            .toggle( value > this.min )
            .toggleClass('ui-corner-right', value === this.options.max)
            .stop()
            .animate({width:percentage.toFixed(0)+'%'}, duration);
            
        this.element.attr('aria-valuenow', value);
    }
        
}));

//Example usage.
$(document).ready(function(){
    
    //Periodic update function to set the value of the progressbar.
    var update = function(){
        var value = $('#progressbar').progeffects('value');
        var max = $('#progressbar').progeffects('option', 'max');
        if (value < max) {
            $('#progressbar').progeffects('value', value + 1);
            setTimeout(update, 100)
        }
    };
    
    //Create the widget and start updating the progress.
    $('#progressbar').progeffects();
    update();
    
});

This example will continually update the progressbar value until it reaches 100.  It works just like a regular progressbar widget with one minor difference - it accepts a duration parameter, used for the animation each time the value changes.  Here is how it works.

First, we create a progeffects widget that extends the progressbar widget.  We're only changing a couple things about it so we should reuse as much as possible.  When a jQuery UI core widget is extended, the options need to be redefined by the custom widget, otherwise, they won't be recognized.  We're also adding a new duration option that'll be used with the animation effect.

The bulk of the new progeffects widget is housed in _refreshValue().  This method is used by the original implementation to update the progress.  We use some of the original implementation, but we've modified it to use animate() to set the CSS width instead of using CSS.

The example code that uses our new widget value 10 times per second.  You may notice a problem here.  By default, the animation duration of progeffects is 250.  Yikes.  This means the progress bar will still be updating long after it has reached 100!  There are two ways to get around this problem.

The first way - pass progeffects a duration value reflective of the update frequency.  So for example, we could have passed it {duration:100}.  This approach, however, introduces a couple new problems.  First of all, how do you know ahead of time how fast the progress will be updated?  If you did, it would be nothing better than our static example.  Second, try actually passing 100 as the duration value - it can be choppy to the point that it negates all we've done.  I'd say this approach is out.

The solution I'm using in progeffects is to monitor the update frequency.  Before the animation effect is executed, it will check if enough time has elapsed since the last update to be meaningful and to look good.

There are several variations to this, I'm sure.  Its a minor enhancement, but it sure does look cool.