Chapter 7. Extending jQuery with custom plugins

This chapter covers

  • Why to extend jQuery with custom code
  • The rules for effectively extending jQuery
  • Writing custom utility functions
  • Writing custom wrapper methods

Over the course of the previous chapters, we’ve seen that jQuery gives us a large toolset of useful commands and functions; we’ve also seen that we can easily tie these tools together to give our pages whatever behavior we choose. Sometimes that code follows common patterns we want to use again and again. When such patterns emerge, it makes sense to capture these repeated operations as reusable tools that we can add to our original toolset. In this chapter, we explore how to capture these reusable fragments of code as extensions to jQuery.

But before any of that, let’s discuss why we’d want to pattern our own code as extensions to jQuery in the first place.

7.1. Why extend?

If you’ve been paying attention at all while reading through this book, as well as to the code examples presented within it, you undoubtedly have noted that adopting jQuery for use in our pages has a profound effect on how script is written within a page.

The use of jQuery promotes a certain style for a page’s code, frequently in the guise of forming a wrapped set of elements and then applying a jQuery command, or chain of commands, to that set. When writing our own code, we can write it however we please, but most experienced developers agree that having all of the code on a site, or at least the great majority of it, adhere to a consistent style is a good practice.

So one good reason to pattern our code as jQuery extensions is to help maintain a consistent code style throughout the site.

Not reason enough? Need more? The whole point of jQuery is to provide a set of reusable tools and APIs. The creators of jQuery carefully planned the design of the library and the philosophy of how the tools are arranged to promote reusability. By following the precedent set by the design of these tools, we automatically reap the benefit of the planning that went into these designs—a compelling second reason to write our code as jQuery extensions.

Still not convinced? The final reason we’ll consider (though it’s quite possible others could list even more reasons) is that, by extending jQuery, we can leverage the existing code base that jQuery makes available to us. For example, by creating new jQuery commands (wrapper methods), we automatically inherit the use of jQuery’s powerful selector mechanism. Why would we write everything from scratch when we can layer upon the powerful tools jQuery already provides?

Given these reasons, it’s easy to see that writing our reusable components as jQuery extensions is a good practice and a smart way of working. In the remainder of this chapter, we’ll examine the guidelines and patterns that allow us to create jQuery plugins and we’ll create a few of our own. In the following chapter, which covers a completely different subject (Ajax), we’ll see even more evidence that creating our own reusable components as jQuery plugins in real-world scenarios helps to keep the code consistent and makes it a whole lot easier to write those components in the first place.

But first, the rules...

7.2. The jQuery plugin authoring guidelines

Sign! Sign! Everywhere a sign! Blocking out the scenery, breaking my mind. Do this! Don’t do that! Can’t you read the sign?

—Five Man Electric Band, 1971

Although the Five Man Electric Band may have lyrically asserted an antiestablishment stance against rules back in 1971, sometimes rules are a good thing. Without any, chaos would reign.

Such it is with the rules—more like common sensical guidelines—governing how to successfully extend jQuery with our own plugin code. These guidelines help us ensure, not only that our new code will plug into the jQuery architecture properly, but also that it will work and play well with other jQuery plugins and even other JavaScript libraries.

Extending jQuery takes one of two forms:

  • Utility functions defined directly on $ (an alias for jQuery)
  • Methods to operate on a jQuery wrapped set (so-called jQuery commands)

In the remainder of this section, we’ll go over some guidelines common to both types of extensions. Then in the following sections, we’ll tackle the guidelines and techniques specific to writing each type of plugin element.

7.2.1. Naming files and functions

To Tell the Truth was an American game show, first airing in the 1950’s, in which multiple contestants claimed to be the same person with the same name, and a panel of celebrities was tasked with determining which person was whom they claimed to be. Although fun for a television audience, name collisions are not fun at all when it comes to programming.

We’ll discuss avoiding name collisions within our plugins, but first let’s address naming the files within which we’ll write our plugins so that they do not conflict with other files.

The guideline recommended by the jQuery team is simple but effective, advocating the following format:

  • Prefix the filename with jquery.
  • Follow that with the name of the plugin.
  • Conclude with .js.

For example, if we write a plugin that we want to name Fred, our JavaScript filename for this plugin is

jquery.fred.js

The use of the jquery. prefix eliminates any possible name collisions with files intended for use with other libraries. After all, anyone writing non-jQuery plugins has no business using the jquery. prefix.

But that leaves the plugin name itself still open for contention within the jQuery community.

When we’re writing plugins for our own use, all we need to do is avoid conflicts with any other plugins that we plan to use. But when writing plugins that we plan to publish for others to use, we need to avoid conflicts with any other plugin that’s already published.

The best way to avoid conflicts is to stay in tune with the goings-on within the jQuery community. A good starting point is the page at http://docs.jquery.com/ Plugins; but, beyond being aware of what’s already out there, there are other precautions we can take.

