# JQuery: Novice to Ninja- P25

Chia sẻ: Cong Thanh | Ngày: | Loại File: PDF | Số trang:15

0
49
lượt xem
6

## JQuery: Novice to Ninja- P25

Mô tả tài liệu

JQuery: Novice to Ninja- P25:No matter what kind of ninja you are—a cooking ninja, a corporate lawyer ninja, or an actual ninja ninja—virtuosity lies in first mastering the basic tools of the trade. Once conquered, it’s then up to the full-fledged ninja to apply that knowledge in creative and inventive ways.

Chủ đề:

Bình luận(0)

Lưu

## Nội dung Text: JQuery: Novice to Ninja- P25

1. 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! Licensed to JamesCarlson@aol.com 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: 2. 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 Licensed to JamesCarlson@aol.com 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( … ); }; 3. 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); }); Licensed to JamesCarlson@aol.com 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):
4. 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 Licensed to JamesCarlson@aol.com 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! 5. 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 Licensed to JamesCarlson@aol.com 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
6. 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; Licensed to JamesCarlson@aol.com 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(); }); }); };
7. 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 Licensed to JamesCarlson@aol.com 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: 8. 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() { Licensed to JamesCarlson@aol.com 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.
9. 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 Licensed to JamesCarlson@aol.com 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);
10. 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) { Licensed to JamesCarlson@aol.com 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: 11. Plugins, Themes, and Advanced Topics 347 chapter_09/07_$_prefixed_functions/script.js (excerpt) .append('' + $.lapsed(this.created_at) + ➥'') Overwriting Existing Functionality One of the main differences between adding plugins via$.fn.myPlugin rather than $.extend({ myPlugin : … }), is that the extend mechanism augments the element you’re extending, rather than replacing it entirely. This is what makes it possible to extend jQuery itself (or existing plugins) with additional functionality. As well as adding new methods to jQuery, as we did above, we can also extend the function­ ality of existing methods! Licensed to JamesCarlson@aol.com As a simple example, we’re going to extend the functionality of the$.trim method. $.trim takes a string and removes any leading or trailing spaces. We’ll add an extra parameter to the method that, if set to true, will remove all spaces from the given string: chapter_09/08_overwriting_existing_function/script.js (excerpt) (function($) { var _trim = $.trim;$.extend({ trim:function(text, clear) { if (clear) { return text.replace(/\s+/g,''); } return _trim.call(this, text); } }); })(jQuery); First, as always, we’re playing nice for people who don’t want to use the $for jQuery (see the section called “Plugins”). Next, we’re storing the original trim function in a variable so we can access it later on. Finally, we reach the important bit: extending the trim function. Our version of trim is going to accept an extra Boolean parameter. If it’s set we run our custom regular expression to remove all whitespace, and if it’s not set, we call the original jQuery trim function. 12. 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 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 Ten basic filters, four content filters, two visibility filters, six or so attribute filters, four child filters, and 14 form-focused filters. This is more than just the makings of a wicked Christmas carol: jQuery’s built-in filters allow you to easily select just Licensed to JamesCarlson@aol.com 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? The Fold "Above the fold” in web design terms refers to the area of a page visible on page- load, without scrolling. Hence, “below the fold” refers to the total area beneath that, which is invisible without scrolling. The term originates from newspapers, which are generally folded in half 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 can definitely work in your favor. Rather than finding elements below the fold, let’s turn to a more useful purpose, and detect when an element is visible, or above the fold, on pageload. If the element is above the fold, we can load its content via Ajax straight away. If not, we can delay the Ajax request until later, saving both bandwidth and pageload time:
13. Plugins, Themes, and Advanced Topics 349 chapter_09/09_custom_selectors/script.js (excerpt) $.extend($.expr[':'], { abovethefold: function(el) { return $(el).offset().top <$(window).scrollTop() + ➥$(window).height(); } }); 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 pass in a key/value pair, where the key is the name of our filter, and the value is a function that takes a reference to an element. The Licensed to JamesCarlson@aol.com function should return true or false. If it returns true, the element tested (el) will be added to the selection. Otherwise, it will be filtered out: chapter_09/09_custom_selectors/script.js (excerpt)$('p:abovethefold').css('color', 'red'); If we test our new :abovethefold filter once the page has loaded, we’ll see that only paragraphs visible without scrolling will be turned red. The extensibility of the selector engine is a little-known feature of jQuery, and one that distinguishes the novices from the ninjas! Events The event system in jQuery is very powerful. Its primary purpose is to normalize all browser events to match the W3C standards, thus allowing us to perform cross- browser 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 Properties With a standardized event in your hands, you can rely on the features that the standards set out. So what exactly does that mean? The jQuery event wrapper gives you a lot of 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
14. 350 jQuery: Novice to Ninja many of the commonly used features throughout the book—the pageX and pageY properties, and the stopPropagation and preventDefault methods. But where 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 names so that we can easily identify them inside our debugger: chapter_09/10_event_properties/script.js (excerpt) $('p:first').click(function firstClick() { Licensed to JamesCarlson@aol.com // first click handler }) .click(function secondClick() { // second click handler }) .mouseover(function firstMouse() { // first mouseover click handler }); Should you want to access the details of those event handlers at a later point in your code, you need only call .data('events') on your paragraph: chapter_09/10_event_properties/script.js (excerpt) var events =$('p:first').data('events'); console.log(events); The data('events') call gives us back an object containing references to all of the events attached to that paragraph. You can see an output of the event data in the Firebug console (see the section called “Troubleshooting with console.log” in Chapter 4) in Figure 9.1.
15. Plugins, Themes, and Advanced Topics 351 Figure 9.1. data('events’) inspected in Firebug The event data contains both the event type and the event handlers themselves, Licensed to JamesCarlson@aol.com nested inside the object. Custom Events We saw earlier that bind and trigger do all the real work for the events that we fire; they’re behind the scenes of click, mouseover, toggle … every one of them! The shorthand actions make for shorter, more readable code, and the expanded bind and trigger syntax provides exactly the same functionality. Why use bind and trigger then? It turns out that they’re far more versatile and powerful than we’ve witnessed thus far. For starters, we can break away from browser-specified events and start creating some of our own! Creating your 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: