Wednesday 25 May 2011

jQuery progressbar

The controls found in the AjaxControlToolkit fit perfectly into the ASP.NET programming model (as to be expected).  You can configure the controls properties through the markup which in turn affects how the control's display and behavior on the client.  Many of the control's properties support databinding so using the controls within a databound control like a ListView or a GridView is no big deal.  Take for example the ProgressBar Toolkit control.  One of the places that I use this control is in a GridView's TemplateField.  Instead of displaying the percentage as a text value, I use the ProgressBar to display the percentage in a more visual manner.  It looks something like this ...



With the Toolkit, setting this up is really simple.  Just use a databinding expression to set the Value property of the ProgressBar control to the value of the PercentComplete property of the databound item.  Done.
   1: <mb:ProgressControl 
   2:     ID="ProgressControl17" runat="server" 
   3:     Value='<%# Eval("PercentComplete") %>' 
   4:     Animate="true" Mode="Manual" ShowStatusText="false" 
   5: />

And if I start using and building jQuery plugins I am thinking there will be cases where I am going to miss how simple this is.  So after learning about jQuery's metadata plugin, I thought it would be interesting to see explore it a bit more to see if it could be used to enable some of the common scenarios.  There are 2 things the metadata plugin has going for it that I think will help it fit into the ASP.NET programming model ...
  • If a jQuery plugin supports the metadata plugin in, you can specify the plugin options on a per element basis by using some JSON within the elements class attribute to specify the option properties you would like to use.  To me this really doesn't feel too different than pointing a Toolkit control (using TargetControlID) at an existing web control.
  • It can all be done via markup - no need to emit any extra javascript

Creating the Plugin

Before I show how this can be done using the ListView, I thought I would show what the jQuery plugin looks like (granted it doesn't have nearly as many features as the Toolkit version, but it still very useful for the data grid scenario).  Here is how the plugin works ...
  1. Coalesce the default options together with any options that are explicitly provided when the progressbar plugin is applied (Line #5)
  2. Inject the DIV elements that are used for styling the progress bar (Line #9)
  3. Check to see if the metedata plugin is available.  If it is override any of the element specific options (Line 12)
  4. Finally, use find to locate the progress_indicator DIV whose background image is set to the progress image (this is applied via the stylesheet).  Set the title attribute of this element and animate the width to the specified value. (Lines #16-#20)
Could it get any simpler?
   1: (function($) {
   2:  
   3:     $.fn.progressbar = function(options) {
   4:         // build main options before element iteration
   5:         var opts = $.extend({}, {value:0, tooltip:''}, options);
   6:  
   7:         return this.each(function() {
   8:             //  add the progress DOM structure
   9:             $(this).html('<div class="progress_outer"><div class="progress_inner"><div class="progress_indicator"></div></div></div>');
  10:             
  11:             //  if the metadata plug-in is installed, use it to build the options
  12:             var o = $.metadata ? $.extend({}, opts, $(this).metadata()) : opts;
  13:             
  14:             //  locate the DOM element that contains
  15:             //  the progress image        
  16:             $(this).find('.progress_indicator')
  17:                 //  add the tooltip
  18:                 .attr('title', o.tooltip)            
  19:                 //  and animate the width
  20:                 .animate({width: o.value + '%'}, 'slow');
  21:         });
  22:     };
  23:  
  24: })(jQuery);

Using the jQuery progressbar Plugin with the ListView

Then, I can use databinding expressions to encode the tooltip and value options using the databinding expression.  It isn't as pretty, but it defiantly works.  All of the magic happens in line #16.  And if you set the control to runat server, you could populate this value from the codebehind as well. 
   1: <asp:ListView ID="lvWorkItems" runat="server" DataSourceID="ldsWorkItems">
   2:     <LayoutTemplate>
   3:         <table class="yui-grid" cellspacing="0" cellpadding="0">
   4:             <tr class="hdr">
   5:                 <th><asp:LinkButton ID="btnIDSort" runat="server" Text="ID" CommandName="Sort" CommandArgument="ID" /></th>
   6:                 <th><asp:LinkButton ID="LinkButton1" runat="server" Text="Name" CommandName="Sort" CommandArgument="Name" /></th>
   7:                 <th><asp:LinkButton ID="LinkButton2" runat="server" Text="Percent Complete" CommandName="Sort" CommandArgument="PercentComplete" /></th>
   8:             </tr>
   9:             <tr id="itemPlaceholder" runat="server" />
  10:         </table>
  11:     </LayoutTemplate>
  12:     <ItemTemplate>
  13:         <tr class='<%# Container.DataItemIndex % 2 == 0 ? "row" : "altrow" %>'>
  14:             <td><%# Eval("ID") %></td>
  15:             <td><%# Eval("Name") %></td>
  16:             <td><div class='progressBar {value: "<%# Eval("PercentComplete") %>", tooltip:"<%# string.Format("Task {0} is {1}% complete!", Eval("ID"), Eval("PercentComplete")) %>"}'></div></td>
  17:         </tr>
  18:     </ItemTemplate>
  19: </asp:ListView> 

Applying the Plugin

And the only remaining bit of awkwardness is that the progressbar plugin needs to be applied twice.  Once when the page first loads.
   1: function pageLoad(sender, args){
   2:     if(!args.get_isPartialLoad()){ 
   3:         //  apply the 
   4:         $('.progressBar').progressbar();
   5:     }
   6: }

And then again just after an UpdatePanel is refreshed (I apply it to the panels new contents) ...
   1: Sys.WebForms.PageRequestManager.getInstance().add_pageLoaded(function(sender, args){
   2:     var updatedPanels = args.get_panelsUpdated();
   3:     if(updatedPanels && updatedPanels.length > 0){
   4:         for(var i = 0; i < updatedPanels.length; i++) {
   5:              $('.progressBar', updatedPanels[i]).progressbar();
   6:         }
   7:     }                    
   8: });

Footprint

And as promised, here is the JavaScript and CSS footprint for this example.  I am using ASP.NET AJAX's ScriptManager and UpdatePanel controls - so those are the axd JavaScript references.  The JavaScript for my plugin is ~1 KB unminified.
JavaScript: 113 KB
image
CSS: 2 KB
image

No comments:

Post a Comment