glowbuttons - Writing My First jQuery Plugin

So I figure what better way to learn more about jQuery than to dig right in and create a plugin or two?  At first I thought this sounded pretty intimidating, but after browsing through some of the existing jQuery plugins I figured it might be easy enough to try and 'rearrange' one of the Toolkit controls that I created.  So that is what I did with my GlowButtonExtender control I created a few months back (and I plan on doing it with the ProgressBar one as well very soon).  I kind of liked this because I thought creating the same UI widget using both the Toolkit and jQuery might give a little more insight into what the relative advantages are between the two frameworks.

In case you didn't see my original post on creating the GlowButtonExtender control, here is how it works:  When you mouse over the button, it glows as it comes into focus and of course when the mouse leaves the glow slowly fades away.  These are just screen shots so you will have to check out the demo page to see it in action.   

Live Demo (IE7, FF and Opera)* | Download

* IETester seems to be on the fritz - so I wasn't able to test in IE6.

image imageimageimageimage

Footprint

Before I jump into the details, I just thought that I would mention that from now on, all of my posts (at least the ones that include a live demo) will include a section titled Footprint that has a brief discussion on the size of JavaScript and CSS resources my post is making use of.  I really should have been doing this all along, but better late than never, right?  So here it is for this post.  I am using minified versions of jQuery and the metadata plugin (58 KB total combined) plus the color plugin (another 4 KB).  And finally, the JavaScript for my glowbuttons plugin is a whopping 3 KB un-minified plus another 2 KB for the CSS. 

JavaScript: Total 64 KB

image

CSS: Total 2 KB

image

 

Basic jQuery Plugin Template

So to get started creating my glowbutton plugin I first did some googling and came across a great resource that goes over some of the basics about creating jQuery plugins.  I recommend reading (and understanding) the code samples.  Below is the shell that I started with for my plugin.  I would pay particular attention to the following ...

  • There is a decent amount of plumbing that really needs to be included in your plugin to make sure it can play together nicely with other jQuery plugins as well as other JavaScript libraries
    • Notice the first and last lines.  The plugin is wrapped in a self executing function that takes a single parameter '$'.  And the last line of code invokes the function passing in the global jQuery object.  If you follow this pattern you can safely use the $ shortcut to refer to the jQuery object without worrying about colliding with other bits of JavaScript code that might also be running on the client.  If you don't write your plugin using this technique I believe you should not be using the $ shortcut at all (or do at your own peril).
    • Line 5 I am creating the entry point to my glowbuttons plugin and adding it to the $.fn object
    • If you notice in line 5, my plugin accepts a single parameter called options.  I am using lines 19 through 24 to define the options my plugin supports as well as the default values for each of the properties.  Line 8 examines the options my plugin is provided along with the default option values and fills in any missing options with the defaults.  If no options are supplied all of the default values will be used.
    • Line 10 is where we actually start doing something.  Here we iterate over each of the elements in the wrapped set and do something with them.  Two things are important here.  The first is that I am returning this which is a reference to the wrapped set.  This allows users to chain calls together creating the nice compact, fluent interface that jQuery is know for.  And the second is that where I have the comment 'do something with $(this)' is where the actual plug-in logic goes.  We will see that soon. 
   1: (function($) {
   2:   //  add our glowbuttons function.  it accepts
   3:   //  a single parameter that specifies any parameters
   4:   //  our plugin supports
   5:   $.fn.glowbuttons = function(options) {
   6:     
   7:     // build main options before element iteration
   8:     var opts = $.extend({}, $.fn.glowbuttons.defaults, options);
   9:     
  10:     return this.each(function() {
  11:  
  12:         //  do something with $(this).
  13:         //  this is where all of the core plug in code belongs
  14:  
  15:     });
  16:   };
  17:   
  18:   // default options - these are used of none others are specified
  19:   $.fn.glowbuttons.defaults = {
  20:     from: '#016bbd',
  21:     to: '#b1ddff',
  22:     className: 'blue',
  23:     speed: 1000
  24:   };
  25:  
  26: //  invoke the function we just created passing it
  27: //  the jQuery object
  28: })(jQuery);

 

Adding the GlowButton Plugin Logic

So after creating the template, I went back to my existing GlowButtonsExtender logic and started moving it over into my plugin.  My existing plugin did basically 3 things

  • Injected 2 SPAN's surrounding the button that I am using for styling (border and background)
  • Injected some browser-specific style workarounds
  • Setup 2 Toolkit animations for animating the background color as the mouse enters and leaves the button

