Posted 11.27.2009

Posted 11.27.2009

Writing jQuery Plugins (Part 2 – A More in Depth Discussion)

Tuesday, we looked at the BASICS of writing a jQuery plugin.

A Quick Note:

I’m still going to assume that you already have a basic understanding of jQuery. If not, head over to jQuery’s site. There’s some wonderful documentation there to help you get started. When I first decided to learn this library, I was pleasantly pleased with how easy it was.—particularly after having attempted JavaScript (multiple times) and having to write code for multiple browsers… I digress.

A quick review:

Every plugin will contain 3 things:

Your file name will be jquery.your_plugin_name.js.

Comments at the top of your plugin containing information about plugin

/* ==================================================================
*
*    ROLLOVERS PLUGIN
* 
*    DATE:                 AUGUST 22, 2009
*    AUTHOR:             AMY HAYWOOD
*     URL:                 WWW.AMYHAYWOOD.COM
*
*    REQUIREMENTS:         jQuery
*                        Be sure to include the jQuery library before including this script
*     IMPLEMENTATION:
*        Within the $(document).ready(); all that you really need to include is:
*        |    $('.CLASS_NAME_FOR_ROLLOVER').rollover();
*        
*        Within your HTML, be sure to give the image, use the same classname that you used to call .rollover()
*        Within the rel attribute, include the path to the rollover image
*        |    <img src="MY_IMAGE_NAME.jpg" class="CLASS_NAME_FOR_ROLLOVER" rel="MY_ROLLOVER_IMAGE.jpg" />
*
===================================================================== */ 

The actual code will always follow the same format

 (function($) {
        $.fn.rollover = function(options){
            // DEFINE OPTIONS
            var defaults = {};

            var settings = $.extend(defaults, options);

            return this.each(function() {

            });
        };
    })(jQuery);

So far so good?


GOING TO THE NEXT LEVEL :: A ROTATOR

Today, we’re going to use another “real world” example. Hopefully, by walking through the code, you’ll get a feel for setting up preferences—making your code even more reusable.

Here’s the HTML we’re dealing with:

 <div id="DIV_THAT_WRAPS_ALL_ROTATING_ITEMS">
    <div>My Rotator Item #1</div>
    <div>My Rotator Item #2</div>
    <div>My Rotator Item #3</div>
</div> 

Note:

The cool thing about this particular plugin is that you can put whatever you want in your div containers: Text, images, or a mix of both.


THE LOGIC:

  • Hide all the elements within the rotator
  • Show the first element
  • Set up a timer. Every time the timer goes off, hide the current item and show the next element in line.
  • If we’re at the end of the rotator, start over and show the first element

Easy enough?


Start Building

Let’s start plugging in the pieces.

I’m going to create a new file called jquery.rotator.js. Done! Next!.

I’m going to create a comment block at the top of my file that tells me what date I wrote the plugin, the author is me, my web site, the plugin requirements, and how to implement it.

In this particular instance, I’ll hold off writing out the implementation until the end, after the code is written.

 /* ==================================================================
*
*    ROTATOR PLUGIN
* 
*    DATE:                 NOVEMBER 27, 2009
*    AUTHOR:             AMY HAYWOOD
*     URL:                 WWW.AMYHAYWOOD.COM
*
*    REQUIREMENTS:         jQuery
*                        Be sure to include the jQuery library before including this script
*     IMPLEMENTATION:
*/ 

Nothing scary…yet. :-)

Now for the code. One of the things I like to do is actually write the code, as if I’m writing for one site. It’s easier for me to think that way, and then abstract (Abstract is just a fancy computer word for reducing the details to focus on the core concepts so that it can be reused as much as possible) it later.

Let’s just walk through the logic that we outlined.

Hide all the elements within the rotator.
I want my entire rotator to be wrapped in a div with an ID, so that I can find it easily. In the HTML I showed you earlier, I had an ID of DIV_THAT_WRAPS_ALL_ROTATING_ITEMS. You can name it whatever you want, just make sure you call it the same thing in your javascript.

$('#DIV_THAT_WRAPS_ALL_ROTATING_ITEMS').children('div').hide();

Show the first element.
This code is simple enough:

$('#DIV_THAT_WRAPS_ALL_ROTATING_ITEMS).children('div:first).fadeIn();

However, I know that I need to call this multiple times. Every time that I get to the end of my rotator, I’ll need to go back to the first element. Why not put this code in a function? Then, I don’t have to write out the code every time this happens, I can just call the function. But, Amy, it’s only one line of code. Is putting one line of code in it’s own function really the easiest thing? Glad you asked (pardon my cheesiness). Actually, yes! If I wanted to get crazy and make my rotator due something special every time it goes back to the beginning, I only have to change it in one place, inside the function.

Let’s call our function DisplayTheFirstProduct. (It could be an ad for a Store rotator).

DisplayTheFirstProduct = function($element) {
    $element.children('div:first').fadeIn();
    return;
};

DisplayTheFirstProduct($(#DIV_THAT_WRAPS_ALL_ROTATING_ITEMS)); 

You’ll notice that I already jumped ahead and did a little abstraction. I’m passing an $element to my function DisplayTheFirstProduct. Then, it uses the element that I passed in to find the first div nested inside and fades it in.

If we were to publish our code right now, all we’d see is the first item in our rotator. Nothing more, nothing less.

Moving along.

Set up a timer. Every time the timer goes off, hide the current item and show the next element in line.
This is going to require a little bit of Javascript. Nothing scary. A quick look at w3schools documentation shows that

var t=setTimeout("javascript statement",milliseconds); 

The setTimeout() method returns a value – In the statement above, the value is stored in a variable called t. If you want to cancel this setTimeout(), you can refer to it using the variable name.

The first parameter of setTimeout() is a string that contains a JavaScript statement. This statement could be a statement like “alert(‘5 seconds!’)” or a call to a function, like “alertMsg()”.

The second parameter indicates how many milliseconds from now you want to execute the first parameter.

Don’t freak out. All it’s saying is that the first parameter, the javascript statement, is what we want to happen every time the timer goes off. The second parameter is how often we want the timer to go off. In this case, it’s measured in milliseconds. There are 1000 milliseconds in 1 second. So 5000 means that the rotator will change every 5 seconds.

var myTimer = setInterval(ChangeProduct, 5000); 

I want it to run the function ChangeProduct every 5 seconds. Well, I need to define that function:

First, it will need to find the current item. We know that the current item will be visible to the user. So, let’s use the :visible selector in the jQuery library.

var currentlyVisible = $('#DIV_THAT_WRAPS_ALL_ROTATING_ITEMS').children('div:visible');

Then, we want to it fade out the current item.

currentlyVisible.fadeOut();

The cool thing about jQuery’s fadeOut() function is that you can pass in a callback function. This means it won’t run until the item is completely faded out. Then, we want it to display the next item in line.

currentlyVisible.fadeOut(function() {
    currentlyVisible.next().fadeIn();
});

If we’re at the end of the rotator, start over and show the first element
Let’s add a condition within our callback function to make sure we’re fading in the right element. If they’re no more elements, call our DisplayFirstProduct function, otherwise, show the next item.

currentlyVisible.fadeOut(function() {
    if (currentlyVisible.next().length < 1) {
        DisplayTheFirstProduct($('#DIV_THAT_WRAPS_ALL_ROTATING_ITEMS'));
    } else {
        currentlyVisible.next().fadeIn();
    }
});

Not too bad. Now, our entire ChangeProduct function looks like this:

ChangeProduct = function() {
    // FIND THE CURRENT PRODUCT
    var currentlyVisible = $('#DIV_THAT_WRAPS_ALL_ROTATING_ITEMS').children('div:visible');
    
    // FADE OUT THE CURRENT PRODUCT AND FADE IN AND SHOW THE NEXT ITEM
    currentlyVisible.fadeOut(function() {
        if (currentlyVisible.next().length < 1) {
            DisplayTheFirstProduct($('#DIV_THAT_WRAPS_ALL_ROTATING_ITEMS'));
        } else {
            currentlyVisible.next().fadeIn();
        }

    });

    return;
};

Awesome! If we never needed this code again, we’d be done…. but, we do want to reuse this code. So, let’s plug in our code to the structure:

(function($) {
    $.fn.rollover = function(options){
        // DEFINE OPTIONS
        var defaults = {};

        var settings = $.extend(defaults, options);

        return this.each(function() {

        });
    };
})(jQuery);

All of our content is going to go into the return this.each(function() {});. Now, it looks like this:

return this.each(function() {
    // CHANGES THE PRODUCT
    ChangeProduct = function() {                
        // FIND THE CURRENT PRODUCT
        var currentlyVisible = $('#DIV_THAT_WRAPS_ALL_ROTATING_ITEMS').children('div:visible');
        
        // FADE OUT THE CURRENT PRODUCT AND FADE IN AND SHOW THE NEXT ITEM
        currentlyVisible.fadeOut(function() {

            if (currentlyVisible.next().length < 1) {
                DisplayTheFirstProduct($('#DIV_THAT_WRAPS_ALL_ROTATING_ITEMS'));
            } else {
                currentlyVisible.next().fadeIn();
            }

        });

        return;
    };

    // DISPLAYS THE FIRST PRODUCT LISTED
    DisplayTheFirstProduct = function($element) {
        $element.children('div:first').fadeIn();
        return;
    };
    
    // INITIALIZE THE ROTATOR
    $('#DIV_THAT_WRAPS_ALL_ROTATING_ITEMS').children('div').hide();        // HIDE ALL THE PRODUCTS
    DisplayTheFirstProduct($('#DIV_THAT_WRAPS_ALL_ROTATING_ITEMS'));        // SET THE CURRENT PRODUCT
    
    var myTimer = setInterval(ChangeProduct, 5000);        
        
}); 

This would be great if our div always had the ID of DIV_THAT_WRAPS_ALL_ROTATING_ITEMS. Regardless, every time you call the plugin, you are actually going to pass in that element, that main div container with the ID of DIV_THAT_WRAPS_ALL_ROTATING_ITEMS.

Let me show you. In, the header, in $(document).ready(); There will be a line $(’#DIV_THAT_WRAPS_ALL_ROTATING_ITEMS’).rotator(); that applies the plugin to whatever div item we want.

Note:

That .rotator(); call matches up with the first part of our plugin structure $.fn.rollover = function(options){


If we’re passing in that element #DIV_THAT_WRAPS_ALL_ROTATING_ITEMS, then, we change everywhere in our plugin where it says #DIV_THAT_WRAPS_ALL_ROTATING_ITEMS to $element. Next, we just have to tell jQuery what that $element is.

$element = $(this); In our main code, when we call $(’#DIV_THAT_WRAPS_ALL_ROTATING_ITEMS’).rotator();, $(this) references $(’#DIV_THAT_WRAPS_ALL_ROTATING_ITEMS).

In our plugin structure return this.each(function() { cycles through every instance of $('#DIV_THAT_WRAPS_ALL_ROTATING_ITEMS) or $(this).

So, now our block of code within our plugin looks like:


return this.each(function() {
    var $element = $(this);
    
    // CHANGES THE PRODUCT
    // SHOULD BE THE NEXT ITEM LISTED, UNLESS IT IS THE LAST ITEM, IN WHICH CASE IT GOES BACK TO THE FIRST ITEM
    ChangeProduct = function() {                
        // FIND THE CURRENT PRODUCT
        var currentlyVisible = $element.children('div:visible');
        
        // FADE OUT THE CURRENT PRODUCT AND FADE IN AND SHOW THE NEXT ITEM
        currentlyVisible.fadeOut(function() {
            if (currentlyVisible.next().length < 1) {
                DisplayTheFirstProduct($element);
            } else {
                currentlyVisible.next().fadeIn();
            }

        });

        return;
    };

    // DISPLAYS THE FIRST PRODUCT LISTED
    DisplayTheFirstProduct = function($element) {
        $element.children('div:first').fadeIn();
        return;
    };
        
        
    // INITIALIZE THE ROTATOR
    $element.children('div').hide();        // HIDE ALL THE PRODUCTS
    DisplayTheFirstProduct($element);        // SET THE CURRENT PRODUCT
    
    var myTimer = setInterval(ChangeProduct, 5000);        
};

The only thing we have left to do is pass in our preferences.—Let our timer of 5000 milliseconds be configured.

That’s what the defaults section of our structure is for:

// DEFINE OPTIONS
    var defaults = {
        amount_of_time: 5000                    // AMOUNT OF TIME BEFORE CHANGING IN MILLISECONDS
    };

We leave 5000 listed here. If I don’t set any preference, it will automatically default to 5 seconds.

Then, we change our interval in our timer, where 5000 is hard coded in, to look at our preferences.

var myTimer = setInterval(ChangeProduct, settings.amount_of_time);

Also, I just picked a variable name, amount_of_time there’s nothing special about. You just need to make sure that the reference within the code matches.

NOTE:

You might be asking why we’re referencing settings.amount_of_time when we defined amount_of_time in defaults={}. Well, if you look at our plugin structure, we actually pass in our defaults to settings: var settings = $.extend(defaults, options);. The $.extend() comes straight out of the jQuery library.

That’s it! Our full plugin code:


(function($) {
    $.fn.rotator = function(options){
        // DEFINE OPTIONS
        var defaults = {

            amount_of_time: 1000                    // AMOUNT OF TIME BEFORE CHANGING IN MILLISECONDS
        };
    
        var settings = $.extend(defaults, options);
    
        return this.each(function() {
            var $element = $(this);
        
            // CHANGES THE PRODUCT
            // SHOULD BE THE NEXT ITEM LISTED, UNLESS IT IS THE LAST ITEM, IN WHICH CASE IT GOES BACK TO THE FIRST ITEM
            ChangeProduct = function() {                
                // FIND THE CURRENT PRODUCT
                var currentlyVisible = $element.children('div:visible');
                
                // FADE OUT THE CURRENT PRODUCT AND FADE IN AND SHOW THE NEXT ITEM
                currentlyVisible.fadeOut(function() {

                    if (currentlyVisible.next().length < 1) {
                        DisplayTheFirstProduct($element);
                    } else {
                        currentlyVisible.next().addClass(settings.currentAdClass).fadeIn();
                    }

                });

                return;
            };

            // DISPLAYS THE FIRST PRODUCT LISTED
            DisplayTheFirstProduct = function($element) {
                $element.children('div:first').fadeIn();
                return;
            };
            
 &
nbsp;          
            // INITIALIZE THE ROTATOR
            $element.children('div').hide();        // HIDE ALL THE PRODUCTS
            DisplayTheFirstProduct($element);        // SET THE CURRENT PRODUCT
            
            var myTimer = setInterval(ChangeProduct, settings.amount_of_time);        
            
        });
    };
})(jQuery); 

In our HTML head, it would look like this:

 <header>
    <title>JQUERY ROLLOVER TEST</title>
    
    <!-- LINK JAVASCRIPT FILES -->
    <script src="js/jquery-1.3.2.min.js" type="text/javascript" charset="utf-8"></script>
    <script src="js/jquery.rotator.js" type="text/javascript" charset="utf-8"></script>
    <script type="text/javascript" charset="utf-8">
        $(document).ready(function () {
            // INITIALIZE PLUGIN
            $('#DIV_THAT_WRAPS_ALL_ROTATING_ITEMS').rotator(); 
        });
    </script>
</header> 

If we wanted to change the amount of time it took to rotator, we would change that one line to read:

$('#DIV_THAT_WRAPS_ALL_ROTATING_ITEMS').rotator({
    amount_of_time: 5000;
});

Before we throw our hands in the air in triumph, let’s go back and add that last block of documentation to the top of our jQuery file, our implementation. I know it’s tedious, but it will be worth it when you get ready to reuse your code and can’t remember how you’re supposed to implement it. Did you notice? I speak from experience!

/*
    *     IMPLEMENTATION:
    *        Within the $(document).ready(); all that you really need to include is:
    *        |    $('#DIV_THAT_WRAPS_ALL_ROTATING_ITEMS').rotator();
    *    
    *        However, you can change the amount of time that it takes ot rotate:
    *        |    $('#DIV_THAT_WRAPS_ALL_ROTATING_ITEMS').rotator({
    *        |        amount_of_time: 5000;
    *        |    });
    *
    *        Within your HTML, you must have a div that wraps all rotator items with a unique id.
    *        Each individual rotator item must be wrapped in a div
    *        |    <div id="DIV_THAT_WRAPS_ALL_ROTATING_ITEMS">
    *        |        <div>My Rotator Item #1</div>
    *        |        <div>My Rotator Item #2</div>
    *        |        <div>My Rotator Item #3</div>
    *        |    </div>
*/

 

Download My Code

jquery.rotator.js (4 kB)


What’s next?

Almost three weeks ago, I spoke at the Young Adult fall retreat about “Leveraging Your Life via Social Media”.—A lot of it focused on how to use social media responsibly and how to get the most out of it. Even I learned something while preparing. I’ll post my notes + slides on Sunday.