550 likes | 658 Views
jQuery: New Features and APIs. 4-25-2012. Contents. Introduction Deferred The New Event API. Introduction. Bio. Rob Larsen.
E N D
jQuery: New Features and APIs 4-25-2012
Contents • Introduction • Deferred • The New Event API
Bio Rob Larsen Rob Larsen has more than 12 years’ experience as a front end engineer and team leader, building web sites and applications for some of the world’s biggest brands. He is currently a Senior Specialist, Platform at Sapient Global Markets. Rob is an active writer and speaker on web technology with a special focus on emerging standards like HTML5, CSS3 and the ongoing evolution of JavaScript. He’s written one book, Professional jQuery (WROX), and is working on two more. github: github.com/roblarsen. Twitter: @robreact Blog @ HTML + CSS + JavaScript http://htmlcssjavascript.com/
But… No. I’m not a complete jQuery fanboy. • http://keithnorm-jschi.heroku.com/#4
That guy works at Groupon, btw • He could probably buy John outright
#dumbjquerystuff • I don’t always agree with what they do. And this isn’t just API nitpicks • Killing $.tmpl() without a completed replacement • Killing the entire plugin site without a replacement in place
I’m Still Skeptical • I was basically forced into using it every day only a couple of years ago • Stylistically, there are things I dislike about jQuery • Everything is an anonymous function. • No code reuse • Tightly coupled application logic • Monolithic callbacks • I don’t believe less code or less characters always === better code • Sometimes it’s great, but, not always. • A prime example is the $(function(){}) shortcut for $(document).ready(function(){}) One is clear, especially for beginners.
Still, The New Stuff is Shiny. • And New. • Based on the code I see day-to-day, “new” is roughly defined as 1.5+ • A couple of these are major and will make you better at your job • Let’s look at what “new” means
Significant New Features of jQuery 1.5+ • 1.5 • Rewrite of $.ajax() • Introduction of $.deferred() • 1.6 • attr() (use for actual HTML attributes) and prop() (use for DOM properties) • This broke stuff • $.holdReady( ) • Manage the $.ready() event manually. • 1.7 • $.on() and $.off() • The one Event API to rule them all. • innerShivized versions of $.html(), etc. • HTML5 magic
Two Stand Out (For Me, At Least) • Let’s look at some of this stuff in depth
Defined • $.Deferred, introduced in version 1.5, is a chainable utility object that provides fine-tuned control over the way callback functions are handled. • For example, instead of merely having a single success method for your $.ajax calls, you can now define multiple callbacks that can be combined with precision and flexibility. • No more: • Monolithic success blocks success : function(){ //10,000 lines later } • Unmaintainable function calls buried at the end of other function calls success : function(){ //10,000 lines later doSomethingElse(); }
Promises/Promises • Basically defined, a promise is a proxy for the result of an action that will happen at an unspecified time in the future. • jQuery’s specific implementation is based on the Promises/A proposal from CommonJS. var promise = ajaxPromise() { //XHR request //state is set to fulfilled, unfulfilled, or failed //depending on the result of the request return state }; function unfulfilled(){ //handle negative result } function fulfilled() { //handle successful result } fucntion failed(){ //Error! } promise.then( unfulfilled, fulfilled, failed );
$.Deferred() • jQuery’s implementation adds several useful enhancements. • Importantly, it integrates directly with $.ajax • The format of the implementation is heavy on the ability to chain methods • It’s also written in a friendly, human-readable style.
$.when, Deferred.done, and Deferred.fail • $.when accepts either one or more Deferred objects (which importantly include $.ajax calls) or a plain object. • If a Deferred is passed to $.when, its promise object is returned by the method. • Deferred.done accepts a single function or array of functions to fire when a promise is resolved successfully. • Deferred.fail also accepts a function or array of functions. It fires when a promise is rejected.
$.when, Deferred.done, and Deferred.fail function fib() { return $.Deferred(function() { var int1 = 0, int2 = 1, int3, sequence = "<li>0</li><li>1</li>"; for (var i = 3; i <= 100; i++) { int3 = int1 + int2; int1 = int2; int2 = int3; sequence += "<li>" + int3 + "</li>" } $("#numbers") .append(sequence) .show(1000, this.resolve); }).promise(); } function success() { $( "h1" ).text( "Fibonacci!" ); } function failure() { $ ("h1" ).text( "No numbers?" ); } $(function(){ $.when( fib() ) .done( success ) .fail( failure ); }); http://jsfiddle.net/5qAMD/
So? • Although this is a simple example, it illustrates the power of the Deferred pattern. Callbacks in jQuery are a common source of tight coupling. They’re often defined in place, as anonymous functions with too little attention paid to abstraction.
Decoupling for fun and profit • Without Deferreds, callback logic for animations is tied directly to the animation call as an optional complete argument.
Traditionally… function updateStatus() { var $update = $("<ul />"), $statusbar = $("#statusbar"), html = []; // text buffer for ( var i = 0, test = 20; i < test; i++ ) { html.push( "<li>status update</li>"); } html = html.join("\n"); // buffer -> string $update.append(html); $statusbar.append($update); $statusbar.slideDown(5000, function() { console.log("animation is done! On to the next operation"); }); } $(function(){ updateStatus(); }); http://jsfiddle.net/robreact/EuecW/
Decoupling for fun and profit • Though there’s a simple console.log in the complete argument in this example, far more complicated application data is commonly packed into similar complete callbacks. • This might be acceptable if updateStatus will only ever be used with the anonymous callback function. • Alternatively, to solve this, you could add a callback function as an argument to updateStatus, but using Deferred and returning a resolved promise is far more flexible. • The following code sample shows how updateStatus could be rewritten to leverage Deferreds:
Rewritten Using Deferred function updateStatus() { return $.Deferred(function() { var $update = $("<ul />"), $statusbar = $("#statusbar"), html = []; // text buffer for ( var i = 0, test = 20; i < test; i++ ) { html.push( "<li>status update</li>"); } html = html.join("\n"); // buffer -> string $update.append(html); $statusbar.append($update); $statusbar.slideDown(1000, this.resolve); }).promise() } http://jsfiddle.net/robreact/MFU29/1/
Decoupling for fun and profit • With just the simple application of a Deferred, updateStatus is now much more flexible. • Instead of being tied to a single callback, you can now use that same piece of code in several different flows. • The following example contains three callback functions. • The first is equivalent to the original anonymous function in the original example. • The second, alternativeCallback, represents a different path to follow after the animation completes. • If the same animation is running on a second page, a different piece of markup will have to be updated. • The third, happyBirthday, represents a function to be called in a special case where a specific UI update would be required, a “HAPPY BIRTHDAY” message indicator, for example. • Three uses of $.when follow. The first replicates the original case with a single callback function. The second shows the alternative callback. The third shows multiple callbacks.
Profit! function callback(){ console.log("The animation is done. On to the next operation"); } function alternativeCallback(){ console.log("The animation is done. Let's follow a different path."); } function specialCase(){ console.log("This is a special case. Let's do a special UI update."); } $.when( updateStatus() ) .done( callback ); //an alternative callback $.when( updateStatus() ) .done( alternativeCallback ); //multiple callbacks $.when( updateStatus() ) .done( [ callback, specialCase ] ); http://jsfiddle.net/robreact/MFU29/1/
Decoupling for fun and profit • As you can see, the code nicely decoupled the callback logic from the animation buried in the function body. • No one even needs to read updateStatus to know how the application flows. It’s all laid out in (nearly) plain English.
Deferred.then, Syntactic Sugar for Deferred.fail() and Deferred.done • As with aliases for $.ajax like $.get and $.post, jQuery provides a convenient alias for the core Deferred objects: Deferred.then. • Deferred.then accepts two arguments, one for resolved promises and the other for rejected promises. • Like the functions it maps to, Deferred.done and Deferred.fail, the arguments for Deferred.then can either be individual functions or an array of functions.
$.Deferred.then() function fib() { return $.Deferred(function() { var int1 = 0, int2 = 1, int3, sequence = "<li>0</li><li>1</li>"; for (var i = 3; i <= 100; i++) { int3 = int1 + int2; int1 = int2; int2 = int3; sequence += "<li>" + int3 + "</li>" } $("#numbers") .append(sequence) .show(1000, this.resolve); }).promise(); } function success() { $( "h1" ).text( "Fibonacci!" ); } function failure() { $ ("h1" ).text( "No numbers?" ); } $(function(){ $.when( fib() ) .then( success, failure ); }); http://jsfiddle.net/robreact/gjzct/
Using the Deferred Aspects of $.ajax • As was previously mentioned, $.ajax is a Deferred object • the success, error, and complete callback methods are analogous to Deferred.done, Deferred.fail, and Deferred.always
A typical ajax request $.get("/status/json/", function( data ) { var $update = $( "<ul />" ), $statusbar = $( "#statusbar" ), html = "", statusMessages = data.statusMessages; for ( var i = 0, test = statusMessages.length; i < test; i++ ) { html += "<li>" + status[i] + "</li>"; } $update.append(html); $statusbar.append($update); $statusbar.slideDown(1000); } );
Using the Deferred Aspects of $.ajax • Let’s leverage Deferred • the arguments passed to updateStatus are the same that would be passed to the callback function of a typical Ajax request. • the data, • a text string indicating request status • the jQuery XMLHttpRequest, (jqXHR) object • an enhanced version of the standard XMLHttpRequest object.
A Deferred ajax request function getStatus() { return $.ajax({ url : "/status/json/", dataType : “json” }) } function updateStatus( data ) { var $update = $( "<ul />" ), $statusbar = $( "#statusbar" ), html = "", statusMessages = data.statusMessages; for ( var i = 0, test = statusMessages.length; i < test; i++ ) { html += "<li>" + statusMessages[i] + "</li>"; } $update.append( html ); $statusbar.append( $update ); $statusbar.slideDown( 1000 ); } $.when( getStatus() ) .done( updateStatus );
Using the Deferred Aspects of $.ajax • note that once again the callback logic is decoupled from a single Ajax request • getStatus function becomes more flexible • multiple $.ajax requests can be passed into $.when • multiple callbacks can be chained
Execute a Function No Matter What the Promise Resolution Is with Deferred.always • Oftentimes you want a function to be called no matter what the promise resolution is. • use Deferred.always (this is just like the complete callback) • The following code shows a simplified example that changes the “last updated” indicator in a mail application.
Always! $.when( $.ajax( "/get/mail/" ) ).done( newMessages, updateMessageList, updateUnreadIndicator ).fail( noMessages ).always( function() { var date = new Date(); $( "#lastUpdated" ).html( "<strong>Folder Updated</strong>: " + date.toDateString() + " at " + date.toTimeString() ); } ); http://jsfiddle.net/SegWH/
Chaining and Filtering Deferreds with Deferred.pipe • Deferred.pipe, introduced in jQuery 1.6, provides a method to filter and further chain Deferreds. • The following code shows a simplified example that uses a chained animation used to manage the build out of the components of a web application. • Once the animation queue is complete, the promise is automatically resolved and the done method is fired.
Deferred.pipe function buildpage() { return $.Deferred(function( dfd ) { dfd.pipe(function() { return $( 'header' ).fadeIn(); }) .pipe(function() { return $( '#main' ).fadeIn(); }) .pipe(function() { return $( 'footer' ).fadeIn(); }) }).resolve(); } $.when( buildpage() ) .done(function() { console.log(‘done’) } ); http://jsfiddle.net/robreact/MQyqY/
Filtering Deferreds with Deferred.pipe • Deferred.pipe can filter Deferreds based on secondary criteria. • Deferred.pipe accepts three arguments: a doneFilter, a failFilter, and a progressFilter • Typical use case is a service that returns 200 with an empty result set. It will never get to the error callback.
Deferred.pipe $.when( $.ajax( "/get/mail/" ) ).pipe( function( data ) { if ( data.messages.length > 0 ) { return data } else { return $.Deferred().reject(); } } ).done( newMessages, updateMessageList, updateUnreadIndicator ).fail( noMessages ).always( function() { var date = new Date(); $("#lastUpdated").html("<strong>Folder Updated</strong>: " + date.toDateString() + " at " + date.toTimeString() ); } );
Resolving and Rejecting Promises • You occasionally need to manually resolve or reject promises. • Deferred.resolve and Deferred.reject. • optional args argument • passed to the Deferred.done or Deferred.fail methods depending on the resolution of the promise • The following example shows a simplified illustration of these optional arguments. • Returning to the mail example, the code snippet creates an updated date string to represent the time of the mail update. • This string is passed along to the Deferred.fail and Deferred.done methods. Additionally, the number of messages is passed to Deferred.done to update the #message paragraph with the current number of new messages retrieved.
Reject and Resolve Manually function newMessages( obj ) { $( "#message" ).text("you updated at " + obj.date + " and have " + obj.number + " new messages" ) } function noMessages( obj ) { $( "#message" ).text("you updated at " + obj.date + " and have no new messages" ) } $.when( $.ajax( "/get/mail/" ) ).pipe( function( data ) { var date = new Date(); date = date.toDateString() + " at " + date.toTimeString(); if ( data.messages.length > 0 ) { return $.Deferred().resolve({ date: date, number: data.messages.length }); } else { return $.Deferred().reject({ date: date }); } } ).done( newMessages ).fail( noMessages ); http://jsfiddle.net/VrLah/
The new event API Clearing up a big, confusing mess
JQuery’s New Event API • Starting with version 1.7 jQuery has made an effort to simplify the API for setting events. • While you’ll still be able to .bind() and .unbind(), .live() and .die() and set .click() and .blur() the old way, there’s now a unified API which bundles all the various functionalities under one, consistent API. • The two new methods are .on() and .off(). • Moving forward these two new methods represent the preferred API • The beauty of .on() is that it replaces the functionality of all the existing event methods with a single, flexible API • The following code sample shows a basic example of .on(), using it to bind a click event to an element already present in the DOM. • This would compare to directly using .bind(“click”) or .click()
Basic $.on() <html> <head> <script type ="text/javascript" src="http://code.jquery.com/jquery-1.7.1.js"></script> <script type="text/javascript"> $(function(){ $("#aDiv").on('click', function(){ console.log("Handler 1"); }); $("#aDiv").on('click', function(){ console.log("Handler 2"); }); }); </script> </head> <body> <div id="aDiv" class="boxDiv">Press Me </div> </body> </html>
JQuery’s New Event API • Beyond that basic example, on() also exposes the same functionality present in .delegate() and .live() and does so with the exact same syntax as the basic on() example. • To use .on() like delegate() or .live(), simply add a second, optional selector argument representing the target element for the event. • Here on() is used to listen for clicks on the document and fire the provided handler function if the click originates from an element with the id #anchor. • Since the listener is set on the document, it will work whenever and wherever the #anchor appears.
$.on() as $.live() <html> <head> <script src="http://code.jquery.com/jquery-1.7.1.js"></script> <script> $(function(){ // element doesn’t exist yet, but // we can create a handler anyways $(document).on("click", "#anchor", function(event){ console.log("I have a handler"); }); $("body").append("<a id='anchor'> Anchor </a>") }); </script> </head> <body> </body> </html>
JQuery’s New Event API • Using on() like delegate() uses the exact same syntax as the live() replacement in the previous example. • This illustrates one of the benefits of the new, simplified events API. • Instead of remembering two separate methods and their associated arguments, you simply remember the one on() method and adjust your arguments accordingly. • If you truly need to bind events to generic elements across the whole page, you just use .on() on the document and pass in your generic p or a selector. • If, you want to follow the more performant delegate pattern, you simply apply .on() to a more focused selector
$.on() as $.delegate() <html> <head> <script type="text/javascript" src="http://code.jquery.com/jquery-1.7.1.js"></script> <script type="text/javascript"> $(function(){ $("#delegate").on("click", "p" , function(){ console.log('ouch'); }).css("color", "green"); }); </script> </head> <body> <div id="delegate"> <p>Hit Me!</p> </div> </body> </html>
JQuery’s New Event API • Removing events is as simple as reversing the process with .off(), the same way you would use .unbind() or .die() The following code shows how to remove the events set in the previous examples. • $( "#delegate“ ).off( "click", "p“ ); • $( document ).off( "click", "#anchor“ ); • $( "#aDiv“ ).off( 'click‘ );