jQuery's easy-to-use syntax led developers to begin writing scripts to achieve custom effects and other tasks. To make these scripts configurable and reusable, these developers constructed these scripts as plugins, or scripts that extend jQuery by adding new methods to the library. In this chapter, you'll learn how to add your own plugins to jQuery.
In some cases, it may be desirable to add a function directly to the jQuery
object, which means you would be able to call it like so:
$.yourFunction();
Adding functions to jQuery can help you keep your scripts organized and ensure that your function calls follow a consistent format. However, it's important to note that adding a function to jQuery does not allow you to chain it with a set of selected DOM elements; for this, you must use a method, which you'll learn how to do in this chapter.
In your first extending jQuery example, you will add the date validation function you wrote in the last chapter to the jQuery
object. Specifically, you'll leverage valid-date.js
.
One thing you should consider is allowing custom aliases in your jQuery plugins. While this isn't strictly necessary, it is strongly encouraged because it can help you keep your plugin from breaking if the $
shortcut is given up by jQuery.noConflict()
. Better still, this feature is so simple to implement that it's actually a little silly not to include it.
When building a new plugin, you should put the plugin code within a function that is executed immediately when the script is loaded. At its outset, the script should look like this:
(function(){ // plugin code here... })();
The second set of parentheses causes the preceding code to be executed immediately as a function, which is where the custom alias comes in. If you pass the jQuery
object to the second set of parentheses and the $
shortcut to the internal function, then the code will work properly with the $
shortcut, even if it's given back to the global namespace using jQuery.noConflict()
:
(function($){ // plugin code here... })(jQuery);
You could use any valid JavaScript variable name in place of the $
, and the script would still execute properly with this method:
(function(custom){ // Adds a background color to any paragraph // element using a custom alias custom("p").css("background-color","yellow"); })(jQuery);
To attach the function to jQuery, you can add the following code to valid-date.js
:
(function($){
// Extends the jQuery object to validate date strings
$.validDate = function()
{
// code here
};
})(jQuery);
Using this format, you now call the validDate()
function like this:
$.validDate();
Just as in the original validDate()
function, a date string will be passed into the function. However, to make this function more configurable, you can pass in an object containing configuration options (if necessary) to modify the regex pattern used to match the date string:
(function($){ // Extends the jQuery object to validate date strings
$.validDate = function(date, options)
{
// code here
};
})(jQuery);
The options
object will only have one property: the pattern to be used for validation. Because you want the options
object to be optional, you define a default value for the pattern in the function by inserting the following bold code:
(function($){ // Extends the jQuery object to validate date strings $.validDate = function(date, options) {// Sets up default values for the method
var defaults = {
"pattern" : /^d{4}-d{2}-d{2}sd{2}:d{2}:d{2}$/
};
}; })(jQuery);
You can extend the default
object using the $.extend()
function, which will create a new object by combining the default options with the user-supplied options. If there are three options available and the user passes an object with only two of them defined, using $.extend()
will only replace the two properties redefined by the user.
Insert the code shown in bold to extend the default
object:
(function($){ // Extends the jQuery object to validate date strings $.validDate = function(date, options) { // Sets up default values for the method var defaults = { "pattern" : /^d{4}-d{2}-d{2}sd{2}:d{2}:d{2}$/ },// Extends the defaults with user-supplied options
opts = $.extend(defaults, options);
}; })(jQuery);
This step is nearly identical to one in the original function, except you access the pattern here through the opts
object:
(function($){ // Extends the jQuery object to validate date strings $.validDate = function(date, options) { // Sets up default values for the method var defaults = { "pattern" : /^d{4}-d{2}-d{2}sd{2}:d{2}:d{2}$/ }, // Extends the defaults with user-supplied options opts = $.extend(defaults, options);// Returns true if a match is found, false otherwise
return date.match(opts.pattern)!=null;
}; })(jQuery);
Now that the file name has changed, you need to update footer.inc.php
to include it. Make the changes shown in bold to load the correct file:
<script type="text/javascript"
src="http://www.google.com/jsapi"></script>
<script type="text/javascript">
google.load("jquery", "1");
</script>
<script type="text/javascript"
src="assets/js/jquery.validDate.js"></script>
<script type="text/javascript"
src="assets/js/init.js"></script>
</body>
</html>
Finally, adjust init.js
to call the new jQuery function you've just added by making the adjustments shown in bold:
jQuery(function($){ var processFile = "assets/inc/ajax.inc.php", fx = {...} $("li a").live("click", function(event){...}); $(".admin-options form,.admin") .live("click", function(event){...}); // Edits events without reloading $(".edit-form input[type=submit]").live("click", function(event){ // Prevents the default form action from executing event.preventDefault(); // Serializes the form data for use with $.ajax() var formData = $(this).parents("form").serialize(), // Stores the value of the submit button submitVal = $(this).val(), // Determines if the event should be removed remove = false, // Saves the start date input string start = $(this).siblings("[name=event_start]").val(), // Saves the end date input string end = $(this).siblings("[name=event_end]").val(); // If this is the deletion form, appends an action if ( $(this).attr("name")=="confirm_delete" ) { // Adds necessary info to the query string formData += "&action=confirm_delete" + "&confirm_delete="+submitVal; // If the event is really being deleted, sets // a flag to remove it from the markup if ( submitVal=="Yes, Delete It" ) { remove = true; } }
// If creating/editing an event, checks for valid dates
if ( $(this).siblings("[name=action]").val()=="event_edit" )
{
if ( !$.validDate(start) || !$.validDate(end) )
{
alert("Valid dates only! (YYYY-MM-DD HH:MM:SS)");
return false;
}
}
// Sends the data to the processing file
$.ajax({
type: "POST",
url: processFile,
data: formData,
success: function(data) {
// If this is a deleted event, removes
// it from the markup
if ( remove===true )
{
fx.removeevent();
}
// Fades out the modal window
fx.boxout();
// If this is a new event, adds it to
// the calendar
if ( $("[name=event_id]").val().length==0
&& remove===false )
{
fx.addevent(data, formData);
}
},
error: function(msg) {
alert(msg);
}
});
});
$(".edit-form a:contains(cancel)")
.live("click", function(event){...});
});
After saving the preceding code, you can reload http://localhost/
and try to submit a new event with bad date values. The result is identical to the result obtained when using the original validDate()
function.
To add a chainable method to the jQuery object, you have to attach it to the fn
object of jQuery. This allows you to call the method on a set of selected elements:
$(".class").yourPlugin();
The fn
object of jQuery is actually just an alias for the jQuery object's prototype
object. Modifying the prototype of an object will affect all future instances of that object, rather than just the current instance. For more information on this, check out the brief and simple explanation of the prototype
object in JavaScript at http://www.javascriptkit.com/javatutors/proto.shtml
.
The plugin you'll build in this section will rely on a simple method to enlarge the event titles when a user hovers over them, and then return them to their original size when the user moves his mouse off the title.
This plugin will be called dateZoom
, and it will allow the user to configure the size, speed, and easing equation used for the animation.
Your first order of business when creating this plugin is to give it a name. Create a new file in the js
folder called jquery.dateZoom.js
and insert the custom alias function:
(function($){
// plugin code here
})(jQuery);
Inside this function, attach the new method to the fn
object by inserting the following bold code:
(function($){// A plugin that enlarges the text of an element when moused
// over, then returns it to its original size on mouse out
$.fn.dateZoom = function(options)
{
// code here
};
})(jQuery);
In your validDate()
plugin, the function's default options are stored in a private object. This can be undesirable, especially in instances where a user might apply the plugin method to multiple sets of elements and then want to modify the defaults for all instances.
To make default options publicly accessible, you can store them in the dateZoom
namespace. For your dateZoom
plugin, create a publicly accessible defaults
object that contains four custom properties:
fontsize
: The size to which the font will expand. Set this to 110%
by default.
easing
: The easing function to use for the animation. Set this to swing
by default.
duration
: The number of milliseconds the animation should last. Set this to 600
by default.
callback
: A function that fires upon completion of the animation. Set this to null
by default.
Now add the default options to the dateZoom
plugin by inserting the following bold code:
(function($){ // A plugin that enlarges the text of an element when moused // over, then returns it to its original size on mouse out $.fn.dateZoom = function(options) { // code here };// Defines default values for the plugin
$.fn.dateZoom.defaults = {
"fontsize" : "110%",
"easing" : "swing",
"duration" : "600",
"callback" : null
};
})(jQuery);
At this point, a user can change the defaults for all calls to the dateZoom
plugin using syntax something like this:
$.fn.dateZoom.defaults.fontsize = "120%";
To override default options, a user can pass an object with new values for one or more of the default options, as in the validDate
plugin. You can use $.extend()
to create a new object that contains values for the current invocation of the plugin when it is created.
The following bold code adds this functionality to the dateZoom
plugin:
(function($){ // A plugin that enlarges the text of an element when moused
// over, then returns it to its original size on mouse out $.fn.dateZoom = function(options) {// Only overwrites values that were explicitly passed by
// the user in options
var opts = $.extend($.fn.dateZoom.defaults, options);
// more code here
}; // Defines default values for the plugin $.fn.dateZoom.defaults = { "fontsize" : "110%", "easing" : "swing", "duration" : "600", "callback" : null }; })(jQuery);
To keep plugin methods chainable, the method must return the modified jQuery
object. Fortunately, this is easy to accomplish with jQuery: all you need to do is run the .each()
method on the this
object to iterate over each selected element, and then return the this
object.
In the dateZoom
plugin, you can make your method chainable by inserting the code shown in bold:
(function($){ // A plugin that enlarges the text of an element when moused // over, then returns it to its original size on mouse out $.fn.dateZoom = function(options) { // Only overwrites values that were explicitly passed by // the user in options var opts = $.extend($.fn.dateZoom.defaults, options);// Loops through each matched element and returns the
// modified jQuery object to maintain chainability
return this.each(function(){
// more code here
});
}; // Defines default values for the plugin $.fn.dateZoom.defaults = { "fontsize" : "110%", "easing" : "swing", "duration" : "600", "callback" : null
}; })(jQuery);
To keep your plugin code clean and organized, you will place the actual animation of the elements in a helper method called zoom
.
This method, like the defaults
object, will be publicly accessible under the dateZoom
namespace. Making this method public means that a user can potentially redefine the method before calling the plugin or even call the method outside of the plugin, if he so desires.
You create the zoom
method by inserting the following bold code into the dateZoom
plugin:
(function($){ // A plugin that enlarges the text of an element when moused // over, then returns it to its original size on mouse out $.fn.dateZoom = function(options) { // Only overwrites values that were explicitly passed by // the user in options var opts = $.extend($.fn.dateZoom.defaults, options); // Loops through each matched element and returns the // modified jQuery object to maintain chainability return this.each(function(){ // more code here }); }; // Defines default values for the plugin $.fn.dateZoom.defaults = { "fontsize" : "110%", "easing" : "swing", "duration" : "600", "callback" : null };// Defines a utility function that is available outside of the
// plugin if a user is so inclined to use it
$.fn.dateZoom.zoom = function(element, size, opts)
{
// zoom the elements
};
})(jQuery);
This method accepts the element to animate, the size to which it should be animated, and an object containing options.
You're keeping the size separate from the rest of the options because the element's original font size will be used for returning the element to its original state and that value is not available in the options
object.
Inside this method, you will use the .animate(), .dequeue()
, and .clearQueue()
methods to animate the object and prevent animation queue buildup; add the code shown in bold to accomplish this:
(function($){ // A plugin that enlarges the text of an element when moused // over, then returns it to its original size on mouse out $.fn.dateZoom = function(options) { // Only overwrites values that were explicitly passed by // the user in options var opts = $.extend($.fn.dateZoom.defaults, options); // Loops through each matched element and returns the // modified jQuery object to maintain chainability return this.each(function(){ // more code here }); }; // Defines default values for the plugin $.fn.dateZoom.defaults = { "fontsize" : "110%", "easing" : "swing", "duration" : "600", "callback" : null }; // Defines a utility function that is available outside of the // plugin if a user is so inclined to use it $.fn.dateZoom.zoom = function(element, size, opts) {$(element).animate({
"font-size" : size
},{
"duration" : opts.duration,
"easing" : opts.easing,
"complete" : opts.callback
})
.dequeue() // Prevents jumpy animation
.clearQueue(); // Ensures only one animation occurs
}; })(jQuery);
The .dequeue()
method takes the current animation out of the animation queue, preventing the animation from jumping to the end when the queue is cleared with .clearQueue()
. Allowing the queue to build up can cause the animated element to look jumpy or to perform the animation many times in rapid succession, which is definitely an undesirable effect.
Because the .each()
method accepts a callback, you can easily modify each matched element in the jQuery
object being processed. For the dateZoom
plugin, you'll add hover
event handlers to each selected element.
When a user hovers her mouse over an element to which dateZoom
has been applied, the zoom
method will run. This method relies on the fontsize
property of the defaults
object to enlarge the text appropriately. When the user stops hovering, the original text size will be passed to zoom
, and the element's text will return to its original size.
To store the original size, use the .css()
method and place the original font size in a private variable.
You use the .hover()
method to implement this functionality by inserting the following bold code into the dateZoom
plugin:
(function($){ // A plugin that enlarges the text of an element when moused // over, then returns it to its original size on mouse out $.fn.dateZoom = function(options) { // Only overwrites values that were explicitly passed by // the user in options var opts = $.extend($.fn.dateZoom.defaults, options); // Loops through each matched element and returns the // modified jQuery object to maintain chainability return this.each(function(){// Stores the original font size of the element
var originalsize = $(this).css("font-size");
// Binds functions to the hover event. The first is
// triggered when the user hovers over the element, and
// the second when the user stops hovering
$(this).hover(function(){
$.fn.dateZoom.zoom(this, opts.fontsize, opts);
},
function(){
$.fn.dateZoom.zoom(this, originalsize, opts);
});
}); };
// Defines default values for the plugin $.fn.dateZoom.defaults = { "fontsize" : "110%", "easing" : "swing", "duration" : "600", "callback" : null }; // Defines a utility function that is available outside of the // plugin if a user is so inclined to use it $.fn.dateZoom.zoom = function(element, size, opts) { $(element).animate({ "font-size" : size },{ "duration" : opts.duration, "easing" : opts.easing, "complete" : opts.callback }) .dequeue() // Prevents jumpy animation .clearQueue(); // Ensures only one animation occurs }; })(jQuery);
At this point, your plugin is ready to implement. All that remains is to include the file and select a set of elements to run it on.
To include the plugin file, you need to modify footer.inc.php
and add a new script tag. As with the validDate
plugin, the dateZoom
plugin needs to be included before init.js
, so that the method is available to be called:
<script type="text/javascript" src="http://www.google.com/jsapi"></script> <script type="text/javascript"> google.load("jquery", "1"); </script> <script type="text/javascript" src="assets/js/jquery.validDate.js"></script><script type="text/javascript"
src="assets/js/jquery.dateZoom.js"></script>
<script type="text/javascript" src="assets/js/init.js"></script>
</body> </html>
The plugin is now included in the application, so you can call the .dateZoom()
method on a set of elements. The next set of changes requires that you modify init.js
, so open that file now.
Begin by changing the default fontsize
value to 13px
, and then add the .dateZoom()
method to the chain of methods on the set of elements selected with the "li a" string. As already indicated, you implement these changes by adding modifying init.js
, as shown in the bold code that follows:
jQuery(function($){ var processFile = "assets/inc/ajax.inc.php", fx = {...}// Set a default font-size value for dateZoom
$.fn.dateZoom.defaults.fontsize = "13px";
// Pulls up events in a modal window and attaches a zoom effect
$("li a")
.dateZoom()
.live("click", function(event){
// Stops the link from loading view.php event.preventDefault(); // Adds an "active" class to the link $(this).addClass("active"); // Gets the query string from the link href var data = $(this) .attr("href") .replace(/.+??(.*)$/, "$1"), // Checks if the modal window exists and // selects it, or creates a new one modal = fx.checkmodal(); // Creates a button to close the window $("<a>") .attr("href", "#") .addClass("modal-close-btn") .html("×") .click(function(event){ // Removes event fx.boxout(event); }) .appendTo(modal);
// Loads the event data from the DB $.ajax({ type: "POST", url: processFile, data: "action=event_view&"+data, success: function(data){ // Displays event data fx.boxin(data, modal); }, error: function(msg) { alert(msg); } }); }); $(".admin-options form,.admin") .live("click", function(event){...}); // Edits events without reloading $(".edit-form input[type=submit]") .live("click", function(event){...}); $(".edit-form a:contains(cancel)") .live("click", function(event){...}); });
Save these changes, reload http://localhost/
in your browser, and then hover over an event title to see the dateZoom
plugin in action (see Figure 10-1).
You should now feel comfortable building custom plugins in jQuery, both as chainable methods and as functions. This chapter is fairly short, but that brevity stands as a testament to the ease with which you can extend the jQuery library with your own custom scripts.
Congratulations! You've now learned how to use PHP and jQuery together to build custom applications with a desktop app-like feel. Now you're ready to bring all your great ideas to life on the Web!