How to Create Your Own jQuery Plugin

JavaScript jQuery basics How to Create Your Own jQuery Plugin

jQuery is one of the most popular JavaScript frameworks ever. At first, it was just a library to provide CSS selectors for JavaScript but after Dean Edwards released his excellent cssQuery library, jQuery's development shifted in a different direction. The author of the jQuery library is John Resig.

But enough with jQuery theories, let's go programming now. As the title of this article suggests, we're going to write our own jQuery plug-in. You have surely already stumbled across these plug-ins. We call them as any other function in this library, for example like this: $(element).function().

There are plenty of plug-ins on the Internet, maybe that's why the framework is so successful. However, it may happen that you won't be happy with any of the available plug-ins, perhaps because of its license. The license doesn't have to be open source by all means, so you can see paid plug-ins as well. You can also develop your own paid plug-ins of course. This article is an introduction to creating custom jQuery add-ons. The article is based on the development of a real plug-in - a simple content slider.

HTML & CSS structure

When writing our own plug-in, we need to come up with an HTML structure and style it. We want to make a content slider, so the best for us would be to use a list, i.e. the <ul> element. We'll style it using classes which will assign the plug-in by itself. Here is a small example of the HTML structure with basic CSS styles:

The HTML structure

<ul>
        <li>The content of the first slide</li>
        <li>The content of the second slide</li>
</ul>

CSS styles

/**
 * All slides
 */
ul.easySlider-content {
padding: 20px;
        margin-bottom: 25px;
        position: relative;
        height: 250px;
        z-index: 100;
        -webkit-box-shadow: 0 0 10px #aaa;
        -moz-box-shadow: 0 0 10px #aaa;
        box-shadow: 0 0 10px #aaa;
        border: 1px solid #dcdcdc;
        background: #ffffff;
        background: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiA/Pgo8c3ZnIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgdmlld0JveD0iMCAwIDEgMSIgcHJlc2VydmVBc3BlY3RSYXRpbz0ibm9uZSI+CiAgPGxpbmVhckdyYWRpZW50IGlkPSJncmFkLXVjZ2ctZ2VuZXJhdGVkIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeDE9IjAlIiB5MT0iMCUiIHgyPSIwJSIgeTI9IjEwMCUiPgogICAgPHN0b3Agb2Zmc2V0PSIxJSIgc3RvcC1jb2xvcj0iI2ZmZmZmZiIgc3RvcC1vcGFjaXR5PSIxIi8+CiAgICA8c3RvcCBvZmZzZXQ9IjEwMCUiIHN0b3AtY29sb3I9IiNlZWVlZWUiIHN0b3Atb3BhY2l0eT0iMSIvPgogIDwvbGluZWFyR3JhZGllbnQ+CiAgPHJlY3QgeD0iMCIgeT0iMCIgd2lkdGg9IjEiIGhlaWdodD0iMSIgZmlsbD0idXJsKCNncmFkLXVjZ2ctZ2VuZXJhdGVkKSIgLz4KPC9zdmc+);
        background: -moz-linear-gradient(top,  #ffffff 1%, #eeeeee 100%);
        background: -webkit-gradient(linear, left top, left bottom, color-stop(1%,#ffffff), color-stop(100%,#eeeeee));
        background: -webkit-linear-gradient(top,  #ffffff 1%,#eeeeee 100%);
        background: -o-linear-gradient(top,  #ffffff 1%,#eeeeee 100%);
        background: -ms-linear-gradient(top,  #ffffff 1%,#eeeeee 100%);
        background: linear-gradient(top,  #ffffff 1%,#eeeeee 100%);
        filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#ffffff', endColorstr='#eeeeee',GradientType=0 );
}

ul.easySlider-content li {
        margin-right: 20px;
}

/**
 * Active slide
 */
ul li.active {
        display: block !important;
}

/**
 * Navigation arrows
 */
a.arrowLeft {
        display: block;
        width: 70px;
        height: 70px;
        background: #fff url('images/arrow_left.png') 3px center no-repeat;
        position: absolute;
        margin: 140px -48px;
        border-radius: 50px;
        border: 5px solid #dcdcdc;
}

a.arrowRight {
        display: block;
        width: 70px;
        height: 70px;
        background: #fff url('images/arrow_right.png') 18px center no-repeat;
        position: absolute;
        margin: 140px 770px;
        border: 5px solid #dcdcdc;
        border-radius: 50px;
}

a.arrowLeft:hover, a.arrowRight:hover {
        background-color: #dcdcdc;
}

jQuery.fn

jQuery.fn is, you could say, a namespace for your plug-ins. All functions placed in this object can be called directly from jQuery. The context of these functions is the currently selected element. But what is the functions' context? Each function in JavaScript is processed in some context. Simply put, it's the this keyword. Therefore, if we put a function into jQuery.fn and call our plug-in, for example like this: $('#content').myFunctionName(), the this keyword will contain the element with id="content". However, it'll be accessible as an HTMLElement object, not as a jQuery object. If we'd like to use it as a jQuery object, we'd simply pass this as the jQuery object's parameter: jQuery(this) or $(this) (both notations do the same).

Now that we know where to put our plug-in, we can start coding. In general, it's best to wrap our plug-in in an anonymous function to prevent variables from being overwritten and so. It's also better because the $ character is used by other libraries (not only by jQuery) as a wildcard, and we don't need to care about this in anonymous functions. We should also consider how to call additional functions within the plug-in and how to handle user settings. I will now give a simple example of a basic plug-in with a function calling logi. We're gonna explain everything in a moment:

// Our anonymous function - the only parameter is $ which represents the jQuery object
(function( $ ) {

        // The methods that can also be called by the user will be placed here
        var methods = {

                // Initialization method - is called from the main function
                init: function( o ) {}
        };

        // Input function of the plug-in
        $.fn.easySlider = function( method ) {
                // If we have the name of the function we want to call
                if ( methods[method] ) {
                        // We call the given function with all the other plug-in arguments passed
                        return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
                } else if ( typeof method == 'object' || !method ) {
                        // If not, we call the initialization method
                        return methods.init.apply(this, arguments);
                } else {
                        // If the method doesn't exist, we print an error
                        $.error('Metoda ' + method + ' neexistuje v plug-inu jQuery.easySlider');
                }
        };

        // Default settings
        $.fn.easySlider.defaults = {
        };
})( jQuery ); // we call the functions with the jQuery parameter

We created an easySlider() function in the jQuery.fn object. Therefore, we can call it like e.g.: $('ul#slider').easySlider().

But what if we'll need to set something up or to call an internal function of the plug-in? It's very impractical to change the plug-in's code itself. Such codes are also often minimized so it might not be even possible. However, the example above takes this into account as well. As an argument, we can specify either an object with all the plug-in settings, or a name of one of the methods object functions, or nothing and the plugin calls methods.init() by itself. This is just one way to make the function calling logic. It could have been done differently too. For us, however, this is very practical, so we don't have to deal with it anymore.

Lastly, we have the defaults object. The default settings are stored in it. This is for the user so they won't have to set everything, just what they want differently.

Before we start with the plug-in itself, I'd like to clarify what the apply() function does. Every function in JavaScript has some context in which it's being processed, as I've already mentioned above. The apply() function calls a given function within a context we choose. It takes two arguments, the first is the context. We set it to this, which is the currently selected element. The second argument is an array through which we pass all the function's arguments. We use the arguments variable, which is an array of all the function arguments.

Initialization method

We have the basis so let's initialize each element. The only argument will be an object carrying the user settings. However, to ensure that all the settings are in this object, we'll extend defaults by this object. Again, I will introduce the entire code of the function and then describe it:

// Initialization method - is called from the main function
init: function( o ) {
        // Gets the plug-in settings
        o = $.extend({}, $.fn.easySlider.defaults, o);
        // Saves the current context of the function (which is the plug-in element)
        var $that = $(this);

        // Functions called once the arrows are clicked
        var left_click = function( e ) {
                e = $.event.fix(e);
                e.preventDefault();
                $that.easySlider("prev");
        }, right_click = function( e ) {
                e = $.event.fix(e);
                e.preventDefault();
                $that.easySlider("next");
        };

        // We go through all the elements passed and initialize the plug-in for them
        return this.each(function() {
                // Finds all images in the slider
                var $items = $(this).find('li'),
                        count = $items.length,
                        $self = $(this);

                // Hides them all and positions them absolutely
                $items.css({ display: 'none', position: 'absolute' });

                // Assigns a class to the list to make it easier to manipulate with from CSS
                $self.addClass('easySlider-content').data('speed', o.speed);

                // Makes the first element active
                $($items.get(o.active)).addClass("active");

                // Creates the arrows on the sides for the slider navigation
                var $arrowLeft = $('&lt;a /&gt;').attr('href', '#left').addClass('arrowLeft');
                var $arrowRight = $('&lt;a /&gt;').attr('href', '#right').addClass('arrowRight');
                // Sets the onclick callback
                $arrowLeft.bind('click', left_click);
                $arrowRight.bind('click', right_click);
                // Inserts the arrows before the slide list
                $self.before( $arrowRight );
                $self.before( $arrowLeft );
        });
}

Right on the first line there's the jQuery.extend() function used. This function extends any number of objects where the lastly provided objects have a higher priority. The first argument is the target object, where we want to store the result (for more information see the official documentation of jQuery.extend). And now in English. We take the variables and functions from the defaults object and if there is a function of the same name or a variable in the o object, we overwrite its value with the value from the o object. This ensures that we don't have to determine whether the user has specified all the options, that would be too complicated. Then there's a $that auxiliary variable. Except it makes the code more readable and probably saves us fractions of a second to write it, there are also other functions defined in methods.init() with a different context. And in these functions, $(this) would mean something totally different. Therefore, we need to store it in a variable that is also accessible from these functions.

The whole function returns this, the selected elements again, and performs the plug-in settings for each element. So let's discuss the code in detail. Since we assume that the slider's structure will be a list (<ul> and <li> in HTML), we find all the <li> elements and store them in the $items variable. Then there's an auxiliary variable holding the number of the <li> elements (i.e. the slide count) and again, the $self auxiliary variable, in which is the context of the function we're working within. That's because the context has changed by using jQuery.each as you might have noticed.

We've already loaded it all into variables, so we set them up. First of all, we hide all the contents and set the absolute position to it. This is to keep the contents together during the transition. Additionally, we add the .easySlider-content class to the <ul> element and set data-speed to it to the value specified by the user. We do that so we can read it further in another function, but we'll get there later.

Now we create buttons to switch to the next or previous slide. We assign the class to them and place them before the slider. I've intentionally skipped bind('click'). We have to tell the script what to do if the arrows are clicked. We use the left_click() and right_click() functions we defined earlier. We defined these function before return so we don't have to define them a hundred times, which could slow the whole script down. In this function you should define only the most necessary code which must really be done for each element. What I would like to emphasize in these functions is using $.event.fix(e). As you may know, in some browsers you have to access event variables differently or the parameters are named differently, so we have to be careful. However, only few people know that this jQuery function deals with this for us so we don't have to worry about it anymore. Then we just use e.preventDefault() to prevent the default event from occurring (links would normally redirect to their href addresses). At the end of the function, we call methods.next() through our calling logic. A synonym for this call is methods.next.apply(that);, where that is the context of the init() function.

Public and private functions

As already mentioned, all the functions in the methods object are callable from the outside of our plug-in as $(element).easySlider("function_name", ["first parameter", "second parameter", ...]); (the brackets indicate only that these arguments are optional - we don't write them in our script!). Therefore, the functions are public. We can also create private functions by storing them outside the methods object, in the same anonymous function. Such functions would be accessible only within our anonymous function and would not be accessible from the outside. In our case, we don't use private methods, but these can be very useful sometimes.

Now there are only three functions left. One sets the active layer and simultaneously performs the animation (sliding) based on the index passed. Let's call it active(). The next two are prev() and next() which calculate the index of the previous, resp. the next slide and pass it to the active() function.

// This function sets the active slide (The index is zero-based and corresponds with the number of gallery images)
active: function( index, direction ) {
        // Sets the speed
        speed = $(this).data('speed') || 800;
        // Sets the effect directions
        directionHide = direction || 'left';
        directionShow = directionHide == 'left' ? 'right' : 'left';
        // Hides the active item
        $(this).find('li.active').hide('slide', { direction: directionHide }, speed);
        // Removes the .active class from all the items
        $(this).find('li').removeClass('active');
        // Loads the active slide
        var slide = $(this).find('li').get(index) || false;
        // Displays it
        $(slide).addClass('active').show('slide', { direction: directionShow }, speed);
        // Returns the active element
        return $(this).find('li').get(index) || false;
},

// Moves to the next slide
next: function() {
        // Finds the next element and its index to which we add +1
        var index = ($(this).find('li.active').index()+1);
        // Activates the slide if exists. If not, automatically moves to the first one (with index 0)
        return $(this).easySlider("active", ($(this).find('li').get(index) ? index : 0));
},

// Moves to the previous slide
prev: function() {
        var index = $(this).find('li.active').index()-1 < 0 ?$(this).find('li').length-1 : $(this).find('li.active').index()-1;
        // Activates the slide with this index
        return $(this).easySlider("active", index, 'right');
},
/** The init method... */

$(element).ea­sySlider(acti­ve, index, direction)

All we have to do is to describe these three methods and our plug-in is done. The first active() function activates the element we find by the passed index (see the first parameter of the function). The second parameter, direction, is optional (it's either 'left' or 'right'), defining the direction in which the slider moves. The function to display and hide the element uses jquery.effects.core.js from the jQuery UI library. Therefore, this plug-in depends on it. If you want to use the pure jQuery only, you can replace them with fadeIn() and fadeOut() or by slideDown() and slideUp() or simply by animate().

$(element).ea­sySlider(next)

Moves the slider to the next element. If there isn't one, it moves it to the beginning. Calls the active() function. The prev() function is the same except that it moves to the previous or the last element.

Final tips

  • Minimize your code, but offer normal versions as well (if you don't want to create commercial plug-ins). This will allow your plug-ins to be extended and modified by others unless it conflicts with your license. To minimize the code, I recommend the Dean Edwards' script - Packer
  • Put as many things as possible into the custom settings. Then write detailed documentation for these settings.
  • Try not to set CSS styles much. Leave it on external files. Keep only the necessary styles in the script.
  • Write unobtrusive code. You don't want the users to have to add HTML, JavaScript can do that too ;)
  • Before releasing, test your work in as many browsers as possible. If some browser doesn't support it, include this information in your documentation.

 

 

Article has been written for you by Drahomir Hanak
Avatar
Do you like this article?
No one has rated this quite yet, be the first one!
Thumbnail
Previous article
Styling in jQuery
Thumbnail
All articles in this section
jQuery basics
Activities (3)

 

 

Comments

To maintain the quality of discussion, we only allow registered members to comment. Sign in. If you're new, Sign up, it's free.

No one has commented yet - be the first!