jQuery.UI ProgressBar Widget

I don't know squat about building widgets with jQuery, so I thought I would fix this by rearranging my trusty progress bar code once more and see how it would look as a jQuery.ui widget.  The jQuery.ui documentation is still a work in progress so it was a bit of an adventure, but once again the elegance of jQuery kept my frustrations to a minimum and I think I ended up with something pretty useful.  Read on if you are interested in the details and don't forget to check out the demo page (progress bar's don't show so well as static images).

Live Demo | Download | ui.progressbar.js

image

 

What is jQuery.ui?

Assuming you aren't real familiar with jQuery.ui (I certainly wasn't until yesterday), here is a quick overview ...

jQuery UI provides abstractions for low-level interaction and high-level, themeable widgets, built on top of the jQuery JavaScript Library, that you can use to build highly interactive web applications.

The core of the library revolves around different mouse interactions, namely drag and dropping, sorting, selecting, and resizing, as well as a powerful set of effects. - The jquery.ui documentation

 

What's a Widget?

A widget is a bit akin to the AjaxControlToolkit script controls you are probably already familiar with.  So far the jQuery.ui team has put together a collection of 5 controls/widgets - Accordion, Datepicker, Dialog, Slider and Tabs.  And of course, also like the toolkit they have their own client side framework for building new controls based on the widget framework (I am using the widget framework in this post).  You might be thinking that 5 widgets isn't too many, but you should keep in mind that because of the jQuery's rich plug-in support reusable bits of JavaScript can be captured in a plug-in.  Which, depending upon the functionality you wish to achieve, might be a better fit.

 

How do I Create a Widget?

Good question.  The jQuery.ui developer guide provides some guidance on how to create a widget, but I found it most useful to browse through the source for the existing 5 widgets.  After I thought I had learned all I could from the documentation and existing widgets, I made the bold attempt of creating my own.  Here are the steps I followed ...

1.  Just like when I created the progressbar plug-in, I start with a basic skeleton that I gleaned from the developer guide ...

   1: (function($) {
   2:     $.widget("ui.progressbar", {
   3:         init: function() {
   4:         
   5:             //  write your initialization code
   6:             //  this.element refers to the element being extended
   7:             //  this.options refers to the widgets options
   8:  
   9:         }
  10:         
  11:         //  add any other functions you need for the widget here
  12:     });
  13:  
  14:     $.extend($.ui.progressbar, {
  15:         defaults: {
  16:             //  define the available options plus their
  17:             //  default values
  18:         }
  19:     });
  20: })(jQuery);

