Showing posts with label jQuery. Show all posts
Showing posts with label jQuery. Show all posts

Wednesday, March 13, 2013

Exploring jQuery Queues

This week I ran into an interesting issue at work. There was an event that was triggered when a custom font loaded that added a fixed position element to an item in a container. Another team wrote some code that caused that container to animate when the page loaded. Sometimes the fixed position element would get placed randomly inside that container because the event was firing before the animation completed. Instead of tightly coupling the animation with the positioning of the element I decided to inspect the animating element's FX queue when the event was triggered to make sure that the element was not in the process of moving when it was added.
Queues in jQuery are a useful way to add a list functions to an element that should be executed in a particular order. The real benefit is that queues give you full control of when each item in the queue is invoked using the dequeue method. If you've used a jQuery animation function you've already used the jQuery fx queue. What a lot of people don't know is that jQuery provides a nice API for inspecting and manipulating queues.

Lets take a look at the typical use case for a jQuery queue; adding multiple animations to an element:



Demo:

JS Bin


When this code is executed each function is invoked one immediately right after the other. However, each animation is NOT performed instantly, each animation is added to the elements fx queue. When each animation is finished the next animation in the queue is invoked.

One interesting thing to note about the fx queue is that calling the stop() method on an element that is animating will only stop the current animation in the queue and immediately call the next animation in the queue. In order to truly stop all of the the animations the element's queue must be cleared as well.
In order to modify the first example to stop all animations when an element is clicked the following code is needed:



Demo:

JS Bin

Delaying And Creating Queues

Custom queues can be created on any elements by passing in a name to the queue call. In addition any queue's execution can be delayed when it is dequeued by using the delay( delayMs, queueName ) method. It's important to note that the delays are added to the queue itself so they need to be added to the queue before the function that you want delayed, however calling dequeue will trigger the delay and invoke the next callback from the queue. This timer will not start until dequeue() is called on the element's queue.

In the following example the queue's execution will be delayed by 1 second, an item will be added to the element, the queue will be delayed by another half second, another item will be appended to the element, the queue is then delayed for another second, and finally another item will be added to the element. The first item in the queue will be invoked in the $.ready() function and each callback in the queue will dequeue the next item in the queue.



Demo:

JS Bin

What makes custom queues with delays an attractive alternative to setTimeout or setInterval is that each item in the queue can have a different delay time before it is invoked.

Tuesday, September 4, 2012

jQuery Function Of The Week: Deferred (deep dive)

This week we'll take a deeper look into jQuery's Deferred object.  According to the jQuery docs:
jQuery.Deferred(), introduced in version 1.5, is a chainable utility object that can register multiple callbacks into callback queues, invoke callback queues, and relay the success or failure state of any synchronous or asynchronous function.
That is a bit of a mouthful.  I like to think of deferrds somewhat like a telephone call.  You'd like to contact someone so you dial a number.  Based on what happens when you call that number you will take action.  If the callee answers the call you can start a conversation, if you get their voice mail (I almost said answering machine but I'm afraid that some readers will have no idea what that is) you can take leave a message.  Also, when the phone is ringing you can even do something else, you only care about what happens once the number you are calling responds in some way.  This is an extremely simplified way of describing how Deferreds work because Deferrds can do so much more.

If you've used jQuery's AJAX functions you've already been taking advantage of Deferreds, the AJAX functions are actually using Deferrds behind the scenes with a lot of the details abstracted out for you.

When talking about Deferreds its important to note that there are two parts to each Deferred implementation: the callers and the callees.  The callers are the functions that either do something that other functions are waiting for or functions that take action based on the results of another function.  The callees are the functions that depend on the results of at least one of the callee functions.

The Callers

Like I mentoned before, the caller functions are divided into two parts: the functions that do something that other functions depend on, and the functions that take action based on the the results of other functions.  The when(deferrds)function is the first part of the callers and is a wrapper for all of the functions have results that other functions are waiting for.  jQuery will execute the functions passed into when immediately and asynchrouously.  This means if we have the following code:

func1(), func2(), and func()will all be called at approximately the same time.  

Functions passed into when are expected to return a promise object back to the when() function.  This promise will either be resolved or rejected (more on these in the callees section) by the function passed into when, and based on this result one of callback functions will be called.  

The second part of the callers are the functions that respond to the results of the functions passed into the when function.  The three functions are: then, done, and fail.  The then(doneCallbacks, failCallbacks) is a single method that encapsulates the functionality of the done() and fail() callbacks.  The first argument to the then function is a function, or array of functions, that will be called when all the functions passed into the when() function are resolved.  

