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
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
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!