Because I am rearranging the progress bar code from my AjaxControlToolkit progressbar script control, I wanted make sure my widget makes use of the same structure (just a handful of nested DIV's used for styling).  So, in the widget's init function (it gets invoked automatically by the jQuery.ui widget framework), I set the innerHtml of the supplied element to include the 3 nested DIV's, each with their own identifying class name.  This only takes a single LOC using jQuery's html function ... 

   1: init: function() {
   2:     //  wrap the element in the required markup
   3:     $(this.element)
   4:         //  build the DIV structure
   5:         .html('<div class="progress-outer"><div class="progress-inner"><div class="progress-indicator"></div></div></div>');
   6: }

The init function is a special function where you can place any of the logic your widget needs to execute as it is spinning up.  Like the toolkit behaviors, there is a way to access the DOM element the widget has been applied to (using this.element).  

 

2.  After implementing init, I then started filling in the blanks with the stuff I needed to animate my progress bar.  First, I decided that my progress bar was going to support 2 public methods:  start and stop.  So I updated the skeleton to include these functions as well.  A couple of things to note here ...

  • I am making use of jQuery's queue function to control running the animations.  When start is called, I track down the progress-indicator DIV (this is the one that contains the progress background image) and kick off the animation function passing the function both a reference to the DOM element as well as the options object.
  • When stop is invoked I flush the animation queue by calling stop on the animated element
  • You can learn more about jquery effects here
   1: $.widget("ui.progressbar", {
   2:     init: function() {
   3:         //  wrap the element in the required markup
   4:         $(this.element)
   5:             //  build the DIV strucutre
   6:             .html('<div class="progress-outer"><div class="progress-inner"><div class="progress-indicator"></div></div></div>');
   7:     },
   8:     
   9:     start: function() {
  10:         var o = this.options;
  11:         
  12:         //  stop the animation if it is running
  13:         this.stop();        
  14:         
  15:         //  start the animation
  16:         $('.progress-indicator', this.element)
  17:             //  queue up the animation function
  18:             .queue(function(){$.ui.progressbar.animations[o.animation](this, o);})
  19:             //  and let it rip
  20:             .dequeue();
  21:     },
  22:     
  23:     stop: function() {
  24:         //  stop the animation and set its width
  25:         //  back to zero
  26:         $('.progress-indicator', this.element).stop(true).width(0);
  27:     }
  28: });

 

3.  Next, I worked on creating the options that I wanted my progress bar to support.  I knew I needed the basics like what speed to run the animation at and if the animation should begin when the progressbar is created, but I also thought it would be neat if I could parameterize the animation algorithm.  So to do this, I filled in the defaults object with my default option values for the auto start and speed parameters.  But I also provide a third value - animation that specifies the name of the animation algorithm that should run when the progress bar is playing.  Here is what my default options look like ...

   1: $.extend($.ui.progressbar, {
   2:     defaults: {
   3:         //    start the progressbar as soon as it is created
   4:         autostart:true,
   5:         //    provide a speed for the animation 
   6:         speed:1000,
   7:         //    default the animation algorithm to the basic slide
   8:         animation:'slide'
   9:     }
  10: });

 

4. Next, I added another property to my ui.progressbar widget that contains the list of functions that implement the animation algorithms.  I settled on 4 core animation's:

  • slide: basic left to right animation
  • slideback: left to right, then right to left
  • slidethru: left to right, then keep the image pushing through (can't quite explain this one, you better check it out on the demo page)
  • fade: fade the image in before sliding

Here is how I add these core animations to my progressbar widget. 

   1: $.extend($.ui.progressbar, {
   2:     defaults: {
   3:         autostart:true,
   4:         speed:1000,
   5:         animation:'slide'
   6:     },
   7:     animations : {
   8:         slide: function(e, options) {
   9:             //  set the width to zero
  10:             $(e).width(0);    
  11:             //  animate
  12:             var args = arguments;
  13:             $(e).animate({width: $(e).parent().width()}, options.speed, function(){ args.callee.call(this, e, options); } );
  14:         },
  15:         slideback: function(e, options) {
  16:             //  set the width to zero
  17:             $(e).width(0);    
  18:             //  animate
  19:             var args = arguments;
  20:             $(e)
  21:                 .animate({width: $(e).parent().width()}, options.speed)
  22:                 .animate({width: 0}, options.speed, function(){ args.callee.call(this, e, options); } );
  23:         },
  24:         slidethru: function(e, options) {
  25:             //  set the position and left
  26:             $(e).css({width: 0, position:'absolute', left:'0px'});                     
  27:             
  28:             //  animate
  29:             var args = arguments;
  30:             $(e)
  31:                 .animate({width: $(e).parent().width()}, options.speed)
  32:                 .animate({left: $(e).parent().width()}, options.speed, function(){ args.callee.call(this, e, options); } );
  33:         },
  34:         fade: function(e, options) {
  35:             //  set the width to zero
  36:             $(e).css({width: 0, opacity: '1.0'});  
  37:                         
  38:             //  animate
  39:             var args = arguments;
  40:             $(e)
  41:                 .animate({width: $(e).parent().width()}, options.speed)
  42:                 .animate({opacity: '0.0'}, options.speed, function(){ args.callee.call(this, e, options); });
  43:         }                                                   
  44:     }
  45: });

 

But, the real cool part about the $.extend function is that if you wanted to define your own custom animations, you just have to extend my $.ui.progressbar.animations object and add what ever function you want to define your custom animation.  Then, you just update the animation value to point to your new animation algorithm.  To test this out, I created 2 more animation functions that live outside of the progressbar widget, but are made available through the use of extend.  Here are my 2 custom animation functions ...

   1: $.extend($.ui.progressbar.animations, {
   2:     fancy: function(e, options) {
   3:         //  set the width to zero
   4:         $(e).width(0);    
   5:         //  animate
   6:         var args = arguments;
   7:         $(e)
   8:             .animate({width: '75%'}, options.speed)
   9:             .animate({width: '25%'}, options.speed)
  10:             .animate({width: '100%'}, options.speed)
  11:             .animate({width: '0%'}, options.speed, function(){ args.callee.call(this, e, options); });
  12:     },
  13:     glow: function(e, options) {
  14:         //  set the width to zero
  15:         $(e).css({opacity: '0.1', width:'0px'});
  16:         //  animate
  17:         var args = arguments;
  18:         $(e)
  19:             .animate({width: '100%'}, {duration: options.speed, queue:false})
  20:             .animate({opacity: '1.0'}, options.speed)
  21:             .queue(function(){
  22:                 $(this).width(0);
  23:             }).dequeue()
  24:             .animate({width: '100%'}, {duration: options.speed, queue:false})
  25:             .animate({opacity: '0.1'}, options.speed, function(){ args.callee.call(this, e, options); });
  26:     }                                                        
  27: }); 

 

And the script that runs this is located on the main page (i.e. outside of my plugin).  And everything worked wonderfully.

 

How do I Use a Widget

You can create a new widget just like you call a jQuery plugin.  Below I am passing my custom options through to the progressbar widget and then invoking the start function.

   1: $(document).ready(function(){
   2:     //  find the element and fireup the
   3:     //  progressbar using the fade animation
   4:     $('#fade .progress')
   5:         //  don't start it right away
   6:         .progressbar({animation: 'fade', autostart:false})
   7:         //  wait until .... NOW!
   8:         .progressbar('start');
   9: });

 

Footprint

image 

Note: ui.progressbar.js and ui.core.js are not minified.

 

Conclusion

Well, that is my first attempt at building a jQuery widget.  I know I probably missed some things, but I certainly learned a lot about jQuery's effects as well as some of the basics about creating a jQuery widget.     

 

Widget Source

And just for a reference, here is the complete source for the progressbar.

   1: (function($) {
   2:     $.widget("ui.progressbar", {
   3:         init: function() {
   4:             //  wrap the element in the required markup
   5:             $(this.element)
   6:                 //  build the DIV strucutre
   7:                 .html('<div class="progress-outer"><div class="progress-inner"><div class="progress-indicator"></div></div></div>');
   8:             
   9:             //  kick off the animation
  10:             if(this.options.autostart){
  11:                 this.start();
  12:             }
  13:         },
  14:         
  15:         start: function() {
  16:             var o = this.options;
  17:             
  18:             //  stop the animation if it is runnin
  19:             this.stop();        
  20:             
  21:             //  start the animation
  22:             $('.progress-indicator', this.element)
  23:                 //  queue up the animation function
  24:                 .queue(function(){$.ui.progressbar.animations[o.animation](this, o);})
  25:                 //  and let it rip
  26:                 .dequeue();
  27:         },
  28:         
  29:         stop: function() {
  30:             //  stop the animation and set its width
  31:             //  back to zero
  32:             $('.progress-indicator', this.element).stop(true).width(0);
  33:         }
  34:     });
  35:  
  36:     $.extend($.ui.progressbar, {
  37:         defaults: {
  38:             autostart:true,
  39:             speed:1000,
  40:             animation:'slide'
  41:         },
  42:         animations : {
  43:             slide: function(e, options) {
  44:                 //  set the width to zero
  45:                 $(e).width(0);    
  46:                 //  animate
  47:                 var args = arguments;
  48:                 $(e).animate({width: $(e).parent().width()}, options.speed, function(){ args.callee.call(this, e, options); } );
  49:             },
  50:             slideback: function(e, options) {
  51:                 //  set the width to zero
  52:                 $(e).width(0);    
  53:                 //  animate
  54:                 var args = arguments;
  55:                 $(e)
  56:                     .animate({width: $(e).parent().width()}, options.speed)
  57:                     .animate({width: 0}, options.speed, function(){ args.callee.call(this, e, options); } );
  58:             },
  59:             slidethru: function(e, options) {
  60:                 //  set the position and left
  61:                 $(e).css({width: 0, position:'absolute', left:'0px'});                     
  62:                 
  63:                 //  animate
  64:                 var args = arguments;
  65:                 $(e)
  66:                     .animate({width: $(e).parent().width()}, options.speed)
  67:                     .animate({left: $(e).parent().width()}, options.speed, function(){ args.callee.call(this, e, options); } );
  68:             },
  69:             fade: function(e, options) {
  70:                 //  set the width to zero
  71:                 $(e).css({width: 0, opacity: '1.0'});  
  72:                             
  73:                 //  animate
  74:                 var args = arguments;
  75:                 $(e)
  76:                     .animate({width: $(e).parent().width()}, options.speed)
  77:                     .animate({opacity: '0.0'}, options.speed, function(){ args.callee.call(this, e, options); });
  78:             }                                                   
  79:         }
  80:     });
  81: })(jQuery);

 

That's it.  Enjoy!


TrackBack

TrackBack URL for this entry:
http://mattberseth.com/blog-mt/mt-tb.fcgi/138

Listed below are links to weblogs that reference jQuery.UI ProgressBar Widget:

» Innerhtml Urls from Urlrecorder - URL sharing
Your url was recorded with keywords innerhtml! [Read More]

Comments


Posted by: Pk on July 15, 2008 12:00 AM

Very nice. Im Trying to go away from toolKit.

Great job.

Hey, great widget! Ive actually been working to make my password strength meter a widget too, Ive got most of it done but Im not happy with the animation on the strength bar.

Im going to look at your plugin as a dependency to create the graphical element of it, as its exactly what im looking for.

This is a marvelous tutorial Matt. Thanks for sharing!

Posted by: Andy Hough on July 16, 2008 12:00 AM

Great stuff Matt, been following your blog for a while. Always useful info. Only started unsing jquery in a production environment a month or so ago, so easy to learn coming from a javascript/css background - no going back now. Keep the articles coming....

Perfectly done Matt, and I wished to show how far your post were easy to follow and build simple widget that value.
I wonder why trackbacks are not working from my blog! it used to!! It should trackback here! do my posts rated as spams?!
I should check that with Mad

Good post! Ive been checking out jQuery lately; writing a type of GridView calendar display that has Prev and Next images to slide to the next or previous year from left to right. Thus far, its been a fun adventure. I reputably like to write my own JS, but minified and GZipped that library is almost too hard to pass up.

BTW, your RSS feed is not up to date; I got to this post throuh Muhammads blog. I think you might need a MT plugin to ping FeedBurner everytime you add a new post.

Best regards...

@Pk -
I am not at that point yet, but I am getting closer. I read through MSFTs road map document for ASP.NET AJAX and I am at least encouraged in MSFT dedicating a few more resources to the toolkit and adding jQuery style features like $query.

@Tane Piper -
That sounds cool. Let me know how it turns out.

@Richard D Worth -
No problem.

@Andy Hough -
Thanks Andy. I am still pretty new to jQuery too, but it is so easy to learn and I have really fell in love with the elagance of the API.

@Muhammad Mosa -
I think the problem with the tracebacks is on my side. I am not sure exactly what is happeneing, but my blog (MT 3.2) will randomly eat tracebacks and comments. I gotta look into this ...

@Josh -
Hi Josh. Be sure to post about that gridview/calendar widget. It sounds pretty interesting.

And thanks for letting me know about my feed. I will take a look.

Great, great work, Matt! Exactly where jQuery is the best!

Hopefully I can find the time to do so. Its not actually a "widget" per se, but perhaps I can use this post to help me turn it into one.

Posted by: Tony on October 8, 2008 12:56 AM

Great plugin.

Just one or two things, set the speed to 4 seconds minimum and look at your processor !? Especially if you have more than one on a same page

A progress bar must start and stop after x seconds or its just an animated bar. So is there a way to stop the animation after x seconds and not restart everytime?

Post a comment

(If you haven't left a comment here before, you may need to be approved by the site owner before your comment will appear. Until then, it won't appear on the entry. Thanks for waiting.)

Consulting Services

Yep - I also offer consulting services. And heck, I'll do just about anything. If you enjoy my blog just drop me an email describing the work you need done.

Recent Comments

  • Tony wrote: Great plugin. Just one or two things, set the speed to 4 seconds minimum and look at your processor...
  • Matt Berseth wrote: @Pk - I am not at that point yet, but I am getting closer. I read through MSFTs road map document f...
  • Janko wrote: Great, great work, Matt! Exactly where jQuery is the best! ...
  • Josh Stodola wrote: Hopefully I can find the time to do so. Its not actually a "widget" per se, but perhaps I can use t...
  • Tane Piper wrote: Hey, great widget! Ive actually been working to make my password strength meter a widget too, Ive g...
  • Richard D. Worth wrote: This is a marvelous tutorial Matt. Thanks for sharing! ...
  • Andy Hough wrote: Great stuff Matt, been following your blog for a while. Always useful info. Only started unsing jque...
  • Muhammad Mosa wrote: Perfectly done Matt, and I wished to show how far your post were easy to follow and build simple wid...