The second argument passed into the then function is a function, or array of functions, that will be immediately invoked as soon as any of the functions passed into the when() function are rejected  The then and done functions will receive arguments from each function passed into the when function, in the order they were passed.   These arguments are passed into them via the resolve and reject methods (we will talk about when we discuss the callees).  If you'd like to be a bit more expressive in your code instead of using the then() you can specify the doneCallbacks and failCallbacks in the done() and fail() methods.  These methods are called the same was as the doneCallbacks, and failCallbacks of the then() function.

The Callees

When functions are passed into the when function jQuery expects these functions to return a promise to it.  A promise is just a message telling the when function that sometime in the future the function is either going to send it a  reject( args ) or resolve( args ) message.  Once the when function receives the promise object from a function it just sits and waits for a response.  The callee functions can also pass information back to the when function by passing arguments into the reject and resolve methods (I'll show you an example of this in the real world example).  Lets take a look at a typical callee function that implements the Deferred object and talk a bit more about what is going on:

Here we have a function that is going to implement the $.Deferred() function and immediately return a promise message to the when() function that called it.  The $.Deferred() constructor  function takes an optional function as an argument which is passed a reference to itself as an argument.    The above function could also have been rewritten as: 

Its important to note that if a function passed into when does not implement this Deferred pattern it will immediately be treated as resolved.

Real World Example

Now that we've looked at the parts of the Deferred lets take a look at a real world example.  In order to demonstrate the power of Deferred's lets take a look at some code that can benefit from them:
JS Bin

The intent of this code is to fade out any current content, get some tweets from twitters search API, and finally display the new tweets.  When any of the buttons are pressed you'll quickly see that something isn't right, and this is because the code being called relies on asynchronous operations like doing an AJAX call and jQuery animations.

One approach to fix this would be to put the getTweets function as a callback to the cleanDiv function's fadeOut callback, move the addTweets function to the ajax calls success callback, and finally move the the $tweetDiv.fadeIn('slow') code to the addTweets function. Doing so would tightly couple this code together making it tough to extend and maintain.  A better solution would to use the power of jQuery's Deferred object.

First lets look at our click handler function rewritten to take advantage of the Deferred object:



The first thing you probably notice is that there is more code in the new click handler.  The Deferred calls do add a bit of code, but I feel this code is quite expressive (meaning that the intent of the code is very clear) and each of the callback functions are decoupled from any function they are waiting on.  One other thing to note here is the use of the done() and fail() callbacks.  In the done() callback I use the when() and then() methods of the Deferred implementation.  Here I am only specifying a  single argument which will get called if/when the addTweets function passed into it has completed.  Since I'm not going to reject the promise anywhere in the addTweets function only passing in a done callback is sufficient.

Now lets take a look at one of the new callee functions.  Here is the code for the new getTweets function:



Since there is no access to the internal representation of the $.ajax method's Deferred function I have to wrap the entire function in a Deferred call.  Another thing to note is that Twitter's API will return a 200 status (which will invoke the ajax function's success callback) even if there is an error so  I will inspect the object returned from Twitter when determining weather to resolve or reject the Deferred object.  If a key of "error" is found in the data returned from Twitter I reject the Deferred which tells our click handler function to run the fail callback.  I'll also pass a message to the fail callback through the reject method call.


Putting it all together we get a nice and smooth implementation of our application.  As you can see I've also included an error button which will trigger an error in the twitter API.

JS Bin

Tuesday, August 14, 2012

jQuery Function of the Week: grep

This week's jQuery function of the week is grep.  According to the jQuery docs grep:
Finds the elements of an array which satisfy a filter function. The original array is not affected.
There is only a single signature for the grep function: $.grep(array, function(element, index) [,invert]). The function being passed in should return a truthy value or a falsy value based on the input argument.  If the invert parameter is left out, or is false then the grep function will return a new array with all the items that the satisfy the truth test function that was passed in.  If invert is set to true, grep will return a new array with all the items that do not satisfy the truth test function.

Lets take a look at looping through an array of text and removing the word "the":

JS Bin demo

Now lets take a look at the same code, but with the invert parameter set to true:

JS Bin demo

You'll see that only the word "the" is returned.

Check out the all of the jQuery function's of the week.

Thursday, August 9, 2012

jQuery Function Of The Week: nextAll

I'm super busy this week so this week's jQuery function of the week is going to be an easy one: nextAll.  According to the jQuery Documentation use nextAll to:
Get all following siblings of each element in the set of matched elements, optionally filtered by a selector.
Lets say we had a list of items, and we wanted to highlight every vegetable  Using nextAll would look like this:

JS Bin demo

Optionally a selector can be passed into nextAll to further filter the results.  Here we'll only select the even vegetables that are after our original selector:

JS Bin demo

Wednesday, August 1, 2012

jQuery Function Of The Week: proxy

In my JavaScript fundamentals presentations I have a section that talks about scope and how it can become confusing when using jQuery.  The reason for this is becuase jQuery normalizes the this scope in its function calls.  In other words when inside a jQuery method call this usually refers to the DOM elements in the jQuery set.

This can be clearly illustraed in a simple jQuery event callback (note use your browsers console, not jsbin's for the meantime):

JS Bin demo

This can become a problem when we try to write reusable code. Lets say we want to put our event handler into a object and then call it when a user clicks on a link:

JS Bin Demo

If you run the code you'll see that you get "Hello, undefined" instead of "Hello, Homer Simpson". The reason is simple, jQuery used the link as the context of this where no myName property exists. Enter jQuery.proxy.

According to the jQuery Documentation, the proxy function:
Takes a function and returns a new one that will always have a particular context.
This means that by using the $.proxy function we can make our callback run in the proper context, in this case itself:

JS Bin Demo

The change is subtle, but the result is exactly what we want.   Now, at this point you might be asking yourself, "isn't this the same thing as using the apply() or call() methods (mdn apply documentation)?   The answer is yes...and no.  Lets look at how to get the desired result using apply():

JS Bin Demo

If you look closely you'll notice a slight difference, the callback is now invoked in an anonymous function instead of passed directly to the on() method.  This is because the apply method invokes the function immediately whereas jQuery's proxy function only returns a reference function for later invocation.   This is a bit nicer because it makes for slightly more terse code.

In addition the proxy() function does a few more things:

  • it makes sure that the function passed in is actually a function
  • it assignes a unique ID to the function.  This is very important because when trying to unbind a function you may not be unbinding the proper function if you are passing in the function to the off() method.  Its best to always use namespaced events when using on() and off() with $.proxy().

Friday, July 27, 2012

Solving JavaScript Async Issues the Easy Way, With Events

By now we all know that JavaScript is asyncronous and what that means, right?  If not the following code will help explain the concept in 30 seconds:


This code will run function a() then b(), even if the ajax request in a() has not yet completed.

There are a lot of approaches that can be used to make sure that b only runs when a is done.  One approach would be to use jQuery deferreds like so:


We can also use a async library, such as q, if we need a bit more power.  Or we can use JavaScript's setTimeout(func, delay) function to continuously check for a variable the function a() sets when it is done before we call b(), how awful does that sound? In some cases there is a quite simple way to achieve this, by using events.  I might even argue that using events is the more JavaScripty way to do things in the right situation.  Lets take a look at the a/b example, this time using events:


Lets take take a look at a real world example of using events to make sure things happen in the proper order.  Here we will use a backbone collection as a wrapper for a one or more Contact objects that will be reused in multiple places in an application:


Let say we want to use this collection to populate a drop down when a view is initialized like so:


Using this code there is a good chance that the models will be an empty array when we try to add them to the dropdown.  Also, when the collection's fetch() completes the app.contacts.models array will be reset, meaning that the reference to the contacts in your view likely will not reference the current app.contacts.models array.

To fix this, lets first update our collection to trigger an event when the contacts are loaded.  In this example I'm going to use jQuery's trigger(eventName) function to keep things easy:


The last step is to make sure we are adding the carriers to the drop down after they have been added to the collection. To do this we just need to add an event listener to our initialize function:



Now we will always be working with a populated collection in our addContacts method.

Monday, July 23, 2012

jQuery Function Of The Week: one

jQuery's one function is an extremely useful function when you want an event to fire exactly one time.   According to the official jQuery documentation use one to, "Attach a handler to an event for the elements. The handler is executed at most once per element"  One is a much more terse way of setting an event handler and calling unbind() or off() in the handler function.

The easiest use of one is to pass it an event and a handler like so:



Click Here to Demo on JS Bin

You can click the link as many times as you wish, however the event will only fire once.

You can pass in more than one event handler to the one function by passing in a list of events separated by a space.  When you do this each event will fire once.  In this example the event will fire 3 times, one time for each event:



Click Here to Demo on JS Bin

You can also  pass in custom data to the event handler, the data comes attached to the event.data object like so:



Click Here to Demo on JS Bin

One additional thing you can do with the one function is use an additional selector to pass into the one event that will attach the event to exactly one of the descendants of the matched set. In the following example click on any of the li elements, then click on another, you'll see the event only fires for the first element selected.



Click Here to Demo on JS Bin

Check out the all of the jQuery function's of the week.


Fork me on GitHub