Writing a jQuery Plug-in

In my last entry, I presented my latest venture in jQuery plug-in writing. You've seen how easy it is to use it, now I'll show you how simple it is to write one.

Preparing the Field

Before writing a plug-in, or any other type of code, we need a need. That doesn't sound right, does it? Um... we require requirements? ... There has to be a need. To code, we need requirements, functionality that we want to provide. So, let's look at what we need for our tooltip plug-in.

  • Floating tooltip, appears dynamically near the appropriate element;
  • Easily skinnable (a big must for me);
  • Cross-browser (also a big must);
  • Can be applied to any element;
  • User can customize the header and content.

A good place to start is to figure out our tooltip's markup. I wanted it to be easily skinnable, and using Douglas Bowman's Sliding Doors of CSS technique I picked up in Bulletproof Web Design seemed like a good place to start.

Sliding Doors of CSS

The premise of this technique is that you use two background images, both longer (or larger) than what is normally required. Placing them on the appropriate sections of our element makes them adjust to our content.

Figure 1 (the "Sliding Doors")

Figure 2 (the effect in action)

As we stated earlier, this technique requires two background images. However, we can only place one per element. To bypass this limitation, we are going to take advantage of our optimized markup and use the containing div for the bottom background image (as it will stretch with the content), and the header (h2) for the top image:

The markup

<div id='rzTooltip'>
    <h2 id='rzTooltip_header'>Header</h2>
    <div id='rzTooltip_body'>Body</div>
</div>

The style

#rzTooltip {
    width: 350px;
}
#rzTooltip_header {
    background: url('images/tooltip_top.gif') top left no-repeat;
}
#rzTooltip_body {
    background: url('images/tooltip_bottom.gif') bottom left no-repeat;
}
#rzTooltip_header {
    padding: 5px 5px 0 5px;
    margin: 0;
    border-bottom: 1px solid #369;
    color: #006;
}
#rzTooltip_body {
    padding: 5px;
}
#rzTooltip_body p {
    margin: 0 0 10px 0;
}

This is the way I went for skinning my tooltip. However, none of this is required. If you don't apply styles to the tooltip, it will still work, albeit it will be a bit bland. You can substitute the images with some of your own. Why not remove the padding? Make it more pronounced? All of it is customizable (or not). And now that we have the markup, and a way to style our tooltip, let's see how we put it all together.

History of a Plug-in

You've been waiting through all that introduction to see some Javascript, and here it is. When I'm writing a plug-in, I don't want other applications to be able to see or modify my variables or methods. An easy way to do this is to wrap our plug-in in an anonymous function:

(function () {
    // this is my code, invisible from prying eyes!

})();

The first thing we will want to do is to make our plug-in available to jQuery. To do so, we attach it to jQuery's fn object:

$.fn.rzTooltip=function () { };

When jQuery fires our method, the first thing we want to do is check for the existence of our tooltip markup. To do so, look for an element with an id of rzTooltip:

if (!$("#rzTooltip").length) {
    $("body").append("<div id='rzTooltip'><h2 id='rzTooltip_header'></h2><div id='rzTooltip_body'></div></div>");
    $("#rzTooltip").css({ top: -1000, left: -1000, position: 'absolute', zIndex: 100 });
    $("body").append("<iframe id='rzTooltip_iframe'></iframe>");
    $("#rzTooltip_iframe").css({ top: -1000, left: -1000, position: 'absolute', zIndex: 50, opacity: 0 });
}

If the length returned is 0, we need to append our markup to the body element. We then position it off-screen and make sure it's position is set to absolute. Notice that we're also creating an iframe. We stated in our plug-in requirements that it had to be cross-browser. In Internet Explorer, drop-downs always appear above absolutely positionned elements. This is obviously a major deal-breaker. However, there is a rather simple fix: iframes can be placed between these elements and the evil drop-downs. All we have to do is make sure that the z-index is lower than the element we want to float over our drop-downs. With an opacity set to 0, we'll never see the iframe, but it will serve us well.

Now that we've made sure that our tooltip exists (although it is sitting somewhere off-screen), we'll want to apply some mouseover and mouseout events to our matched elements. To do so, all we need to do is use jQuery's each method to loop over these elements.

return this.each(function () {

    $(this).mouseover(function () {

        // do some mouseover action with this matched element

    }).mouseout(function () {

        // do some mouseout action with this matched element

    });

});

In our mouseover event, the first thing we'll want to do is create a shortcut variable of our tooltip. Then, we sanity-check the tooltip's position and make sure it's still off-screen. We store the element's title attribute in a global variable, and empty the original. This is to prevent the default browser's behavior for showing the title.

