Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống
1
/ 15 trang
THÔNG TIN TÀI LIỆU
Thông tin cơ bản
Định dạng
Số trang
15
Dung lượng
405,34 KB
Nội dung
Licensed to JamesCarlson@aol.com Plugins, Themes, and Advanced Topics 337 chapter_09/01_plugins/script.js (excerpt) $('p') .hide() .highlightOnce() .slideDown(); It’s quite exciting: our functionality is captured in a chainable, reusable plugin that we’ve nestled in between the hide and slideDown actions. Seeing how 11 lines of code was all that was required (and six of those are stock-standard plugin scaffold- ing!), you can see it’s worth turning any functionality you intend on reusing into a plugin! Adding Options jQuery plugins are an excellent way to produce reusable code, but to be truly useful, our plugins need to be applicable outside the context for which we created them: they need to be customizable. We can add user-specified options to our plugins, which can then be used to modify the plugin’s behavior when it’s put to use. We’re familiar with how options work from a plugin user’s perspective, as we’ve passed in options to just about every plugin we’ve used throughout the book. Options let us modify the plugin’s functionality in both subtle and more obvious ways, so that it can be used in as wide a range of situations as we can imagine. There are two types of plugin options: simple values and object literals. Let’s start with the simpler one to see how this works. For our highlightOnce plugin, it seems quite limiting to have the color hard-coded. We’d like to give developers the choice to highlight their elements in any color they’d like. Let’s make that an option: chapter_09/02_plugin_options/jquery.highlightonce.js (excerpt) $.fn.highlightOnce = function(color) { ⋮ $(this).css('background-color', color || '#fff47f') ⋮ }; The plugin can be called with a color, but can also be called without parameters—in which case a default value will be used (thanks to the JavaScript || operator). Let’s highlight our paragraphs in green: Licensed to JamesCarlson@aol.com Licensed to JamesCarlson@aol.com 338 jQuery: Novice to Ninja chapter_09/02_plugin_options/script.js (excerpt) $('p') .hide() .highlightOnce('green') .slideDown(); If you have one or two simple options that are always required, this approach is fine. But when your plugins start to become more complicated, you’ll end up with numerous settings, and your users will want to override some and keep the default values for others. This is where we turn to the more complex object literal notation. It’s not scary—you already know how to define this type of settings object. We’ve used them for animate, css, and most jQuery UI components. The key/value object has the benefit of only one parameter needing to be defined, in which users will be able to specify multiple settings. Our first step is to set up default values for each option: chapter_09/03_plugin_options_with_defaults/jquery.highlightonce.js (excerpt) $.fn.highlightOnce.defaults = { color : '#fff47f', duration : 'fast' }; We now have the defaults as an object, so we need to make use of the jQuery $.extend function. This handy function has several uses, but for our purposes, we’ll use it to extend an object by adding all of the properties from another object. This way, we can extend the options the user passes in with our default settings: the plugin will have values specified for every option already, and if the user specifies one of them, the default will be overridden. Perfect! Let’s look at the code: chapter_09/03_plugin_options_with_defaults/jquery.highlightonce.js (excerpt) $.fn.highlightOnce = function(options) { options = $.extend($.fn.highlightOnce.defaults, options); return this.each( … ); }; Licensed to JamesCarlson@aol.com Licensed to JamesCarlson@aol.com Plugins, Themes, and Advanced Topics 339 Our options variable contains the correct settings inside it—whether they’ve been defined by the user, or by the default object. Now we can use the settings in our code: chapter_09/03_plugin_options_with_defaults/jquery.highlightonce.js (excerpt) $(this) .data('original-color', $(this).css('background-color')) .css('background-color', options.color) .one('mouseenter', function() { $(this).animate({ 'background-color': $(this).data('original-color') }, options.duration); }); As the plugin user, we can specify the color or duration of the highlight—or accept the defaults. In the following example we’ll accept the default color, but override the duration to be 2,000 milliseconds rather than “fast”: chapter_09/03_plugin_options_with_defaults/script.js (excerpt) $('p') .hide() .highlightOnce({color: '#C0FFEE', duration: 2000}) .slideDown(); Adding Callbacks You have seen how callback functions and events can be very useful. Many of the effects and controls throughout the book have relied on them—and many of the plugins we’ve used have given us access to callbacks to customize their functionality. Callbacks are a mechanism for giving your plugin’s users a place to run their own code, based on events occurring inside your plugin. Generally you’ll have a fairly good idea of what events you’d like to expose to your users. For our highlightOnce plugin, for example, we might want to run additional code when the effect is set up, when the effect concludes, and perhaps when the fade-out commences. To demonstrate, let’s try exposing a setup event (which will run after the mouseover handlers are attached), and a complete event (which will run after the final animate action concludes): Licensed to JamesCarlson@aol.com Licensed to JamesCarlson@aol.com 340 jQuery: Novice to Ninja chapter_09/04_plugin_callbacks/jquery.highlightonce.js (excerpt) $.fn.highlightOnce.defaults = { color : '#fff47f', duration : 'fast', setup : null, complete: null }; The callback functions shouldn’t do anything by default, so we’ll set them to null. When the time comes to run the callbacks, there are a few possible ways of proceed- ing. If our callback needs to run in the place of a jQuery callback, we can simply provide the function passed in by our users to the jQuery action. Otherwise, we’ll need to call the function manually at the appropriate location: chapter_09/04_plugin_callbacks/jquery.highlightonce.js (excerpt) $(this) .data('original-color', $(this).css('background-color')) .css('background-color', options.color) .one('mouseenter', function() { $(this).animate( {'background-color': $(this).data('original-color')}, options.duration, options.complete ); }); // Fire the setUp callback $.isFunction(options.setup) && options.setup.call(this); Above we can see both types of callbacks. The complete callback handler is easy: the effect is completed when the animate action is finished, and the animate action accepts a callback function itself, so we just pass the function along. No such luck with the setup handler, though—we’ll have to fire that one ourselves. We turn to jQuery and a dash of advanced JavaScript to execute the code. First, we check to see if the callback is a function with the handy $.isFunction jQuery action that returns a Boolean value: true if the callback is a function, false if it’s not. If it’s the latter (which is most likely because the user left the defaults as they were, in which case it will still be null), there’s no point trying to execute it! Licensed to JamesCarlson@aol.com Licensed to JamesCarlson@aol.com Plugins, Themes, and Advanced Topics 341 More Utility Functions In addition to $.isFunction, jQuery also provides the following functions: $.isArray (for testing if a variable is an array), $.isPlainObject (for simple JavaScript objects), and $.isEmptyObject (for an object that has no properties). These functions provide you with a number of ways to ascertain the nature and properties of a JavaScript construct. If the callback has been defined, we need to run it. There are several ways to run a JavaScript function, and the easiest is to just call it: options.setup(). This will run fine, but the problem is that it’s called in the scope of the default object, instead of in the scope of the event’s target element (as we’re used to). So the callback function would be unable to determine which DOM element it was dealing with. To remedy this, we use the JavaScript method call. The first parameter you pass to call will override this in the method you’re calling. In our example, we pass in this, which is the DOM element we want. With the scope corrected, we can now use $(this) inside the complete event handler to slide up the element once the effect is done and dusted: chapter_09/04_plugin_callbacks/script.js (excerpt) $('p') .hide() .highlightOnce({ color: '#FFA86F', complete: function() { $(this).slideUp(); } }) .slideDown(); jQuery-style Callback You might have noticed that the jQuery callbacks seem better integrated than our plugin’s named events. For example, in the hide action, you can specify the callback function as either the first (and only) parameter, or you can include the speed parameter and then the callback function. It’s unnecessary to include a key/value pair as we did above. What’s the secret? It turns out to be a little bit of a JavaScript Licensed to JamesCarlson@aol.com Licensed to JamesCarlson@aol.com 342 jQuery: Novice to Ninja hack. If you detect that the first parameter is a function, rather than an object, you can assume that only a callback has been specified, so you shift the parameters over. Here’s a (truncated) example from the jQuery core library, part of the Ajax load action. The params parameter is optional, so if it isn’t supplied the second parameter is assumed to be the callback: load: function( url, params, callback ){ // If the second parameter is a function if ( jQuery.isFunction( params ) ){ // We assume that it's the callback callback = params; params = null; The params parameter is supposed to be an object filled with various settings. But if we detect that it’s actually a function, we assign the function to the callback variable and clear the params variable. It’s a cool trick and a good way to make your plugins feel more jQuery-ish. Let’s modify our highlightOnce plugin to use this callback detection trick: chapter_09/05_jquery_style_callbacks/jquery.highlightonce.js (excerpt) $.fn.highlightOnce = function(options, callback) { if ($.isFunction(options)) { callback = options; options = null; } options = $.extend($.fn.highlightOnce.defaults,options); return this.each(function() { // Do something to each item $(this) .data('original-color', $(this).css('background-color')) .css('background-color', options.color) .one('mouseenter', function() { $(this).css('background-color', ''); $.isFunction(callback) && callback(); }); }); }; Licensed to JamesCarlson@aol.com Licensed to JamesCarlson@aol.com Plugins, Themes, and Advanced Topics 343 Advanced Topics Eventually the thrill of creating plugins will wear off a smidgen, and you’ll start to wonder if there are any other gems hidden in jQuery’s underbelly. And you’d be right to wonder. In addition to the fantastic plugin architecture we’ve just explored, jQuery provides a mechanism for extending and overwriting its core functionality, and a flexible event system for defining and fine-tuning how your components re- spond to your users—and to other components. Extending jQuery Plugins are not the only jQuery mechanisms for code reuse and extensibility; at your disposal is a system to add plugin-like functionality, as well as customize and override elements of the core jQuery system on the fly. If you have a chunk of code you’d like to execute in a native jQuery fashion, you can extend jQuery with your new functionality without creating a plugin, directly from inside your script. This way you can fine-tune your code to more closely fit the jQuery feel, and fine-tune jQuery to suit your exact requirements. Adding Methods to jQuery Sometimes sections of the code you’re writing are such a pivotal part of your applic- ation that you find yourself using them over and over again. When this happens, you may have found a candidate for extending jQuery. Hidden away in the plugins section of the jQuery core library, the extend method is normally the domain of the plugin developer. But don’t let that stop you! jQuery.fn.extend(), or $.fn.extend(), accepts an object that allows us to provide a new set of methods to extend jQuery—adding new actions that can be performed on jQuery selections. This is closely linked to jQuery.extend(), which extends the jQuery object itself. The net result is exactly the same as the plugins we wrote earlier. Generally you’ll use the extend method when you have a group of small related methods you want to add, or when you want to override some existing functionality (we’ll look at that shortly). So let’s take a look at some code we’ve already created and see how it evolves using extend. Back in Chapter 8 we looked at sorting lists: Licensed to JamesCarlson@aol.com Licensed to JamesCarlson@aol.com 344 jQuery: Novice to Ninja var SORTER = {}; SORTER.sort = function(which) { // Sort the selected list } Having to call our widgets like this lacks that jQuery feel. So we’ll convert the reverse method to integrate it more closely with jQuery, using extend: chapter_09/06_extending_jquery/script.js (excerpt) $.fn.extend({ sorter: function() { return this.each(function() { var sorted = $(this).children('li').sort(function(a,b) { // Find the list items and sort them return $(a).text().toLowerCase() > ➥$(b).text().toLowerCase() ? 1 : -1; }); $(this).append(sorted); }); } }); Inside the new sorter and reverser methods, we return the results of running the functions from our original example against each member of the selection on which we called the action. This return structure allows us to use the action in a chain: chapter_09/06_extending_jquery/script.js (excerpt) $('#ascending').click(function() { $('ul.sortable') .hide() .sorter() .slideDown(); }); The biggest change from our original SORTER widget is that once the extend is in place, we no longer call the functions and pass in parameters; instead, we treat it like a normal jQuery action, much as we would had we packaged it into a plugin. Of course, you could take this same code and make it into a plugin! Doing it on the fly like this is just another option available to you. Licensed to JamesCarlson@aol.com Licensed to JamesCarlson@aol.com Plugins, Themes, and Advanced Topics 345 $. Prefixed Functions At the beginning of the book we praised jQuery for being a very consistent library: each time we call upon it we have a selector, an action, and perhaps a few paramet- ers. Once you learned that basic pattern, it would be essentially all you had to re- member. Then, only a few paragraphs later, we snuck in some code that didn’t use a selector at all! Since then, we’ve seen several of these kinds of actions: $.ajax, $.map, $.slice, $.trim, and more. The $. prefixed functions don’t work on a selection; rather, they’re general utility commands that can have a place outside a jQuery command chain. For example, $.map can be used to map any arbitrary array—but it can also be used to modify a jQuery selection. The client liked the Ajax Twitter-search component we developed in Chapter 6, but wanted a more human-readable time displayed for the tweets: for example, rather than “56 seconds ago,” he’d like it to say “about a minute ago.” This sounds like a good candidate for adding to the jQuery object, since it’s the sort of function- ality you’d like to be able to use outside of a selection. Any time we need a friendly looking time period, we’d like to be able to call $.lapsed(time) and obtain a nice string. To get underway, we’ll omit the actual logic for a second while we look at the function’s structure. The skeleton looks very similar to the plugins we created —passing the jQuery object to the wrapped function—so our code will work even if the $ alias has been redefined: chapter_09/07_$_prefixed_functions/script.js (excerpt) (function($) { $.lapsed = function(time) { var then = new Date(Date.parse(time)); var now = new Date(); var minutes = Math.round((now-then) / 60000); var lapsed; // Determine pretty time ⋮ }; })(jQuery); Licensed to JamesCarlson@aol.com Licensed to JamesCarlson@aol.com 346 jQuery: Novice to Ninja Now we just need to attach our function to the jQuery object itself. Because we’re extending jQuery, you’ll have to be careful to avoid unintentionally overwriting any of the built-in function names. The code we’ll use to determine the “pretty” time is nothing more than a series of if/else structures. To keep the example simple, our helper will only go as far as “more than a minute ago”—but you’ll want to extend this to account for larger time spans: chapter_09/07_$_prefixed_functions/script.js (excerpt) // Determine pretty time if (minutes == 0) { var seconds = Math.round((now - then) / 1000); if (seconds < 10) { lapsed = "less than 10 seconds ago"; } else if (seconds < 20) { lapsed = "less than 20 seconds ago"; } else { lapsed = "half a minute ago"; } } else if (minutes == 1) { var seconds = Math.round((now-then) / 1000); if (seconds == 30) { lapsed = "half a minute ago"; } else if (seconds < 60) { lapsed = "less than a minute ago"; } else { lapsed = "1 minute ago"; } } else { lapsed = "more than a minute ago"; } return lapsed; To use this extension in our jQuery code, we pass a timestamp to the new lapsed function, and append the result wherever we need it. Our functionality is neatly encapsulated, and ready for use on any number of future projects: Licensed to JamesCarlson@aol.com [...]... properties and events to play with, though there’s too many to list here (the full list is in the section called “Events” in Appendix A) You’ve already seen Licensed to JamesCarlson@aol.com Licensed to JamesCarlson@aol.com We’ve already looked at $.extend a few times, so accessing the selector engine should be quite familiar The $.expr[:] object is what we need to extend in order to add our custom filters We... own events helps to make your code clearer; for instance, a function buried in a click handler needs to be analyzed to determine its purpose, whereas an event with a specific name might be easier to comprehend at a glance Let’s have a look at creating a custom do-toggle event To do so, we’ll go all the way back to the disclaimer message example we saw in Chapter 2: chapter_09/11_custom_events/index.html... (excerpt) Disclaimer! This service is not intended … This time, instead of the button being responsible for toggling the disclaimer, the disclaimer will be responsible for toggling itself: Licensed to JamesCarlson@aol.com Licensed to JamesCarlson@aol.com The event data contains both the event type and the event handlers... Its primary purpose is to normalize all browser events to match the W3C standards, thus allowing us to perform crossbrowser event handling with relative ease—but that’s just the tip of the iceberg A lot of thought has gone into the internal event system in the core library, and this functionality has also been exposed to those developers who are looking to do more than just react to simple events Event... As well as adding new methods to jQuery, as we did above, we can also extend the function ality of existing methods! 348 jQuery: Novice to Ninja Extending code in this way is particularly useful when dealing with more complex objects; for example, if you need to modify the $.ajax method to do some additional processing on every request In that situation you’d only want to modify the partic ular aspects... partic ular aspects of the code that you needed to, and leave everything else alone By storing the original function, and referring to it as necessary, this becomes a simple task Create Your Own Selectors The Fold "Above the fold” in web design terms refers to the area of a page visible on pageload, without scrolling Hence, “below the fold” refers to the total area beneath that, which is invisible without... for dis play It was therefore considered advantageous to have a headline or advertisement appear above the fold, visible to anyone passing by In particular, important stories would be positioned here in order to attract attention and entice people to buy the paper These are admittedly odd requests, but let’s use the opportunity to examine custom filters in detail; besides, in certain situations they... do those events you assign actually exist? How does a lowly paragraph tag know to react when it’s clicked? jQuery gives you a way to see what a particular element is set up to do by storing the events, using the data action under the key 'events' To see this in action, we’ve set up a paragraph tag and attached three events to it: two separate click events, and one mouseover event We’ve given the functions... allow you to easily select just about anything on the page! But what do we do when we want all the advertisement units in a page, or all the links that were clicked to leave a page, or all the break-out items that are below the fold? Plugins, Themes, and Advanced Topics 349 chapter_09/09_custom_selectors/script.js (excerpt) $.extend($.expr[':'], { abovethefold: function(el) { return $(el).offset().top . be used (thanks to the JavaScript || operator). Let’s highlight our paragraphs in green: Licensed to JamesCarlson@aol.com Licensed to JamesCarlson@aol.com 338 jQuery: Novice to Ninja chapter_09/02_plugin_options/script.js. concludes): Licensed to JamesCarlson@aol.com Licensed to JamesCarlson@aol.com 340 jQuery: Novice to Ninja chapter_09/04_plugin_callbacks/jquery.highlightonce.js (excerpt) $.fn.highlightOnce.defaults. time ⋮ }; })(jQuery); Licensed to JamesCarlson@aol.com Licensed to JamesCarlson@aol.com 346 jQuery: Novice to Ninja Now we just need to attach our function to the jQuery object itself. Because