At the Forge - Writing jQuery Plugins
The past two months, this column has looked at the jQuery library for JavaScript programming. jQuery is one of several popular libraries (like Prototype, YUI and Dojo) that have sprouted up in the last few years, making it possible to use JavaScript in ways that make the Web more satisfying and responsive by incorporating desktop-like behavior.
Part of the reason for jQuery's popularity is the huge library of plugins available for it. There are plugins for almost any type of functionality you can imagine—from GUI widgets to navigational aids to textual transformations. Plugins make it possible to isolate and reuse certain behaviors, achieving a goal known in the Ruby world as DRY (don't repeat yourself).
As I showed last month, using a plugin is generally quite easy. Download the plugin; install any CSS and JavaScript files that come with it, and then incorporate the JavaScript file into one or more HTML pages on your site, using a standard <script> tag. Finally, attach the plugin to one or more elements on the page, using jQuery's event-handling functions, typically inserted into $(document).ready.
If you use jQuery, and you find yourself repeating the same JavaScript patterns over and over, you might want to consider writing your own plugin. Whether you distribute that plugin to the rest of the jQuery community depends on a number of factors, but by making it a plugin, you make it possible for all of your applications to load and use the library in a similar way.
A jQuery plugin is a packaging mechanism for your JavaScript code. This means in order to create your plugin, you first must have some JavaScript that needs packaging.
So, as an example this month, I've decided to create a simple translator into Ubbi Dubbi. Ubbi Dubbi, as some of you may know, is a “secret” language for children that was popularized in the United States by the public TV show Zoom in the 1970s (when I watched it), and then again in the 1990s. The rules for Ubbi Dubbi are simple. Every vowel (a, e, i, o and u) is prefixed with the letters ub. So, hello becomes hubellubo. It's not very hard to teach yourself to speak Ubbi Dubbi, and it sounds hilarious. Give it a try!
In any event, let's begin by creating a basic JavaScript program, using jQuery, that turns text into Ubbi Dubbi when the mouse cursor hovers over it. Let's start with a simple HTML file called ubbi.html (Listing 1). As you can see, there is no JavaScript in this file. Rather, we will use the “unobtrusive” style that jQuery encourages, writing our JavaScript in a separate file (ubbi.js, Listing 2), which we then include by means of a <script> tag.
Listing 1. ubbi.html
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> <head> <script type="text/javascript" src="jquery.js"></script> <script type="text/javascript" src="ubbi.js"></script> <link rel="stylesheet" type="text/css" media="screen" href="ubbi.css" /> <title>Ubbi Dubbi</title> </head> <body> <h1>Ubbi Dubbi</h1> <p class="ubbi">This is in Ubbi Dubbi.</p> <p class="ubbi"> Today, we will learn how to make cereal. First, pour the cereal into a bowl. Then pour milk onto the cereal. Finally, eat the cereal with a spoon. Delicious! </p> </body> </html>
Listing 2. ubbi.js
function ubbify(text) { return text.replace(/([aeiou])/gi, 'ub$1'); } $(document).ready(function() { $(".ubbi").bind('mouseover', function() { var original_text = $(this).html(); $(this).attr({originalText: original_text}); $(this).html(ubbify(original_text)); }); $(".ubbi").bind('mouseout', function() { $(this).html($(this).attr("originalText")); $(this).attr({originalText: ""}); }); });
The HTML itself is not very surprising or exciting. We have two paragraphs of text, each of which has the class ubbi assigned to it. In the JavaScript file, we use the .ubbi selector to set handlers for the mouseover and mouseout events. This is where the magic really happens. When the mouse hovers over the specified paragraph, the text is transformed into Ubbi Dubbi. When the mouse moves away, the text returns to its original form.
The translation depends on our ubbify function, which is defined as follows:
function ubbify(text) { return text.replace(/([aeiou])/gi, 'ub$1'); }
The above JavaScript function takes a single textual argument. It replaces any vowel with the string ub, followed by the letter that was replaced. Admittedly, there's a bug here related to capitalized words that begin with a vowel. Fixing that is left as an exercise for the reader.
Our mouseover handler is defined as follows:
$(".ubbi").bind('mouseover', function() { var original_text = $(this).html(); $(this).attr({originalText: original_text}); $(this).html(ubbify(original_text)); });
This works by using jQuery's bind function, which invokes a function when a particular event fires on an HTML element (or collection of elements). So in this particular case, we tell JavaScript that every HTML element with a class of ubbi should invoke our function when the mouse cursor hovers over it. The function itself grabs the original text, puts it into an attribute named originalText, and then replaces the original text with the ubbified text.
The mouseout handler is similar, doing roughly the reverse, but without the ubbification:
$(".ubbi").bind('mouseout', function() { $(this).html($(this).attr("originalText")); $(this).attr({originalText: ""}); });
To add a bit of pizzazz and styling, we also have ubbi.css, which uses the .ubbi:hover pseudo-selector to colorize and italicize the text when the mouse is hovering over it (Listing 3).
Listing 3. ubbi.css
.ubbi:hover { font-style: italic; border: 0.5px dashed #000; background-color: #cc9999; }
The combination of the CSS and JavaScript is fun and a bit exciting. Normally, the text looks as you would expect. But, when you move your mouse over a piece of text, it is transformed into Ubbi Dubbi. Prubetty cubo-ubol, rubight?
This JavaScript works just fine. However, perhaps there is a general need for Ubbi Dubbi translators that are active when the mouse hovers over text. It would be nice if someone simply could make every paragraph in a document automatically Ubbified with:
$(document).ready(function() { $("p").ubbify(); });
In order to do this, let's create a jQuery plugin. The plugin, when incorporated, will add a new function to the jQuery object. This means that instead of our ubbify function being in the global namespace and instead of being invoked from within an event handler, we will define a function in the jQuery namespace, and it will be invoked by handlers that also are defined in that namespace.
To make this happen, we need to restructure things a bit. First, we need to rename our JavaScript file, because every plugin needs to be of the format jquery.PLUGIN.js. In this case, I will call it jquery.ubbi.js.
Next, we need to define our ubbify function such that the global jQuery object will recognize it. To do this, we define ubbify inside the jQuery namespace:
$.fn.ubbify = function () { // implementation goes here }
Wait a second—what is this $.fn that we are defining inside of? It turns out that if we want to define a global method for the jQuery object, normally aliased to $, we must assign that function to the $.fn object.
But, wait again—it is possible to redefine $ so that it is no longer an alias to the $ function. That allows jQuery to play nicely with JavaScript libraries such as Prototype, which also uses $, but in a very different way. For this reason, many jQuery plugin tutorials tell you not to use $, but rather the full jQuery object, like so:
jQuery.fn.ubbify = function () { // implementation goes here }
Another solution is to wrap the entire function definition inside a closure (that is, a function with state), giving the closure the jQuery object as an environment with variable bindings:
($.fn.ubbify = function () { // implementation goes here });
Now that we have gotten this out of the way, we can define our function inside its new plugin home. Listing 4 contains jquery.ubbi.js, a jQuery plugin that does everything we did before, but within the context of a plugin.
Listing 4. jquery.ubbi.js
(function($) { $.fn.ubbi = function(options) { // Private function function ubbify(text) { return text.replace(/([aeiou])/gi, 'ub$1'); } // Return the results of iterating over our inputs return this.each( function() { $(this).bind( 'mouseover', function() { var original_text = $(this).html(); $(this).attr({originalText: original_text}); $(this).html(ubbify(original_text)); }); $(this).bind( 'mouseout', function() { $(this).html( $(this).attr("originalText")); $(this).attr({originalText: ""}); }); }); }; })(jQuery);
One of the most interesting things about jQuery is the fact that it accepts any number of arguments, thanks to CSS selectors. A function might be called for a single paragraph, identified via a DOM ID. Or, it might be invoked on many tags, or on tags with a certain class. Our function needs to handle any or all of these, and when it's done, our function must then return the jQuery object, so that its use can be “chained” to another set of instructions.
We do this by iterating over each argument and by returning the results, as follows:
return this.each( function() { ... });
jQuery defines .each to be an iterator that operates on each element of the object that invoked it. In this case, we take each of the submitted elements and pass them to a function. The function, of course, assigns the event handlers mouseover and mouseout. Notice how the functions are now invoked on $(this), the jQuery version of the current element.
Finally, our ubbify function is defined privately within the $.fn.ubbi definition. Our ubbify function is available to any and all users within our definition of $.fn.ubbi, which is admittedly a very small number of functions for now.
With our plugin in place, all we have to do is tell our HTML file to load the plugin and to invoke it in the right way:
<script type="text/javascript" src="jquery.ubbi.js"></script> <script type="text/javascript"> $(document).ready(function() { $(".ubbi").ubbi(); }); </script>
Notice that jquery.js must be loaded before any plugins are loaded. We can apply our ubbi plugin to all of the paragraphs on a page with the following:
$("p").ubbi();
With our Ubbi plugin (plubugubin?) in place, it now has become that much easier to provide people with Ubbi Dubbi translations. Thanks to jQuery's plugin mechanism, we can distribute our plugin for others to use too, without having to read or understand the code. Our modified simple HTML file is shown in Listing 5.
Listing 5. ubbi2.html
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> <head> <script type="text/javascript" src="jquery.js"></script> <script type="text/javascript" src="jquery.ubbi.js"></script> <script type="text/javascript"> $(document).ready(function() { $(".ubbi").ubbi(); }); </script> <link rel="stylesheet" type="text/css" media="screen" href="ubbi.css" /> <title>Ubbi Dubbi</title> </head> <body> <h1>Ubbi Dubbi</h1> <p class="ubbi">This is not in Ubbi Dubbi.</p> <p class="ubbi"> Today, we will learn how to make cereal. First, pour the cereal into a bowl. Then pour milk onto the cereal. Finally, eat the cereal with a spoon. Delicious! </p> </body> </html>
jQuery is an amazing JavaScript library, but one of its particularly impressive features is support for plugins. Now that you have seen how easy it is to write a plugin, try to think of ways you can provide value to the community by publishing one or more plugins for others.
Resources
There are many resources on JavaScript and jQuery, both in print and on-line.
From Packt Press, I enjoyed Learning jQuery by Jonathan Chaffer and Karl Sweebber, which is good for Web developers who have experience in another language already, perhaps even JavaScript. It reviews many of the different types of functionality that a JavaScript programmer can accomplish using jQuery.
David Flanagan's JavaScript: The Definitive Guide continues to be an excellent resource, although I will admit that having jQuery has cut down significantly on what I need to know in the underlying JavaScript.
There are similarly many blog postings that might be helpful, including: www.learningjquery.com/2007/10/a-plugin-development-pattern, tkramar.blogspot.com/2008/02/improve-your-jquery-fu-write-plugins.html and www.bennadel.com/blog/800-My-First-jQuery-Plugin.htm.
Reuven M. Lerner, a longtime Web/database developer and consultant, is a PhD candidate in learning sciences at Northwestern University, studying on-line learning communities. He recently returned (with his wife and three children) to their home in Modi'in, Israel, after four years in the Chicago area.