// shortcut variable
var tooltip=$("#rzTooltip");

// make sure the tooltip is off-screen
tooltip.css({
    top: -1000,
    left: -1000
});

// backup the elements title
rzTooltip_title=$(this).attr("title");

// empty the title attribute (override the browser behavior)
$(this).attr("title", "");

This next part is where we are going to parse the title attribute and populate the tooltip's header and content.

// create an array containing the different title strings, separated by a pipe (|)
var title=rzTooltip_title.split("|");

// populate the tooltip header
$("#rzTooltip_header").html(title[0]);

// empty the tooltip body
$("#rzTooltip_body").empty();

// populate the tooltip body by looping over the title array
for (var i=1; i<title.length; i++)
    $("#rzTooltip_body").append("<p>" + title[i] + "</p>");

We use Javascript's split method to separate the string by it's delimiter (a pipe in this case), and store each piece in an array. We then take the first element as the new tooltip header. Next step is emptying the tooltip body before looping over our array (making sure to avoid the first (0) index) and appending it with a new paragraph per piece.

// create browser-specific max width and height vars
var maxWidth, maxHeight;
if ($.browser.msie) {
    maxWidth=document.documentElement.scrollWidth;
    maxHeight=document.documentElement.scrollHeight;
} else {
    maxWidth=Math.max($('html').width(), $(window).width()-30);
    maxHeight=Math.max($('html').height(), $(window).height()-30);
}

To position our tooltip, we need to make sure it doesn't appear off-screen. To do so, we need to get the current document's dimensions. Since jQuery's browser.msie property returns true under Internet Explorer, we can get use different methods to get our document's dimensions. Under IE, we need document.documentElement.scrollWidth and scrollHeight. Under Firefox (and other browsers) however, we want to get the max dimension between the html element and the actual window.

// get position (make sure the tooltip isn't out of bounds)
var dims={
    t: ($(this).offset().top + tooltip.height() >
maxHeight - 10 ? $(this).offset().top+$(this).height()-tooltip.height() : $(this).offset().top),
    l: ($(this).offset().left + $(this).width() + tooltip.width() > maxWidth - 10 ? $(this).offset().left-tooltip.width()-10 : $(this).offset().left + $(this).width())
};

This new section stores the tooltip's (and iframe's) position in a local object. Here we use the dimension to get the current element's position with it's offset() method. Here is the logic behind our tooltip positionning:

  • Top: If the element's top offset added to the tooltip's height is more than the document's maxHeight - 10, place the tooltip at the element's top offset, adding it's height and removing the tooltip's height; else, place it at the element's height.
  • Left: If the element's left offset added to it's width and the tooltip's width is greater than the maxWidth - 10, place the tooltip at the element's left offset, removing the tooltip's width and 10 pixels; else, place it at the element's left, adding the tooltip's width.

// place the tooltip
tooltip.css({ top: dims.t, left: dims.l });

// place and size the iframe to hide undesirable dropdowns in IE
$("#rzTooltip_iframe").css({ top: dims.t, left: dims.l, height: tooltip.height(), width: tooltip.width() });

The last thing we need to do in our mouseover event is position the tooltip and the iframe, and copy the tooltip's dimensions to the iframe.

All we need in our mouseout event is placing the tooltip and iframe off-screen, and reset the element's title attribute.

// take the tooltip and iframe off-screen
$("#rzTooltip,#rzTooltip_iframe").css({ top: -1000, left: -1000 });

// reset the element's title
$(this).attr("title", rzTooltip_title);

Take a Bow

For your viewing pleasure, there is a demo available if you follow the link. Forgive the slow loading images in IE. They're not really big, but my host seems to be having problems serving them. I also attached the source files (with images) to this post. All you need to do is click on the download link below. If you have any questions or comments, or solutions to my slow loading images, feel free to drop me a line.

I want my baby back, baby back, baby back... ribs.

Related Blog Entries

Comments
John Pereira's Gravatar
Good work with the great tutorial. Most of the plugin authoring tutorials deal with coloring elements or something like that rather than events. Exactly what I was looking for.
# Posted By John Pereira | 8/25/08 12:10 AM
battery's Gravatar
http://www.batteryfast.co.uk/laptop-ac-adapter/hp/... New AC ADAPTER FOR HP 316688-001 316687-001 317188-001 19V 7.1A
http://www.batteryfast.co.uk/laptop-ac-adapter/hp/... 19V 9.5A AC Power Adapter for HP Compaq 180W multi-pin
# Posted By battery | 11/15/08 1:35 AM
# Posted By battery | 11/20/08 5:57 AM
BlogCFC was created by Raymond Camden. This blog is running version 5.9.1.002. Contact Blog Owner