Saturday, August 28, 2010

A first look at creating custom jQuery plugins

In my previous posts, we saw several ways to manipulate functions and how functions and objects can be used almost interchangeably in javascript. In this post, I am going to use a popular javascript framework jQuery to create a custom plugin. If you are new to jQuery or any other javascript frameworks, then i suppose this post is definitely not for u.

However, for those who have been fortunate enough to have had the time and patience to work with jQuery, this post should be like a piece of cake.

So, what are jQuery plugins?
In short, jQuery plugins are functions that can operate on a wrapped set of jQuery objects.

For example, consider the usage of the click() function. This function is used to attach a click event handler to a wrapped set of jQuery objects, The objects in turn are obtained using jQuery selectors.

In this post, I am going to try and create a small plugin that contains some custom css and a few event handlers. The code is trivial, and one does not require to create plugins for such requirements, but for a  demonstration purpose, this should suffice.

So, here's the idea
I want to create a div that i am going to call the "main div" that has three child div's. The first child is a header div, the second child is a content div and the third child is a footer div. When the user clicks anywhere on the main div, the string "You clicked + div id" should be appended to the html that is part of the the content div. To add a bit more spice, i also want the content in the Header and Footer to be white in color.

This plugin allows to apply this effect to any html element that has the same structure as the above div's, i.e. one parent container with three children.

Lets see the html first.

Heading
div 1 content - custom enclosing plugin
footer1

Lets first clearly identify that major tasks that demand our attention.

  1. Color the text content of the header and footer div's
  2. Add a click event handler to the "main div", that communicates with the "content div" which happens to be my second child element in the structure shown above.


Lets name our plugin encloseme().

But first we need to get hold of our main div. Right? So, in this experiment, I am going to use a jQuery selector to select a div that contains the string "main". And then I am going to apply my plugin to it like this :

$("div[id*=main]").encloseme();

The above line would select all the div's whose id's contain the text "main", and apply the plugin encloseme() to it.

Now, before I begin writing the code for the plugin, I am going to use a feature of javascript that that i wrote about in my previous posts - the way of calling a function anonymously.

In order to use the "$" symbol in a non conflicting way, I am going to pass the jQuery object as a parameter to the anonymous function that will in turn create my plugin.

Here is the snippet.

(function($){
 //plugin code goes inside here.
        //Here, the "$" symbol can be used without bothering about any sort of 
        //conflicts with external code(i.e. code outside the body of this function).
})(jQuery);

Creating a plugin requires you to create a function on the jQuery.fn object.
The jQuery.fn object is equivalent to the prototype object in javascript that is used to add more functions.

Lets see the code to create our plugin.
$.fn.encloseme=function(){
return $(this).each(function(index,element){
  
  //plugin code for each selected element in the matched set goes here.
  
 });
};

There are two things that you need to notice in the above snippet.
The anonymous function is terminated by a semicolon, unlike other functions that you create in jQuery.
The function returns the result of iterating each element in the matched set. i.e. the function returns a jQuery object that represents a matched set.

Although we can choose to return anything, but the basic idea here is that your plugin should blend in seamlessly with other jQuery functions, and for that to happen, you must ensure that your plugin allows method chaining. It is considered a good practice to return a matched set from your plugins if you don't want to surprise your web authors. If you want to surprise them, well, then you better buy yourself an Iron Man suit, because sooner or later, you are certainly going to need the protection it offers!


Adding colors to the child div's is a pretty trivial task. Ive also added a bit more css to add border to the main elements. Lets see the code for it.

$.fn.encloseme=function(){
  return $(this).each(function(index,element){
   
   $(this).css({
      "background-color":"grey",
      "border-style":"solid",
      "border-width":"2px",
      "width":"200px"});
  });
 };

In the above code, i am selecting the container tag represented by $(this) and then adding a background color it and a few more styles.

Now lets handle the children!(OMG)
Don't worry, handling children in jQuery is not as tough as it is in Real life. The first thing that you need to do is to find a way to get the children of your container. jQuery allows you to do so by callinng the children() function on the container. This function returns a jQuery object that represents the immediate children of the selected container.

the following code can be used to color the text of the children to white.

$(this).children().css({"color":"white"});


Before delving further in this example, I must remind you of the fact that my example is assuming that my container contains 3 child elements and that i am trying to achieve the effect of appending some random text to the text of my second child element whenever the user clicks anywhere on the parent container. That should explain the use of absolute indexing in lines of code that follow.

In the next snippet, i am going to override the css property : color, of the second child element and restore it to black.

$($(this).children()[1]).css({"color":"black"});


jQuery indexed are 0 based. So, the code $(this).children()[1] selects the second DOM element. Since I need to call a jQuery function on the selected DOM, enclose it inside another jQuery selector - $($(this).children()[1])


Now, the only task that remains is to add a click event handler to the main container and associate it with the html text of my second child element. The following code will do my job.


$(this).click(function(){
 $($(this).children()[1]).html($($(this).children()[1]).html() + " You clicked "+ $(this).attr("id"));
});


Following is the final code for the plugin :

(function($){
 $.fn.encloseme=function(){
  return $(this).each(function(index,element){
   
   $(this).css({
      "background-color":"grey",
      "border-style":"solid",
      "border-width":"2px",
      "width":"200px"});
      
   $(this).click(function(){
    $($(this).children()[1]).html($($(this).children()[1]).html() + " You clicked "+ $(this).attr("id"));
   });
   
   $(this).children().css({"color":"white"});
   $($(this).children()[1]).css({"color":"black"});
  });
 };
})(jQuery);



Applying my plugin to my container is pretty easy. Here's the code for it.

$("div[id*=main]").encloseme();


But I already told you that before, didn't I? Well, since its a plugin, it can practically be applied to any jQuery element. In our case, since we are manipulating the second child through absolute indexing, the plugin can be applied to any container which has at least 2 children.


For example, we can create a unordered list with three child elements and then apply the plugin to the unordered list.

Here's the html for it


  • item1
  • item2
  • item3


And here is the jQuery that can be used to apply my plugin

$("ul").encloseme();


That does it!

Though I must say that the above example is not highly usable for practical purposes(At least i personally haven't been able to apply this particular plugin anywhere), however it demonstrates a few things that plugins can be used for :

Grouping together functionality for several elements. Here we have grouped together the click and css aspects of a set of elements.

Adding new custom behavior to jQuery, although one must be careful while creating too many plugins as they may clutter the namespace.

We can also customize plugins by sending them arguments or options. But thats another long story, and ill spare you the details involved...at least for now!

Cheers!
Happy Programming :)
Signing Off
Ryan