I've been developing jQuery plugins for quite a while now, and I've become rather comfortable with a particular style of plugin development for my scripts. This article is meant to share the pattern that I've found especially useful for plugin authoring. It assumes you already have an understanding of plugin development for jQuery; if you're a novice plugin author, please review the jQuery Authoring Guidelines first.
There are a few requirements that I feel this pattern handles nicely:
- Claim only a single name in the jQuery namespace
- Accept an options argument to control plugin behavior
- Provide public access to default plugin settings
- Provide public access to secondary functions (as applicable)
- Keep private functions private
- Support the Metadata Plugin
I'll cover these requirements one by one, and as we work through them we'll build a simple plugin which highlights text.
Claim only a single name in the jQuery namespace
This implies a single-plugin script. If your script contains multiple plugins, or complementary plugins (like $.fn.doSomething() and $.fn.undoSomething()) then you'll claim multiple names are required. But in general when authoring a plugin, strive to use only a single name to hold all of its implementation details.
In our example plugin we will claim the name "hilight".
JavaScript:
-
// plugin definition
-
$.fn.hilight = function() {
-
// Our plugin implementation code goes here.
-
};
And our plugin can be invoked like this:
JavaScript:
-
$('#myDiv').hilight();
But what if we need to break up our implementation into more than one function? There are many reasons to do so: the design may require it; it may result in a simpler or more readable implementation; and it may yield better OO semantics.
It's really quite trivial to break up the implementation into multiple functions without adding noise to the namespace. We do this by recognizing, and taking advantage of, the fact that functions are first-class objects in JavaScript. Like any other object, functions can be assigned properties. Since we have already claimed the "hilight" name in the jQuery prototype object, any other properties or functions that we need to expose can be declared as properties on our "hilight" function. More on this later.
Accept an options argument to control plugin behavior
Let's add support to our hilight plugin for specifying the foreground and background colors to use. We should allow options like these to be passed as an options object to the plugin function. For example:
JavaScript:
-
// plugin definition
-
$.fn.hilight = function(options) {
-
var defaults = {
-
foreground: 'red',
-
background: 'yellow'
-
};
-
// Extend our default options with those provided.
-
var opts = $.extend(defaults, options);
-
// Our plugin implementation code goes here.
-
};
Now our plugin can be invoked like this:
JavaScript:
-
$('#myDiv').hilight({
-
foreground: 'blue'
-
});
Provide public access to default plugin settings
An improvement we can, and should, make to the code above is to expose the default plugin settings. This is important because it makes it very easy for plugin users to override/customize the plugin with minimal code. And this is where we begin to take advantage of the function object.
JavaScript:
-
// plugin definition
-
$.fn.hilight = function(options) {
-
// Extend our default options with those provided.
-
// Note that the first arg to extend is an empty object -
-
// this is to keep from overriding our "defaults" object.
-
var opts = $.extend({}, $.fn.hilight.defaults, options);
-
// Our plugin implementation code goes here.
-
};
-
// plugin defaults - added as a property on our plugin function
-
$.fn.hilight.defaults = {
-
foreground: 'red',
-
background: 'yellow'
-
};
Now users can include a line like this in their scripts:
JavaScript:
-
// this need only be called once and does not
-
// have to be called from within a 'ready' block
-
$.fn.hilight.defaults.foreground = 'blue';
And now we can call the plugin method like this and it will use a blue foreground color:
JavaScript:
-
$('#myDiv').hilight();
As you can see, we've allowed the user to write a single line of code to alter the default foreground color of the plugin. And users can still selectively override this new default value when they want:
JavaScript:
-
// override plugin default foreground color
-
$.fn.hilight.defaults.foreground = 'blue';
-
...
-
// invoke plugin using new defaults
-
$('.hilightDiv').hilight();
-
...
-
// override default by passing options to plugin method
-
$('#green').hilight({
-
foreground: 'green'
-
});
Provide public access to secondary functions as applicable
This item goes hand-in-hand with the previous item and is an interesting way to extend your plugin (and to let others extend your plugin). For example, the implementation of our plugin may define a function called "format" which formats the hilight text. Our plugin may now look like this, with the default implementation of the format method defined below the hilight function.
JavaScript:
-
// plugin definition
-
$.fn.hilight = function(options) {
-
// iterate and reformat each matched element
-
return this.each(function() {
-
var $this = $(this);
-
...
-
var markup = $this.html();
-
// call our format function
-
markup = $.fn.hilight.format(markup);
-
$this.html(markup);
-
});
-
};
-
// define our format function
-
$.fn.hilight.format = function(txt) {'
-
return '<strong>' + txt + '</strong>';
-
};
We could have just as easily supported another property on the options object that allowed a callback function to be provided to override the default formatting. That's another excellent way to support customization of your plugin. The technique shown here takes this a step further by actually exposing the format function so that it can be redefined. With this technique it would be possible for others to ship their own custom overrides of your plugin נin other words, it means others can write plugins for your plugin.
Considering the trivial example plugin we're building in this article, you may be wondering when this would ever be useful. One real-world example is the Cycle Plugin. The Cycle Plugin is a slideshow plugin which supports a number of built-in transition effects נscroll, slide, fade, etc. But realistically, there is no way to define every single type of effect that one might wish to apply to a slide transition. And that's where this type of extensibility is useful. The Cycle Plugin exposes a "transitions" object to which users can add their own custom transition definitions. It's defined in the plugin like this:
JavaScript:
-
$.fn.cycle.transitions = {
-
...
-
};
This technique makes it possible for others to define and ship transition definitions that plug-in to the Cycle Plugin.
Keep private functions private
The technique of exposing part of your plugin to be overridden can be very powerful. But you need to think carefully about what parts of your implementation to expose. Once it's exposed, you need to keep in mind that any changes to the calling arguments or semantics may break backward compatibility. As a general rule, if you're not sure whether to expose a particular function, then you probably shouldn't.
So how then do we define more functions without cluttering the namespace and without exposing the implementation? This is a job for closures. To demonstrate, we'll add another function to our plugin called "debug". The debug function will log the number of selected elements to the Firebug console. To create a closure, we wrap the entire plugin definition in a function (as detailed in the jQuery Authoring Guidelines).
JavaScript:
-
// create closure
-
(function($) {
-
// plugin definition
-
$.fn.hilight = function(options) {
-
debug(this);
-
...
-
};
-
// private function for debugging
-
function debug($obj) {
-
if (window.console && window.console.log)
-
window.console.log('hilight selection count: ' + $obj.size());
-
};
-
...
-
// end of closure
-
})(jQuery);
Our "debug" method cannot be accessed from outside of the closure and thus is private to our implementation.
Support the Metadata Plugin
Depending on the type of plugin you're writing, adding support for the Metadata Plugin can make it even more powerful. Personally, I love the Metadata Plugin because it lets you use unobtrusive markup to override plugin options (which is particularly useful when creating demos and examples). And supporting it is very simple!
Update: This bit was optimized per suggestion in the comments.
JavaScript:
-
// plugin definition
-
$.fn.hilight = function(options) {
-
...
-
// build main options before element iteration
-
var opts = $.extend({}, $.fn.hilight.defaults, options);
-
return this.each(function() {
-
var $this = $(this);
-
// build element specific options
-
var o = $.meta ? $.extend({}, opts, $this.data()) : opts;
-
...
This changed line does a couple of things:
- it tests to see if the Metadata Plugin is installed
- if it is installed, it extends our options object with the extracted metadata.
This line is added as the last argument to jQuery.extend
so it will override any other option settings. Now we can drive behavior from the markup if we choose:
<!-- markup -->
<div class="hilight { background: 'red', foreground: 'white' }">
Have a nice day!
</div>
<div class="hilight { foreground: 'orange' }">
Have a nice day!
</div>
<div class="hilight { background: 'green' }">
Have a nice day!
</div>
And now we can hilight each of these divs uniquely using a single line of script:
JavaScript:
-
$('.hilight').hilight();
Putting it All Together
Below is the completed code for our example:
JavaScript:
-
//
-
// create closure
-
//
-
(function($) {
-
//
-
// plugin definition
-
//
-
$.fn.hilight = function(options) {
-
debug(this);
-
// build main options before element iteration
-
var opts = $.extend({}, $.fn.hilight.defaults, options);
-
// iterate and reformat each matched element
-
return this.each(function() {
-
$this = $(this);
-
// build element specific options
-
var o = $.meta ? $.extend({}, opts, $this.data()) : opts;
-
// update element styles
-
$this.css({
-
backgroundColor: o.background,
-
color: o.foreground
-
});
-
var markup = $this.html();
-
// call our format function
-
markup = $.fn.hilight.format(markup);
-
$this.html(markup);
-
});
-
};
-
//
-
// private function for debugging
-
//
-
function debug($obj) {
-
if (window.console && window.console.log)
-
window.console.log('hilight selection count: ' + $obj.size());
-
};
-
//
-
// define and expose our format function
-
//
-
$.fn.hilight.format = function(txt) {
-
return '<strong>' + txt + '</strong>';
-
};
-
//
-
// plugin defaults
-
//
-
$.fn.hilight.defaults = {
-
foreground: 'red',
-
background: 'yellow'
-
};
-
//
-
// end of closure
-
//
-
})(jQuery);
This design pattern has enabled me to create powerful, consistently crafted plugins. I hope it helps you to do the same.
42 Responses to “A Plugin Development Pattern”
- Nmweb Says:
October 30th, 2007 at 3:38 pmGreat tutorial. Thanks, I was hoping to delve into jQuery plugins someday and this will be a great help. These were indeed the issues I would run into.
- Stan Says:
October 30th, 2007 at 3:39 pmGreat article! Thanks for posting it. And thank you for all the work you've contributed to the jQuery community.
Pax,
- Stan - Byron Says:
October 30th, 2007 at 7:56 pmNice pattern & article Mike, I especially like the last two pointers (private functions and support metadata) as they are something i felt i was lacking an understanding of. thanks for clearing that up :-).
- Joan Piedra Says:
October 30th, 2007 at 8:21 pmI love the way you implemented the metadata plugin, I've been using it for a while, but never thought about implementing it with extend!
Thanks for such a great guide.
- Guy Fraser Says:
October 30th, 2007 at 11:20 pmFirst of all, superb tutorial!
However, the following line of code seems to be getting run excessively:
var opts = $.extend({}, $.fn.hilight.defaults, options, $.meta ? $this.data() : {});
That's a lot of object iterating to do on an element-by-element basis IMHO, especially if the metadata plugin isn't installed.
What if you changed it slightly...
$.fn.hilight = function(options) { debug(this); // work out main settings (without metadata) - only do this work once per plugin call var main_opts = $.extend({}, $.fn.hilight.defaults, options); // iterate and reformat each matched element return this.each(function() { $this = $(this); // if metadata is present, extend main_opts, otherwise just use main_opts var opts = $.meta ? $.extend({}, main_opts, $this.data()) : main_opts; // update element styles ... etc
Feedback?
- roScripts - Webmaster resources and websites Says:
October 31st, 2007 at 6:15 amLearning jQuery » A Plugin Development Pattern...
Learning jQuery » A Plugin Development Pattern...
- Mike Alsup Says:
October 31st, 2007 at 6:29 amThat's a fine optimization, Guy. Thanks!
- James Dempster Says:
October 31st, 2007 at 7:58 amWow a fantastic tutorial on how to write jQuery plugins. That's exactly how every one should do it. Glad I've been doing it right all this time ;¬)
- Tane Piper Says:
October 31st, 2007 at 11:06 amI've been doing most of what you are suggesting already, but there are a few things (like making default options public and using the meta plugin) that I was missing, I'll be upgrading my plugins to include these
- Guy Fraser Says:
October 31st, 2007 at 11:08 amThe if statement in the debug function seems to have the && chimped...
- Ariel Flesler Says:
October 31st, 2007 at 11:52 amGreat article !!
- Guy Fraser Says:
October 31st, 2007 at 3:41 pmJust found another minor optimisation with respect to /packer/ - you can prefix variables to reduce the length of their names:
http://dean.edwards.name/packer/2/usage/#special-chars
This is probably overkill for the tutorial, however as lots of people will be looking at this page it's worth increasing knowledge of this feature of the packer.
So, some tweaks for extra packing goodness:
// // create closure // (function($) { // // plugin definition // $.fn.hilight = function($$options) { ;;; _debug(this); // build main options before element iteration var $settings = $.extend({}, $.fn.hilight.defaults, $$options); // iterate and reformat each matched element return this.each(function() { $this = $(this); // build element specific options var o = $.meta ? $.extend({}, $settings, $this.data()) : $settings; // update element styles $this.css({ backgroundColor: o.background, color: o.foreground }); var $markup = $this.html(); // call our format function $markup = $.fn.hilight.format($markup); $this.html($markup); }); }; // // private function for debugging // function _debug($obj) { if (window.console && window.console.log) window.console.log('hilight selection count: ' + $obj.size()); }; // // define and expose our format function // $.fn.hilight.format = function($txt) { return '' + $txt + ''; }; // // plugin defaults // $.fn.hilight.defaults = { foreground: 'red', background: 'yellow' }; // // end of closure // })(jQuery);
You do have to be super-careful with $ prefixes though. For example, $options and $opts are both packed to "o" (which was already in use lower down the code). To get round that I've used two $$ for the options parameter (packed to "op") and renamed opts to $settings (packed to "s").
I've renamed debug to _debug (packed to _0 - that's a zero, not an "o") and also put three semicolons (;;;) before the call to debug(this) - the packed version would omit that line completely.
It's a shame that Dean Edwards' packer isn't based on Rhino - that way he'd be able to work out automatically with very high accuracy which vars are local or private. I believe Jack Slocum's ExtJS packer is based on Rhino and does this, however I haven't tried it to see if it works.
- Scriptia Says:
November 2nd, 2007 at 8:31 amUn patrón de desarrollo de plugins para jQuery...
Mike Alsup, autor de jQuery form plugin y otras delicias, nos explica cómo crear un plugin para jQuery que cumpla con las condiciones de: no contaminar el espacio de nombres, acepte opciones (y las extienda), mantenga los límites adecuados entre lo p...
- Mario Garcia Says:
November 27th, 2007 at 5:20 pmHi Mike.
Great job !!!! ( Sorry for my english)
and, a couple of questions:
* how I can implement Statics Methods/Attributes ?
* how I can implement Inheritance?
for example:
Person is superclass for employee.
Thank's
Mario Garcia - dnk.nitro Says:
November 30th, 2007 at 7:50 pmAfter update of metadata plugin consider using
var o = $.metadata ? $.extend({}, opts, $this.metadata()) : opts;
code instead in line 18 - Ismael Says:
December 1st, 2007 at 6:00 pmIt DOES help a lot! Thank you for an excellent tutorial.
- Andy Croll Says:
December 18th, 2007 at 10:12 pmIs there a sensible way to set variables or run functions within the already running plugin?
- Omar Abdel-Wahab Says:
January 2nd, 2008 at 10:55 amHi Mike and thanks for the tutorial, yet I have a single comment: using $ instead of jQuery inside your plugin isn't recommended, IMO, since it will mean users trying to rename "$" to anything else will not be able to do that from a single location.
Read: http://docs.jquery.com/Plugins/Authoring#Custom_Alias - Karl Says:
January 2nd, 2008 at 11:53 amHi Omar,
If you look at Mike's last code example (in "Putting it all together"), you'll see that he does exactly what the author of your link recommends:(function($) { // plugin code })(jQuery);
- Alexandre Magno Says:
January 22nd, 2008 at 2:33 pmI saw a lot of plugins that uses object notation, how should behave this way?
$.extend({
somefunction: function(test) {
//somecode goes here
}
});
- Adam T. Says:
January 25th, 2008 at 3:48 pmWhy might I need to wrap my calling statement like the following:
$(function () { $('#myDiv').myMethod(); });
Ideally I'd like to get away from having a wrapper in my code and instead just call
$('#myDiv').myMethod();
directly. Unfortunately, it seems as though my code doesn't work unless I wrap it in this way.
- Josh Kline Says:
January 31st, 2008 at 1:46 amAdam T,
$(function () { ... });
is a shortcut for$(document).ready(function () { ... });
That is roughly equivalent to
<body onload="...">
. The purpose is to run your jQuery code after the entire page has loaded, included jQuery itself, and the dom is ready for manipulation.There are plenty of tutorials on the basics of jQuery, but this post is not one of them. You might want to check out docs.jquery.com or some other posts on this site.
- Adrien Says:
February 15th, 2008 at 2:14 amBravo!
This is a great tutorial, usefull for beginner to confirmed jQuery developpers...
Explanation are crystal clear... you rocks!
Just a explanation for those who start jQuery:
In
var o = $.metadata ? $.extend({}, opts, $this.metadata()) : opts;"$.extend({}, opts, $this.metadata())" returns a new object extending opts with $this.metadata() without modifying opts
as the first argument of $.extends is like passed by reference, using {} will return a brand new object.
using "$.extend(opts, $this.metadata())" will modify the general options witch is not wanted...
my two coins
- christian Says:
February 18th, 2008 at 7:59 pmGREAT TUTORIAL ... this really got me motivated to play around with jquery plugins.
One point i am curious about. What do you think of exposing the format-function not via $.fn.highlight.format but as another attribute of the options. So the format function can be changed globally in the defaults or locally for one jquery call, as the other primitive types in the options. - Frink Says:
March 5th, 2008 at 5:11 pmI propose a slight alteration to the namespace convention suggested above. It is already in widespread use among plugin developers. In creating long chains for complicated processes in jQuery the pater is:
$('selector').method().method().method() etc...
If we use the above recommendation it becomes:
$('selector').namespace.method().namespace.method().namespace.method() etc...
This is over-prototyping and bad coding practice. It would be better to use a namespace-prefix instead. consider:
$('selector').namespaceMethod().namespaceMethod().namespaceMethod() etc...
In the case of re-render function where manipulates or changes the HTML two functions should be provided. You should provide a function to reverse any destructive operation you do. For re-rendering use (/un)namespaceMethod(). For manipulation use (add/remove)Namspace().
$('selector').namespaceMethod();
$('selector').unnamespaceMethod();
$('selector').addNamespaceMethod();
$('selector').removeNamespaceMethod()This naming convention is used all over jQuery already. These re-render and manipulation functions should always return a jQuery object so that chaning may continue. If you need to use a return code consider setting a context persistent variable in the DOM.
You should provide a global object rather than extending the prototype object. Consider:
$.namespace
instead of:
$.fn.namespace
This allows a more accurate representation of data. The global default settings are stored as part of the global default object. The manipulation and render functions are part of the prototyped jQuery select. Also the defaults should not be prototyped. It is better to render the options as a child of the global namespace.
Finally, initialization was not discussed in this article. Often plugins may wish to initialize on document ready. This should be put after the prototype section of the plugin.
Altogether a pulgin template should look like this:
(function($) {
// Private Variables and Functions
var privateVariable = {};
function privateFunction() {
};
// Public Variables and Methods
$.namespace = {
options: {},
publicVariable: [];
publicMethod: function() {}
};
// Prototype Methods
$.fn.extend({
usernameMethod: function() {
return this.each(function() {
// Persistent Context Variables
this.contextVariable = 'foo';
}
}.
unusernameMethod: function() {
return this.each(function() {
delete this.contextVariable;
}
}
});
//Initialization Code
$(function() {
});
})(jQuery);Prototype variables should variables should be avoided. If a context specific variable is required you should use the each() construct and set the variable to this.namespaceVariable. In this way you are using the native DOM construct for variable settings.
Finally, it is generally considered good etiquette to put a usage information in a comment aat the head of your script file along with author attribute, copyright, version and source repository information. That way developers can easily find where to submit bug reports and change ideas.
- Frink Says:
March 5th, 2008 at 8:49 pmThere are errors in my code above. I was typing fast and my connection froze when I tried to go back and edit it. The skeleton template should have been rendered:
(function($) {// Private Variables and Functions
var privateVariable = {};
function privateFunction() {
// Do Something Privately
};// Public Variables and Methods
$.namespace = {
options: {},
publicVariable: [],
publicMethod: function() {
// Do Something Publicly
}
};// Prototype Methods
$.fn.extend({
namespaceMethod: function() {
return this.each(function() {
// Example Setting Persistent DOM Variables
this.contextVariable = 'foo';
});
},
unnamespaceMethod: function() {
return this.each(function() {
// Example Removing Persistent DOM Variables
delete this.contextVariable;
});
}
});//Initialization Code
$(function() {
$.namespace.publicMethod();
});})(jQuery);
One final note. Although this isn't standard practice, you can greatly clarify your code by using a private object as opposed to using free-floating private functions and variable. The the private section would therefore look like this:
// Private Variables and Functions
var private = {
privateVariable: {},
function privateFunction() {
// Do Something Privately
}
};This way when you access private variables and methods it will be apparent in your code:
private.privateVariable = 'foo';
private.privateMethod('bar'); - Frink Says:
March 5th, 2008 at 9:22 pmOne more oops. Low blood sugar. Wasn't thinking. You should not use "private" as a variable name. instead you can use the namespace you choose.
// Private Variables and Methods
var namespace = {
};
// Public Variables and Methods
$.namespace {
}Sorry for the confusion. Argh! I hate not thinking straight.
- malsup Says:
March 6th, 2008 at 8:05 amFrink,
I'm most definitely not suggesting anyone using namespacing to do this:
$('selector').namespace.method().namespace.method().namespace.method()
As was mentioned, this article focuses on a single-plugin script and the suggestion is to use a single namespace for a single plugin. If your script has multiple plugins then you certainly need multiple
$.fn
extension points (for example, $.fn.doSomething() and $.fn.undoSomething()).I believe that using the plugin function for default settings and for exposing public functions is a better implementation than extending the jQuery object itself. I suppose it's a matter of taste, but I prefer to have the entire plugin implementation packaged in the same namespace.
- Kean Says:
March 12th, 2008 at 7:17 pmThanks for the post. I just developed a plugin and I will implement the patterns.
- Alexandre Plennevaux Says:
March 18th, 2008 at 7:01 amHi Mike,
thanks for this most excellent and referential tutorial!Could you update it to explain how to implement callbacks? Some concise background info about callbacks would be great as well.
Thank you so so much for all you do for the jquery community!
Alexandre
- Alexandre Magno Says:
March 28th, 2008 at 10:24 amIm working in a big project of ajax and I would like to use a plugin
pattern thats not working in pratice...All javascript in the project I would like to transform in a jQuery
plugin. But to achieve this I need to separate in categories in
libraries for this site Im workingSo, I wanna to make this:
$("seletor").project.ajax.remote();
$("seletor").helpers.options();
But when Im creating a plugin like this:
$.fn.project.ajax.remote = function() {
//code}
Its not working...
Using extend I reach it, but extending methods of jQuery objects I
cant do that. I need this because I create a ajax library just for the
site and a lot of thing, so we need this pattern...How I can accomplish this????
- Edilson de Souza Says:
March 30th, 2008 at 11:31 pmHails! Mike, I've been writing a plugin and I wanna know about the flexibility of writing in a OO manner, like so:
(function($){
$.myClassName = {calcSomething: function(arg){
//do something
},main: function(){
return
this
.each(function(){
//do something more
$.myClassName.calcSomething(param){}
}
}
$.fn.myMethod = $.myClassName.main;
})(jQuery);I'd like to know your opinion about that OO pattern in jQuery plugins.
Thanks for your article! - michael Says:
April 16th, 2008 at 1:44 pmI looked at different plugins code and this is definitely the best plugin pattern I found. Many thanks for that great tutorial!!
- Brian Says:
May 2nd, 2008 at 9:17 amHi! That was a great article, but I still have a question:
If you extend the options this way and the user doesn't set all the new defaults then some options may be missing that can easily cause problem (as they will be undefined). If each properties are set it doesn't matter but only if a new object is given as defaults, which is more convenient in most cases.
So, wouldn't be better:
//defaults inside the plugin:
var defaults = {
foreground: 'red',
background: 'yellow'
};$.extend( defaults, $.fn.pluginName.defaults, options )
- Website Design Says:
June 2nd, 2008 at 2:55 pmI almost always enjoy what I read on this site.
- Junaidix Says:
July 25th, 2008 at 4:27 amWow.. this a great article.. I'm really novice in Jquery world. Just to leant to write my first plugin Thanks for the tutorial..
- ze Says:
August 7th, 2008 at 4:03 ami love this tutorial, very thanks it's helpfull!
- Andre Says:
August 26th, 2008 at 12:18 pmI had to var the $this on line 16 of the putting it all together list to get it to work right when setting up a binding on an element.
Also, I seem to not have access to opts in from within the getformat function on line 42. Is there a trick to accessing it?
$.fn.hilight.format = function(txt) {
alert(opts) //fails
return '' + txt + '';
}; - mark meves Says:
August 31st, 2008 at 1:45 amOne thing I don't get here, near all of the examples with a line like
return this.each(function() { var $this = $(this); ...
After that last line, $this is now an array, isn't it? In the examples, it isn't used as an array. What am I missing?
- mark meves Says:
August 31st, 2008 at 5:59 amOk disregard my above comment -- i shouldn't have spoken too soon. I have since learned that this is lesson 101 of jquery -- we frequently deal with lists of elements
- Gaurav Says:
September 25th, 2008 at 5:47 amExcellent tutorial !!! Very helpful i created my first plug-in today.
Will post it very soon on my blog.G
- Neva Says:
September 28th, 2008 at 8:01 pmjQuery very good!