And adding this logic into the plugin was really easy.  Below I am just showing the part where I iterate over the wrapped set, but you can see that on line 4 I am wrapping the button in 2 spans using the wrap function.  Then I navigate up to the immediate parent nodes and apply some browser hacks (I left them out of the code snippet here because they don't add much value).  And finally, I attach to the outer most SPAN's hover events and run a simple color animation when the button is hovered over. 

   1: return this.each(function() {
   2:     var button = $(this);
   3:     //  inject the parent nodes            
   4:     button.wrap('<span class="glow-button"><span class="inner"></span></span>');
   5:     
   6:     //  ** do some browser specific style workarounds
   7:     
   8:     button.parent().each(function(){ 
   9:         var innerWrapper = $(this);
  10:         
  11:         //  ** do some browser specific style workarounds
  12:         
  13:         innerWrapper.parent().each(function(){
  14:         
  15:             //  ** do some browser specific style workarounds
  16:         
  17:         })            
  18:         //  add a class to the outer most node - this helps with theming
  19:         .addClass(o.className)
  20:         //  finally attach to the hover events to run the animation
  21:         .hover(
  22:             function(){
  23:                 $(this).stop();
  24:                 $(this).animate({ backgroundColor: o.to }, o.speed);
  25:             },
  26:             function(){
  27:                 $(this).stop();
  28:                 $(this).animate({ backgroundColor: o.from }, o.speed);
  29:             }
  30:         );                     
  31:     });
  32: });

 

And now I can do things like this ...

   1: $(document).ready(function(){   
   2:     $('.glow').glowbuttons();
   3: }); 

 

... and all of the INPUTs on my page with the glow CSS class will start glowing.

 

Overriding Options with the Metadata Plugin

And you could stop there if you want and have a pretty useful plugin.  But, you can make your plugin even more flexible by adding support for the Metadata plugin.  The Metadata plugin allows you to override the option values on a per element basis.  So in the code snippet immediately above this I am applying the default options to all of the glow elements on the page.  But that isn't always what you want - and that is where the Metadata plugin becomes useful.  With this plugin users can override the default options by specifying option values using the class attribute as follows ...

   1: <asp:Button ID="Button1" runat="server" CssClass="glow" Text="Sign Up Now!" />        
   2: <br />
   3: <br />
   4: <asp:Button ID="Button2" runat="server" CssClass="glow {from: '#111111', to: '#555555', className: 'dark'}" Text="Sign Up Now!" />        
   5: <br />
   6: <br />
   7: <asp:Button ID="Button3" runat="server" CssClass="glow {from: '#79B837', to: '#C7EB6E', className: 'green', speed: 500}" Text="Sign Up Now!" />        
   8: <br />
   9: <br />   
  10: <asp:Button ID="Button4" runat="server" CssClass="glow {from: '#9C0063', to: '#D693BD', className: 'purple'}" Text="Sign Up Now!" />  

 

And now we can still fire the same logic on document ready, but now because we are specifying different parameter values on a per element basis, we end up rendering the following buttons.

image

And best of all, to support this all we have to do us update our plugin and add a single line of code.  In the snippet below I updated the template to check first check the current element (i.e. $(this)) to see if it has any metadata properties defined.  If it does we will honor these values.  And if the metadata plugin isn't available, we just fall back and use what ever options are available.

   1: (function($) {
   2:   //  add our glowbuttons function.  it accepts
   3:   //  a single parameter that specifies any parameters
   4:   //  our plugin supports
   5:   $.fn.glowbuttons = function(options) {
   6:     
   7:     // build main options before element iteration
   8:     var opts = $.extend({}, $.fn.glowbuttons.defaults, options);
   9:     
  10:     return this.each(function() {
  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:         //  ... plugin logic
  15:         
  16:     });
  17:   };
  18:   
  19:   // default options - these are used of none others are specified
  20:   $.fn.glowbuttons.defaults = {
  21:     from: '#016bbd',
  22:     to: '#b1ddff',
  23:     className: 'blue',
  24:     speed: 1000
  25:   };
  26:  
  27: //  invoke the function we just created passing it
  28: //  the jQuery object
  29: })(jQuery);

 

And finally, here is the complete source code for the plugin. 

   1: (function($) {
   2:   $.fn.glowbuttons = function(options) {
   3:     // build main options before element iteration
   4:     var opts = $.extend({}, $.fn.glowbuttons.defaults, options);
   5:     
   6:     return this.each(function() {
   7:         var button = $(this);
   8:         //  if the metadata plug-in is installed, use it to build the options
   9:         var o = $.metadata ? $.extend({}, opts, button.metadata()) : opts;
  10:         //  inject the parent nodes            
  11:         button.wrap('<span class="glow-button"><span class="inner"></span></span>');
  12:         //  ie display workaround
  13:         button.css('display', $.browser.msie ? 'inline-block' : 'block');
  14:         
  15:         button.parent().each(function(){ 
  16:             var innerWrapper = $(this);
  17:             
  18:             //  more browser specific workarounds    
  19:             innerWrapper.css('display', $.browser.msie ? 'inline-block' : 'block'); 
  20:             if($.browser.msie) {
  21:                 innerWrapper.css({ 'position':'relative', 'left':'-1px' });
  22:             }
  23:             
  24:             innerWrapper.parent().each(function(){
  25:                 var outerWrapper = $(this);
  26:                 outerWrapper.css('display', $.browser.mozilla ? '-moz-inline-box' : 'inline-block');
  27:                 outerWrapper.css('backgroundColor', o.from);
  28:                 
  29:                 //  our glossy image is a transparent PNG so
  30:                 //  we have a special class that uses an image filter
  31:                 if($.browser.msie && $.browser.version < 7) {
  32:                     outerWrapper.addClass('ie6');    
  33:                 }                     
  34:             })
  35:             //  add a class to the outer most node - this helps with theming
  36:             .addClass(o.className)
  37:             //  finally attach to the hover events to run the animation
  38:             .hover(
  39:                 function(){
  40:                     $(this).stop();
  41:                     $(this).animate({ backgroundColor: o.to }, o.speed);
  42:                 },
  43:                 function(){
  44:                     $(this).stop();
  45:                     $(this).animate({ backgroundColor: o.from }, o.speed);
  46:                 }
  47:            );                     
  48:         });
  49:     });
  50:   };
  51:  
  52:   $.fn.glowbuttons.defaults = {
  53:     from: '#016bbd',
  54:     to: '#b1ddff',
  55:     className: 'blue',
  56:     speed: 1000
  57:   };
  58:  
  59: })(jQuery);

 

What am I doing to learn more about jQuery?

So far I have found jQuery pretty easy to learn.  But I have also only just started so I feel like I have a lot of catching up to do.  Here is what I am doing to learn more about jQuery ...

  • Take Scott Hanselman's advice and become a better dev by reading more code.  The existing jQuery plugins are pretty readable.  I think once you get past the seemingly goofy JavaScript syntax for controlling scoping the rest is pretty straight forward.  Browse the plugins and see how they work.  I think you might be surprised at how simple they are (I know I was)
  • Subscribe to jQuery's discussion board over on Google Groups. 
  • Pick up a JavaScript reference book
  • Check out learningjquery.com it has some great information.  Plus I was over there today and stumbled onto a comment that Martin Fowler made (obviously I don't know that it was him for sure, but you never know).
  • Both Dave Ward and Rick Strahl blog fairly regularly on using jQuery with ASP.NET.  So if you haven't yet, subscribe to their feeds. (here and here)

 

That;s it.  Enjoy!


TrackBack

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

Listed below are links to weblogs that reference glowbuttons - Writing My First jQuery Plugin:

» Weekly Web Nuggets #18 from Code Monkey Labs

Love him or hate him - if you're reading this, there's a very good chance that there's something in your life that has been affected by him. Who? Bill Gates! Today is his last day at Microsoft. The good fellas at Engadget have deemed today Bill Gate...

[Read More]

Comments


Nice work on the plugin, Matt!

One thing you might consider in the footprint section is that Google is providing a CDN for jQuery. So, compared to the Toolkit and/or ScriptManager scripts, the base jQuery library almost doesnt count.

Good call on the jQuery mailing list. I just subscribed to it (no clue why I hadnt before).

First Id like to say that I love your blog. I am also just getting started with jQuery and ASP.NET and just wrote my first plugin... mine was much simpler. Anyway, keep up the great material.

Secondly, I think that comment was left by Mark Fowler, not Martin Fowler.

Posted by: Corey on June 26, 2008 12:00 AM

I highly recommend jQuery in Action. Its a good read and the samples for the book are good as well. The book also highlights some of the more popular / useful plug-ins.

Posted by: Shankar on June 27, 2008 12:00 AM

Nice Post!. I am in the process of getting enlightened on Jquery.

Thanks for your excellent post. Of course, your posts are always excellent, so thats a bit redundant ;) I can already see where writing a plugin similar to yours would result in much cleaner code and would be easier to refactor, instead of wrapping a bunch of elements in multiple spans and divs in order to accomplish something like a CSS sliding-door technique. I really appreciate the "footprint" and your other recent post that offered a comparison between equivalent solutions. Great stuff!

Posted by: Steve on June 27, 2008 12:00 AM

Excellent Matt! I like your decision to build the plugin approach.

@Dave -
Good point with getting jQuery from Google. I will be doing that with future posts. Thanks Dave.

@Andrew -
LOL - You are right. Thats Mark NOT Martin. I must have had Martin on my mind (I just picked up his new refactoring HTML book so maybe thats it). Thanks for pointing that out.

And did I mention billg left a comment on my blog yesterday - I am fairly certain that it was bill gates ;) Am I losing credibility yet?