One way to ensure that our plugin filenames are unlikely to conflict with others is to sub-prefix them with a name that’s unique to us or our organization. For example, all of the plugins developed in this book use the filename prefix jquery.jqia (jqia being short for jQuery in Action) to help make sure that they won’t conflict with anyone else’s plugin filenames should anyone wish to use them in their own web applications. Likewise, the files for the jQuery Form Plugin begin with the prefix jquery.form. Not all plugins follow this convention, but as the number of plugins increases, it will become more and more important to follow such conventions.

Similar considerations need to be taken with the names we give to our functions, whether they’re new utility functions or methods on the jQuery wrappers.

When creating plugins for our own use, we’re usually aware of what other plugins we’ll use; it’s an easy matter to avoid any naming collisions. But what if we’re creating our plugins for public consumption? Or what if our plugins, that we initially intended to use privately, turn out to be so useful that we want to share them with the rest of the community?

Once again, familiarity with the plugins that already exist will go a long way in avoiding API collisions, but we also encourage gathering collections of related functions under a common prefix (similar to the proposal for filenames) to avoid cluttering the namespace.

Now, what about conflicts with that $?

7.2.2. Beware the $

“Will the real $ please stand up?”

Having written a fair amount of jQuery code, we’ve seen how handy it is to use the $ alias in place of jQuery. But when writing plugins that may end up in other people’s pages, we can’t be quite so cavalier. As plugin authors, we have no way of knowing whether a page author intends to use the $.noConflict() function to allow the $ alias to be usurped by another library.

We could employ the sledgehammer approach and use the jQuery name in place of the $ alias, but dang it, we like using $ and are loath to give up on it so easily.

Section 6.2 discussed an idiom often used to make sure that the $ alias referred to the jQuery name in a localized manner without affecting the remainder of the page, and this little trick can also be (and often is) employed when defining jQuery plugins as follows:

(function($){
//
// Plugin definition goes here
//
})(jQuery);

By passing jQuery to a function that defines the parameter as $, $ is guaranteed to reference jQuery within the body of the function.

We can now happily use $ to our heart’s content in the definition of the plugin.

Before we dive into learning how to add new elements to jQuery, let’s look at one more technique plugin authors are encouraged to use.

7.2.3. Taming complex parameter lists

Most plugins tend to be simple affairs that require few, if any, parameters. We’ve seen ample evidence of this in the vast majority of the core jQuery methods and functions, which either take a small handful of parameters or none at all. Intelligent defaults are supplied when optional parameters are omitted, and parameter order can even take on a different meaning when some optional parameters are omitted.

The bind() method is a good example; if the optional data parameter is omitted, the listener function, which is normally specified as the third parameter, can be supplied as the second. The dynamic and interpretive nature of JavaScript allows us to write such flexible code, but this sort of thing can start to break down and get complex (both for page authors and ourselves as the plugin authors) as the number of parameters grows larger. The possibility of a breakdown increases when many of the parameters are optional.

Consider a somewhat complex function whose signature is as follows:

function complex(p1,p2,p3,p4,p5,p6,p7) {

This function accepts seven arguments; let’s say that all but the first are optional. There are too many optional arguments to make any intelligent guesses about the intention of the caller when optional parameters are omitted (as we saw with the bind() method). If a caller of this function is only omitting trailing parameters, this isn’t much of a problem because the optional trailing arguments can be detected as nulls. But what if the caller wants to specify p7 but let p2 through p6 default? Callers would need to use placeholders for any omitted parameters and write

complex(valueA,null,null,null,null,null,valueB);

Yuck! Even worse is a call such as

complex(valueA,null,valueC,valueD,null,null,valueB);

along with other variations of this nature. Page authors using this function are forced to carefully keep track of counting nulls and the order of the parameters; plus, the code is difficult to read and understand.

But short of not allowing so many options to the caller, what can we do?

Again, the flexible nature of JavaScript comes to the rescue; a pattern that allows us to tame this chaos has arisen among the page-authoring communities—the options hash.

Using this pattern, optional parameters are gathered into a single parameter in the guise of a JavaScript Object instance whose property name/value pairs serve as the optional parameters.

Using this technique, our first example could be written as

complex(valueA, {p7: valueB});

And the second as the following:

complex(valueA, {
p3: valueC,
p4: valueD,
p7: valueB
});

Much better!

We don’t have to account for omitted parameters with placeholder nulls, and we also don’t need to count parameters; each optional parameter is conveniently labeled so that it’s clear to see exactly what it represents (when we use better parameter names than p1 through p7, that is).

Although this is obviously a great advantage to the caller of our complex functions, what about the ramifications for us as the function authors? As it turns out, we’ve already seen a jQuery-supplied mechanism that makes it easy for us to gather these optional parameters together and to merge them with default values. Let’s reconsider our complex example function with a required parameter and six options. The new, simplified signature is

complex(p1,options)

Within our function, we can merge those options with default values with the handy $.extend() utility function. Consider the following:

function complex(p1,options) {
var settings = $.extend({
option1: defaultValue1,
option2: defaultValue2,
option3: defaultValue3,
option4: defaultValue4,
option5: defaultValue5,
option6: defaultValue6
},options||{});
// remainder of function...
}

By merging the values passed to us by the page author in the options parameter with an object containing all the available options with their default values, the settings variable ends up with all possible option default values superseded by any explicit values specified by the page author.

Note that we guard against an options object that’s null or undefined with ||{}, which supplies an empty object if options evaluates to false (as we know null and undefined do).

Easy, versatile, and caller-friendly!

We’ll see examples of this pattern in use later in this chapter and in jQuery functions that will be introduced in chapter 8. But for now, let’s finally look at how we extend jQuery with our own utility functions.

7.3. Writing custom utility functions

In this book, we use the term utility function to describe functions defined as properties of jQuery (and therefore $). These functions are not intended to operate on DOM elements—that’s the job of methods defined to operate on a jQuery wrapped set—but to either operate on non-element JavaScript objects or perform some other nonobject-specific operation. Some examples we’ve seen of these types of function are $.each() and $.noConflict().

In this section, we’ll learn how to add our own similar functions.

Adding a function as a property to an Object instance is as easy as declaring the function and assigning it to the object property. (If this seems like black magic to you and you have not yet read through the appendix, now would be a good time to do so.) Creating a trivial custom utility function should be as easy as

$.say = function(what) { alert('I say '+what); }

And in truth, it is that easy. But this manner of defining a utility function isn’t without its pitfalls; remember our discussion in section 7.2.2 regarding the $? What if some page author is including this function on a page that uses Prototype and has called $.noConflict()? Rather than add a jQuery extension, we’d create a method on Prototype’s $() function. (Get thee to the appendix if the concept of a method of a function makes your head hurt.)

This isn’t a problem for a private function that we know will never be shared, but even then, what if some future changes to the pages usurp the $? It’s a good idea to err on the side of caution.

One way to ensure that someone stomping on $ doesn’t also stomp on us is not using the $ at all. We could write our trivial function as

jQuery.say = function(what) { alert('I say '+what); }

This seems like an easy way out but proves to be less than optimal for more complex functions. What if the function body utilizes lots of jQuery methods and functions internally to get its job done? We’d need to use jQuery rather than $ throughout the function. That’s rather wordy and inelegant; besides, once we use the $, we don’t want to let it go!

So looking back to the idiom we introduced in section 7.2.2, we can safely write our function as follows:

(function($){
$.say = function(what) { alert('I say '+what); }
})(jQuery);

We highly encourage using this pattern (even though it may seem like overkill for such a trivial function) because it protects the use of $ when declaring and defining the function. Should the function ever need to become more complex, we can extend and modify it without wondering whether it’s safe to use the $ or not.

With this pattern fresh in our minds, let’s implement a non-trivial utility function of our own.

7.3.1. Creating a data manipulation utility function

Often, when emitting fixed-width output, it’s necessary to take a numeric value and format it to fit into a fixed-width field (where width is defined as number of characters). Usually such operations will right-justify the value within the fixed-width field and prefix the value with enough fill characters to make up any difference between the length of the value and the length of the field.

Let’s write such a utility function that’s defined with the following syntax:


Function syntax: $.toFixedWidth

$.toFixedWidth(value,length,fill)

Formats the passed value as a fixed-width field of the specified length. An optional fill character can be supplied. If the numeric value exceeds the specified length, its higher order digits will be truncated to fit the length.

Parameters

value

(Number) The value to be formatted.

length

(Number) The length of the resulting field.

fill

(String) The fill character used when front-padding the value. If omitted, 0 is used.

Returns

The fixed-width field.


The implementation of this function is shown in listing 7.1.

Listing 7.1. Implementation of the $.toFixedWidth() utility function

This function is simple and straightforward. The passed value is converted to its string equivalent, and the fill character is determined either from the passed value or the default of 0 . Then, we compute the amount of padding needed .

If we end up with negative padding (the result is longer than the passed field length), we truncate from the beginning of the result to end up with the specified length ; otherwise, we pad the beginning of the result with the appropriate number of fill characters prior to returning it as the result of the function .

Simple stuff, but it serves to show how easily we can add a utility function. And, as always, there’s room for improvement. Consider the following exercises:

  • As with most examples in books, the error checking is minimal to focus on the lesson at hand. How would you beef up the function to account for caller errors such as not passing numeric values for value and length? What if they don’t pass them at all?
  • We were careful to truncate numeric values that were too long in order to guarantee that the result was always the specified length. But, if the caller passes more than a single-character string for the fill character, all bets are off. How would you handle that?
  • What if you don’t want to truncate too-long values?

Now, let’s tackle a more complex function in which we can make use of the $.toFixedWidth() function that we just wrote.

7.3.2. Writing a date formatter

If you’ve come to the world of client-side programming from the server, one of the things you may have longed for is a simple date formatter; something that the JavaScript Date type doesn’t provide. Because such a function would operate on a Date instance, rather than any DOM element, it’s a perfect candidate for a utility function. Let’s write one that uses the following syntax:


Function syntax: $.formatDate

$.formatDate(date,pattern)

Formats the passed date according to the supplied pattern. The tokens that are substituted in the pattern are as follows:

  • yyyy: the 4-digit year
  • yy: the 2-digit year
  • MMMM: the full name of the month
  • MMM: the abbreviated name of the month
  • MM: the month number as a 0-filled, 2-character field
  • M: the month number
  • dd: the day in the month as a 0-filled, 2-character field
  • d: the day in the month
  • EEEE: the full name of the day of the week
  • EEE: the abbreviated name of the day of the week
  • a: the meridium (AM or PM)
  • HH: the 24-hour clock hour in the day as a 2-character, 0-filled field
  • H: the 24-hour clock hour in the day
  • hh: the 12-hour clock hour in the day as a 2-character, 0-filled field
  • h: the 12-hour clock hour in the day
  • mm: the minutes in the hour as a 2-character, 0-filled field
  • m: the minutes in the hour
  • ss: the seconds in the minute as a 2-character, 0-filled field
  • s: the seconds in the minute
  • S: the milliseconds in the second as a 3-character, 0-filled field

Parameters

date

(Date) The date to be formatted.

pattern

(String) The pattern to format the date into. Any characters not matching pattern tokens are copied as-is to the result.

Returns

The formatted date.


The implementation of this function is shown in listing 7.2. We’re not going to go into great detail regarding the algorithm used to perform the formatting (after all, this isn’t an algorithms book), but we’re going to use this implementation to point out some interesting tactics that we can use when creating a somewhat complex utility function.

Listing 7.2. Implementation of the $.formatDate() utility function

The most interesting aspect of this implementation, aside from a few JavaScript tricks used to keep the amount of code in check, is that the function needs some ancillary data to do its job—in particular:

  • A regular expression used to match tokens in the pattern
  • A list of the English names of the months
  • A list of the English names of the days
  • A set of sub-functions designed to provided the value for each token type given a source date

We could have included each of these as var definitions within the function body, but that would clutter an already somewhat involved algorithm; and because they’re constants, it makes sense to segregate them from variable data.

We don’t want to pollute the global namespace, or even the $ namespace, with a bunch of names needed only by this function, so we make these declarations properties of our new function itself. Remember, JavaScript functions are first-class objects, and they can have their own properties like any other JavaScript object.

As for the formatting algorithm itself? In a nutshell, it operates as follows:

  • Creates an array to hold portions of the result.
  • Iterates over the pattern, consuming identified token and non-token characters until it has been completely inspected.
  • Resets the regular expression (stored in $.formatDate.patternParts) on each iteration by setting its lastIndex property to 0.
  • Tests the regular expression for a token match against the current beginning of the pattern.
  • Calls the function in the $.formatDate.patternValue collection of conversion functions to obtain the appropriate value from the Date instance if a match occurs. This value is pushed onto the end of the results array, and the matched token is removed from the beginning of the pattern.
  • Removes the first character from the pattern and adds it to the end of the results array if a token isn’t matched at the current beginning of the pattern.
  • Joins the results array into a string and returns it as the value of the function when the entire pattern has been consumed.

Note that the conversion functions in the $.formatDate.patternValue collection make use of the $.toFixedWidth() function that we created in the previous section.

You’ll find both of these functions in the file chapter7/jquery.jqia.dateFormat.js and a rudimentary page to test it at chapter7/test.dateFormat.html.

Operating on run-of-the-mill JavaScript objects is all well and good, but the real power of jQuery lies in the wrapper methods that operate on a set of DOM elements collected via the power of jQuery selectors. Next, let’s see how we can add our own powerful wrapper methods.

7.4. Adding new wrapper methods

The true power of jQuery lies in the ability to easily and quickly select and operate on DOM elements. Luckily, we can extend that power by adding wrapper methods of our own that manipulate selected DOM elements as we deem appropriate. By adding wrapper methods, we automatically gain the use of the powerful jQuery selectors to pick and choose which elements are to be operated on without having to do all the work ourselves.

Given what we know about JavaScript, we probably could have figured out on our own how to add utility functions to the $ namespace, but that’s not true of wrapper functions. There’s a tidbit of jQuery-specific information that we need to know; to add wrapper methods to jQuery, we must assign them as properties to an object named fn in the $ namespace.

The general pattern for creating a wrapper functions is

$.fn.wrapperFunctionName = function(params){function-body};

Let’s concoct a trivial wrapper method to set the color of the matched DOM elements to blue.

(function($){
$.fn.makeItBlue = function() {
return this.css('color','blue'),
}
})(jQuery);

As with utility functions, we make the declaration within an outer function that guarantees that $ is an alias to jQuery. But unlike utility functions, we create the new wrapper method as a property of $.fn rather than of $.


Note

If you’re familiar with object-oriented JavaScript and its prototype-based class declarations, you might be interested to know that $.fn is merely an alias for the prototype property of the jQuery constructor function.


Within the body of the method, the function context (this) refers to the wrapped set. We can use all of the predefined jQuery commands on it; as in this example, we call the css() command on the wrapped set to set the color to blue for all matched DOM elements.


Warning

The function context (this) within the main body of a wrapper method refers to the wrapped set, but when inline functions are declared within this function, they each have their own function contexts. You must take care when using this under such circumstances to make sure that it’s referring to what you think it is! For example, if you use each() with its iterator function, this within the iterator function references the DOM element for the current iteration.


We can do almost anything we like to the DOM elements in the wrapped set, but there is one very important rule when defining new wrapper methods; unless the function is intended to return a specific value, it should always return the wrapped set as its return value. This allows our new command to take part in any jQuery command chains. In our example, because the css() command returns the wrapped set, we simply return the result of the call to css().

In this example, we apply the jQuery css() command to all the elements in the wrapped set by applying it to this. If, for some reason, we need to deal with each wrapped element individually (perhaps because we need to make conditional processing decisions), the following pattern can be used:

(function($){
$.fn.someNewMethod = function() {
return this.each(function(){
//
// Function body goes here -- this refers to individual
// elements
//
});
}
})(jQuery);

In this pattern, the each() command is used to iterate over every individual element in the wrapped set. Note that, within the iterator function, this refers to the current DOM element rather than the entire wrapped set. The wrapped set returned by each() is returned as the new method’s value so that this method can participate in chaining.

That’s all there is to it, but (isn’t there always a but?) there are some techniques we should be aware of when creating more involved jQuery wrapper methods. Let’s define a couple more plugin methods of greater complexity to examine those techniques.

7.4.1. Applying multiple operations in a wrapper method

Let’s develop another new plugin method that performs more than a single operation on the wrapped set. Imagine that we need to be able to flip the read-only status of text fields within a form and to simultaneously and consistently affect the appearance of the field. We could easily chain a couple of existing jQuery commands together to do this, but we want to be neat and tidy about it and bundle these operations together into a single method.

We’ll name our new command setReadOnly(), and its syntax is as follows:


Command syntax: setReadOnly

setReadOnly(state)

Sets the read-only status of wrapped text fields to the state specified by state. The opacity of the fields will be adjusted: 100% if not read-only or 50% if read-only. Any elements in the wrapped set other than text fields are ignored.

Parameters

state

(Boolean) The read-only state to set. If true, the text fields are made read-only; otherwise, the read-only status is cleared.

Returns

The wrapped set.


The implementation of this plugin is shown in listing 7.3 and can be found in the file chapter7/jquery.jqia.setreadonly.js.

Listing 7.3. Implementation of the setReadOnly() plugin method
(function($){
$.fn.setReadOnly = function(readonly) {
return this.filter('input:text')
.attr('readonly',readonly)
.css('opacity', readonly ? 0.5 : 1.0);
}
})(jQuery);

This example is only slightly more complicated than our initial example, but exhibits the following additional key concepts:

  • A parameter is passed that affects how the method operates.
  • Three jQuery commands are applied to the wrapped set by use of jQuery chaining.
  • The new command can participate in a jQuery chain because it returns the wrapped set as its value.
  • The filter() command is used to ensure that, no matter what set of wrapped elements the page author applied this method to, only text fields are affected.

How might we put this method to use?

Often, when defining an online order form, we may need to allow the user to enter two sets of address information: one for where the order is to be shipped and one for the billing information. Much more often than not, these two addresses are going to be the same; making the user enter the same information twice decreases our user-friendliness factor to less than we’d want it to be.

We could write our server-side code to assume that the billing address is the same as the shipping address if the form is left blank, but let’s assume that our product manager is a bit paranoid and would like something more overt on the part of the user.

We’ll satisfy him by adding a check box to the billing address that indicates whether the billing address is the same as the shipping address. When this box is checked, the billing address fields will be copied from the shipping fields and then made read-only. Unchecking the box will clear the value and read-only status from the fields.

Figure 7.1 shows a test form in its before and after states.

Figure 7.1. The Test Form prior to clicking the check box and after clicking the check box

The page for this test form is available in the file chapter7/test.setreadonly.html and is shown in listing 7.4.

Listing 7.4. The implementation of the form for testing the new setReadOnly() command

We won’t belabor the operation of this page, as it’s relatively straightforward. The only truly interesting aspect of this page is the click handler attached to the check box in the ready handler. When the state of the check box is changed by a click we

  1. Copy the checked state into variable same for easy reference in the remainder of the listener.
  2. Set the values of the billing address fields. If they are to be the same, we set the values from the corresponding fields in the shipping address information. If not, we clear the fields.
  3. Call the new setReadOnly() command on all input fields in the billing address container.

But, oops! We were a little sloppy with that last step. The wrapped set that we create with $('#billingAddress input') contains not only the text fields in the billing address block but the check box too. The check box element doesn’t have read-only semantics, but it can have its opacity changed—definitely not our intention!

Luckily, this sloppiness is countered by the fact that we were not sloppy when defining our plugin. Recall that we filtered out all but text fields before applying the remainder of the commands in that method. We highly recommend such attention to detail, particularly for plugins that are intended for public consumption.

What are some ways that this command could be improved? Consider making the following changes:

  • We forgot about text areas! How would you modify the code to include text areas along with the text fields?
  • The opacity levels applied to the fields in either state are hard-coded into the function. This is hardly caller-friendly. Modify the method to allow the levels to be caller-supplied.
  • Oh heck, why force the page author to accept only the ability to affect opacity? How would you modify the method to allow the page author to determine what the renditions for the fields should be in either state?

Now let’s take on an even more complex plugin.

7.4.2. Retaining state within a wrapper method

Everybody loves a slideshow!

At least on the web. Unlike hapless after-dinner guests forced to sit through a mind-numbingly endless display of badly focused vacation photos, visitors to a web slideshow can leave whenever they like without hurting anyone’s feelings!

For our more complex plugin example, we’re going to develop a jQuery command that will easily allow a page author to whip up a quick slideshow page. We’ll create a jQuery plugin, which we’ll name the Photomatic, and then we’ll whip up a test page to put it through its paces. When complete, this test page will appear as shown in figure 7.2.

Figure 7.2. The Photomatic Tester that we’ll use to put our plugin through its paces

This page sports the following components:

  • A set of thumbnail images
  • A full-sized photo of one of the images available in the thumbnail list
  • A set of buttons to move through the slideshow

The behaviors of the page are as follows:

  • Clicking any thumbnail displays the corresponding full-sized image.
  • Clicking the full-sized image displays the next image.
  • Clicking any button performs the following operations:

    • First—Displays the first image
    • Previous—Displays the previous image
    • Next—Displays the next image
    • Last—Displays the last image
  • Any operation that moves off the end of the list of images wraps back to the other end; clicking Next while on the last image displays the first image and vice versa.

We also want to grant the page authors as much freedom for layout and styling as possible; we’ll define our plugin so that the page authors can set up the elements in any manner that they would like and then tell us which page element should be used for each purpose. Furthermore, in order to give the page authors as much leeway as possible, we’ll define our plugin so that the authors can provide any wrapped set of images to serve as thumbnails. Usually, thumbnails will be gathered together as in our test page, but page authors are free to identify any image on the page as a thumbnail.

To start, let’s introduce the syntax for the Photomatic Plugin.


Command syntax: photomatic

photomatic(settings)

Instruments the wrapped set of thumbnails, as well as page elements identified in the settings hash, to operate as Photomatic controls.

Parameters

settings

(Object) An object hash that specifies the settings for the Photomatic. See table 7.1 for details.

Returns

The wrapped set.


Because we have a non-trivial number of parameters to control the operation of the Photomatic (many of which can be defaulted), we utilize an object hash to pass them in as outlined in section 7.2.3. The possible settings that we can specify are shown in table 7.1.

Table 7.1. The settings properties for the Photomatic() plugin command

Setting name

Description

firstControl

(String|Object) Either a reference to or jQuery selector that identifies the DOM element(s) to serve as a first control. If omitted, no control is instrumented.

lastControl

(String|Object) Either a reference to or jQuery selector that identifies the DOM element(s) to serve as a last control. If omitted, no control is instrumented.

nextControl

(String|Object) Either a reference to or jQuery selector that identifies the DOM element(s) to serve as a next control. If omitted, no control is instrumented.

photoElement

(String|Object) Either a reference to or jQuery selector that identifies the <img> element that’s to serve as the full-sized photo display. If omitted, defaults to the jQuery selector '#photomaticPhoto'.

previousControl

(String|Object) Either a reference to or jQuery selector that identifies the DOM element(s) to serve as a previous control. If omitted, no control is instrumented.

transformer

(Function) A function used to transform the URL of a thumbnail image into the URL of its corresponding full-sized photo image. If omitted, the default transformation substitutes all instances of thumbnail with photo in the URL.

With a nod to the notion of test-driven development, let’s create the test page for this plugin before we dive into creating the Photomatic Plugin itself. The code for this page, available in the file chapter7/photomatic/photomatic.html, is shown in listing 7.5.

Listing 7.5. The test page that creates the Photomatic display shown in figure 7.2

If that looks simpler that you thought it would, you shouldn’t be surprised at this point. By applying the principles of Unobtrusive JavaScript and by keeping all style information in an external style sheet, our markup is tidy and simple. In fact, even the on-page script has a tiny footprint, consisting of a single statement that invokes our plugin .

The HTML markup consists of a container that holds the thumbnail images , an image element (initially sourceless) to hold the full-sized photo , and a collection of buttons that will control the slideshow. Everything else is handled by our new plugin.

Let’s develop that now.

To start, let’s set out a skeleton (we’ll fill in the details as we go along). Our starting point should look rather familiar by now because it follows the same patterns we’ve been using so far.

(function($){
$.fn.photomatic = function(callerSettings) {
};
})(jQuery);

This defines our initially empty wrapper function, which (as expected from our syntax description) accepts a single hash parameter named callerSettings. First, within the body of the function, we merge these caller settings with the default settings as described by table 7.1. This will give us a single settings object that we can refer to throughout the remainder of the method.

We perform this merge operation using the following idiom (that we’ve already seen a few times):

var settings = $.extend({
photoElement: '#photomaticPhoto',
transformer: function(name) {
return name.replace(/thumbnail/,'photo'),
},
nextControl: null,
previousControl: null,
firstControl: null,
lastControl: null
}, callerSettings||{});

After the execution of this statement, the settings variable will contain the values supplied by the caller, with defaults supplied by the inline hash object. But we’re not done with settings yet. Consider the photoElement property; it might contain a string specifying a jQuery selector (either the default or one supplied by the page authors), or it could be an object reference. We want to normalize that to something we know how to deal with. By adding the statement

settings.photoElement = $(settings.photoElement);

we create a wrapped set containing the photo element (or possibly multiple elements if the page authors so chose). Now, we have something consistent that we know how to deal with.

We’re also going to need to keep track of a few things. In order to know what concepts like next image and previous image mean, we need not only a list of the thumbnail images but also an indicator that identifies the current image being displayed.

The list of thumbnail images is the wrapped set that this method is operating on—or, at least, it should be. We don’t know what the page authors collected in the wrapped set, so we want to filter it down to only image elements; we can easily do this with a jQuery selector. But where should we store the list?

We could easily create another variable to hold it, but there’s a lot to be said for keeping things corralled. Let’s store the list as another property on settings as follows:

settings.thumbnails = this.filter('img'),

Filtering the wrapped set (available via this in the method) for only image elements results in a new wrapped set (containing only <img> elements) that we store in a property of settings that we name thumbnails.

Another piece of state that we need to keep track of is the current image. We’ll keep track of that by maintaining an index into the list of thumbnails by adding another property to settings named current as follows:

settings.current = 0;

There is one more setup step that we need to take. If we’re going to keep track of which photo is current by keeping track of its index, there will be at least one case where, given a reference to a thumbnail element, we’ll need to know its index. The easiest way to handle that is to anticipate this need and to add an expando (custom property) to the thumbnail elements, recording their respective indexes. We do that with the following statement:

settings.thumbnails.each(function(n){this.index = n;});

This statement iterates through each of the thumbnail images, adding an index property to it that records its order in the list. Now that our initial state is set up, we’re ready to move on to the meat of the plugin.

Wait a minute! State? How can we expect to keep track of state in a local variable within a function that’s about to finish executing? Won’t the variable and all our settings go out of scope when the function returns?

In general that might be true, but there is one case where such a variable sticks around for longer than its usual scope—when it’s part of a closure. We’ve seen closures before, but if you’re still shaky on them, please review the appendix. You must understand closures not only for completing the implementation of the Photomatic Plugin but also when creating anything but the most trivial of plugins.

When we think about the job remaining, we realize that we need to attach a number of event listeners to the controls and elements that we’ve taken such great pains to identify to this point. And each listener we define will create a closure that includes the settings variable, so we can rest assured that, even though settings may appear to be transient, the state that it represents will stick around and be available to all the listeners that we define.

Speaking of those listeners, here’s the list of click event listeners that we need to attach to the various elements:

  • Clicking a thumbnail photo will cause its full-sized version to be displayed.
  • Clicking the full-sized photo will cause the next photo to be displayed.
  • Clicking the element defined as the previous control will cause the previous image to be displayed.
  • Clicking the next control will cause the next image to be displayed.
  • Clicking the first control will cause the first image in the list to be displayed.
  • Clicking the last control will cause the last image in the list to be displayed.

Looking over this list, we immediately note that all of these listeners have something in common; they all need to cause the full-sized photo of one of the thumbnail images to be displayed. And being the good and clever coders that we are, we want to factor out that common processing into a function so that we don’t need to repeat code over and over again.

But how?

If we were writing normal on-page JavaScript, we could define a top-level function. If we were writing object-oriented JavaScript, we might define a method on a JavaScript object. But we’re writing a jQuery plugin; where should we define implementation functions?

We don’t want to infringe on either the global or the $ namespace for a function that should only be called internally from our plugin code, so what can we do? We could define the function as a method of the plugin function itself (similar to what we did in the date formatter of listing 7.2); as first-class JavaScript objects, even functions can possess methods. But there’s an even easier way.

Recall that our plugin function is defined within the body of another function—the function that we use to ensure that the $ means jQuery. Therefore any local variables defined within that outer function become part of the closure defined by our plugin function. What if we define the implementation function, which we’ll name showPhoto(), as a local variable in the outer function?

That solves our issue nicely! Our showPhoto() function will be available to the plugin function as part of its closure, but because it’s declared locally to the outer function, it can’t be seen from outside that function and has no opportunity to pollute any other namespace.

Outside of the plugin function, but inside the outer function, we define the showPhoto() function as follows:

var showPhoto = function(index) {
settings.photoElement
.attr('src',
settings.transformer(settings.thumbnails[index].src));
settings.current = index;
};

This function, when passed the index of the thumbnail whose full-sized photo is to be displayed, uses the values in the settings object to do the following:

  1. Look up the src attribute of the thumbnail identified by index
  2. Pass that value through the transformer function to convert it from a thumbnail URL to a photo URL
  3. Assign the result of the transformation to the src attribute of the full-sized image element
  4. Record the index of the displayed photo as the new current index

But before we break our arms patting ourselves on the back for our cleverness, we should know that there’s still a problem. The showPhoto() function needs access to the settings, but that variable is defined inside the plugin function and isn’t available outside of it.

“Holy scoped closures, Batman! How will we get out of this one?”

We could be inelegant about it and pass settings to the function as another parameter, but we’re slyer than that. Because the problem is that settings isn’t defined in the outer closure containing the implementation and plugin functions the simplest solution is to move it there. Doing so won’t affect its availability to all of the inner closures, but it will ensure that it’s available to both the plugin function and the implementation function or any other implementation functions that we might want to define. (Be aware that this technique assumes that we’re only going to have one Photomatic instance on a page—a restriction that makes sense in this case. For more general cases, passing settings as a parameter would not impose this restriction.)

So we’ll add the following to the outer function:

var settings;

And remove the var from the statement within the plugin function as follows:

settings = $.extend({

The settings variable is now available to both functions, and we’re finally ready to complete the implementation of the plugin function. We define the listeners that we listed earlier with the following code:

settings.thumbnails.click(function(){ showPhoto(this.index); });
settings.photoElement.click(function(){
showPhoto((settings.current + 1) % settings.thumbnails.length);
});
$(settings.nextControl).click(function(){
showPhoto((settings.current + 1) % settings.thumbnails.length);
});
$(settings.previousControl).click(function(){
showPhoto((settings.thumbnails.length + settings.current - 1) %
settings.thumbnails.length);
});
$(settings.firstControl).click(function(){
showPhoto(0);
});
$(settings.lastControl).click(function(){
showPhoto(settings.thumbnails.length - 1);
});

Each of these listeners calls the showPhoto() function with a thumbnail index determined either by the index of the clicked thumbnail, the length of the list, or computed relative to the current index. (Note how the modulus operator is used to wrap the index values when they fall off either end of the list.)

We have two final tasks before we can declare success; we need to display the first photo in advance of any user action, and we need to return the original wrapped set so that our plugin can participate in jQuery command chains. We achieve these with

showPhoto(0);
return this;

Take a moment to do a short Victory Dance; we’re finally done!

The completed plugin code, which you’ll find in the file chapter7/photomatic/jquery.jqia.photomatic.js, is shown in listing 7.6.

Listing 7.6. The complete Photomatic Plugin implementation
(function($){
var settings;

$.fn.photomatic = function(callerSettings) {
settings = $.extend({
photoElement: '#photomaticPhoto',
transformer: function(name) {
return name.replace(/thumbnail/,'photo'),
},
nextControl: null,
previousControl: null,
firstControl: null,
lastControl: null
}, callerSettings || {});
settings.photoElement = $(settings.photoElement);
settings.thumbnails = this.filter('img'),
settings.thumbnails.each(function(n){this.index = n;});
settings.current = 0;
settings.thumbnails.click(function(){ showPhoto(this.index); });
settings.photoElement.click(function(){
showPhoto((settings.current + 1) % settings.thumbnails.length);
});
$(settings.nextControl).click(function(){
showPhoto((settings.current + 1) % settings.thumbnails.length);
});
$(settings.previousControl).click(function(){
showPhoto((settings.thumbnails.length + settings.current - 1) %
settings.thumbnails.length);
});
$(settings.firstControl).click(function(){
showPhoto(0);
});
$(settings.lastControl).click(function(){
showPhoto(settings.thumbnails.length - 1);
});
showPhoto(0);
return this;
};

var showPhoto = function(index) {
settings.photoElement
.attr('src',
settings.transformer(settings.thumbnails[index].src));
settings.current = index;
};

})(jQuery);

Boy, it seemed longer than 45 lines when we were working through it, didn’t it?

This plugin is typical of jQuery-enabled code; it packs a big wallop in some compact code. But it serves to demonstrate an important set of techniques—using closures to maintain state across the scope of a jQuery plugin and to enable the creation of private implementation functions that plugins can define and use without resorting to any namespace infringements.

You’re now primed and ready to write your own jQuery plugins. When you come up with some useful ones, consider sharing them with the rest of the jQuery community. Visit http://jquery.com/plugins/ for more information.

7.5. Summary

This chapter introduced us to how we can write code that extends jQuery.

Writing our own code as extensions to jQuery has a number of advantages. Not only does it keep our code consistent across our web application regardless of whether it’s employing jQuery APIs or our own, but it also makes all of the power of jQuery available to our own code.

Following a few naming rules helps avoid naming collisions between filenames, as well as problems that might be encountered when the $ name is reassigned by a page that will use our plugin.

Creating new utility functions is as easy as creating new function properties on $, and new wrapper methods are as easily created as properties of $.fn.

If plugin authoring intrigues you, we highly recommend that you download and comb through the code of existing plugins to see how their authors implemented their own features. You’ll see how the techniques presented in this chapter are used in a wide range of code and learn new techniques that are beyond the scope of this book.

Having yet more jQuery knowledge at our disposal, let’s move on to learning how jQuery makes incorporating Ajax into our Rich Internet Applications a no-brainer.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.147.89.85