@Corey -
Good addition. I have that book too and find it very useful.

@Shankar -
Have fun!

@Steve -
Thanks Steve.

@David -
Thanks David. Thats an interesting thought about doing the sliding doors stuff via a plugin. Let me know how it turns out.

I saw you were unable to test it in IE 6. I just ran it in IE 6 and it does not work properly. The buttone come in all greyed out and when you hover over them, they flicker. As a matter of fact, the hourglass stays on as long as you are hovering over a button.

This post should be kicked,
Thank you for the clean code Matthttp://www.dotnetkicks.com/aspnet/Writing_My_First_jQuery_Plugin

Posted by: Isaak Malik on June 27, 2008 12:00 AM

Kind thanks, Matt Berseth. I was planning to create such a plugin myself but there is no need for it anymore :).

Currently I cant check IE 6 compatibility as Im on Linux now but to fix the IE6 flickering just add an Expires header to the button images.

Thanks again!

Posted by: Amjad on June 28, 2008 12:00 AM

Excellent Matt! but i am facing the problem to how i adjust the height of button.

Posted by: Sridhar on June 28, 2008 12:00 AM

Hi Matt,

Thanks for posting an excellent article. I just have a quick question. When I am reading the book "jQuery in Action" (an excellent book by the way) I read that the authors are mainly focussed on unobtrusive javascript. It means separating the behavior from mark-up. In that aspect should we use "CssClass="glow {from: #111111, to: #555555, className: dark}" in the mark-up? Is there some other way to do this?

Thanks,
sridhar.

Posted by: Pk on June 29, 2008 12:00 AM

How do you make "Continuous Glowing", like in your original post with AjToolKit.

Thanx

Posted by: padam on June 29, 2008 12:00 AM

Matt,
please, fix it fox ie 6 also.

@Scott Williams, Pk -
I will see if I cant get this fixed tonight. If anyone has already spotted what is wrong, please let me know ;)

@Muhammad Mosa -
Thanks!

@Isaak Malik -
Man x-browser support gets tedius ;) Thanks for the tip.

@Amjad -
Are you looking to go larger or smaller? Either way the best wat to do it would be to open the glossy png image in Paint.NET and scale it to what ever size you would like. Then update the supporting CSS to use this height.

@Sridhar -
Well, the good news is that you can create plug-ins so that the metadata plugin isnt required and the plugin will continue to function normally without it. I am really not the right person to answer the unobtrusive question though. You could try posting that question over on jQuerys discussion board to see what kind of anwsers you get.

Posted by: Pk on June 30, 2008 12:00 AM

Hey Matt, Thanx for looking. Ill be waiting.

@Pk -
Ok, I updated it to work in IE6. Let me know if you run into any problems.

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

Hey Matt,
Thanx for looking, but i was talking about "Continuous Glowing".

Posted by: Vimal Saifudin on August 10, 2008 09:09 AM

Hi,
Excellent post.... But for me a button inside an UpdatePanel doesnt glow..

Posted by: praveent on September 19, 2008 08:46 PM

Hi Matt
Its still not working with IE6
cloneNode property is throwing error

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

  • praveent wrote: Hi Matt Its still not working with IE6 cloneNode property is throwing error...
  • Vimal Saifudin wrote: Hi, Excellent post.... But for me a button inside an UpdatePanel doesnt glow.....
  • Pk wrote: Hey Matt, Thanx for looking, but i was talking about "Continuous Glowing". ...
  • Matt Berseth wrote: @Scott Williams, Pk - I will see if I cant get this fixed tonight. If anyone has already spotted wh...
  • Pk wrote: Hey Matt, Thanx for looking. Ill be waiting. ...
  • Matt Berseth wrote: @Pk - Ok, I updated it to work in IE6. Let me know if you run into any problems. ...
  • Pk wrote: How do you make "Continuous Glowing", like in your original post with AjToolKit. Thanx ...
  • padam wrote: Matt, please, fix it fox ie 6 also. ...