RSS Feed: Brandon Aaron

Mostly about jQuery

RSS Feed Feed download link: http://feeds.feedburner.com/BrandonAaron

  • Event Delegation with jQuery, published on Thursday, 04 March 2010 12:06AM

    jQuery makes implementing event delegation quick and easy! In version 1.4.2 there are now three ways to utilize event delegation: .live(), .delegate(), and do-it-yourself. Each technique progressively gives you a little more control and flexibility. Lets take a look at these three techniques with a quick 101 crash course on event delegation first.

    Event Delegation 101

    At its core event delegation is all about letting events bubble up the DOM tree to a parent element. This provides several advantages such as only binding one event handler instead of potentially 100s and it works with elements currently in the DOM at runtime and those which are injected after runtime.

    Imagine you have a large table of data and you want to do something when the user clicks on a row. You might first start out by binding a click event to each <tr> which would look like this.

    $('tr').bind('click', function(event) {
        // this == tr element
    });
    

    If you have lots and lots of table rows it could take a while to bind all those events. Not to mention the browser now needs to keep track of all those event handlers. Instead, we can use event delegation by binding the click event to the table (or any parent element, maybe even the body) and letting the event bubble up to the table. Then we can inspect the event to see which element was actually clicked on. jQuery either does this for you or makes this part easy and we’ll look at how to do this in just a moment.

    The second reason you might want to use event delegation is for automatically handling dynamic data. Lets say you needed to dynamically add rows to your table. Well, then you’d have to also bind the click event to those new rows. With event delegation the event is actually bound to the table element and any new rows do not need a new event bound. Awesome!

    .live()

    The .live() method, added in jQuery 1.3, provides the most simple way to implement event delegation and is suitable for simple scenarios. Here is some example code illustrating a click event being captured for all <tr> elements (old or newly created) on a page.

    $('tr').live('click', function(event) {
        // this == tr element
    });
    

    You can see this example code in action here.

    Notice, I said “page”. The .live() method binds events to the document by default. You can actually change this, in jQuery 1.4, by passing in a new context to jQuery. If you are unfamiliar with the context in jQuery, I recommend reading this blog post: Understanding the Context in jQuery.

    One of the gotchas of this method is that it isn’t exactly chainable like other jQuery methods. For example if you use a jQuery method that changes the selection of elements, such as .children() or .parent(), before calling .live() then it will not work. In other words, it works best when only used with the given selector. The following code example doesn’t work.

    $('#myContainer').children('table').find('tr').live('click', function(event) {
        /*** does not work! ***/
    });
    

    You might expect that the previous example would still handle click events for <tr> elements. However, it isn’t going to work at all because of the .children() method. So, .live() is a simple method for simple scenarios.

    To stop .live() events, you’d use the .die() method.

    .delegate()

    The .delegate() method was introduced in jQuery 1.4.2 and provides a more focused way of doing your event delegation. Keeping with the table example lets look how we can do it with the .delegate() method.

    $('table').delegate('tr', 'click', function(event) {
        // this == tr element
    });
    

    You can see this example code in action here.

    First off, unlike .live() events the event handler is actually bound to the selected element (“table” in this case). Then the click event is filtered to only fire when the <tr> was clicked on. This is a great method as it makes it easy to specify (and understand) which element you want to delegate from and which elements to filter the event on.

    This method also clears up the confusion with regards to the chainability that the .live() method introduced. Here is the example code that didn’t work with .live() but does work with delegate.

    $(#myContainer').children('table').delegate('tr', 'click', function(event) {
        // this == tr element
    });
    

    Again, the reason this works and .live() doesn’t is because .delegate() is actually binding the event to the selected parent and then filtering based on the selector passed to the .delegate() method.

    To stop delegated events, you’d use the .undelegate() method.

    Do-It-Yourself

    The last technique is the ability to just do-it-yourself. This is for advanced use-cases where you want to have more flexibility than .delegate() or .live(). Using a method called .closest(), which was introduced in jQuery 1.3, we are going to look at the event.target (which element the event happened on) and see if it or any of its parents are the element we want to filter the event on. Keeping with the table example our code looks like this.

    $('table').bind('click', function(event) {
        // this == table element
        var $tr = $(event.target).closest('tr');
    });
    

    You can see this example code in action here.

    As you can see we are just using the good’ol .bind() method to bind our click event to the table element directly. Then when the table is clicked we check the event.target to see if it is the <tr> or if one of its parents is the <tr>. If neither of these cases are true then $tr is an empty jQuery collection.

    Summary

    Use .live() if you just need to add some quick event delegation or handle events on elements that don’t yet exist in the DOM. Beware though that you shouldn’t chain .live() and by default it binds event handlers to the document element.

    Use .delegate() when you want a little more control and want to chain method calls.

    Use the do-it-yourself technique when you want ultimate control and flexibility with your event delegation.

    Which do you prefer and why?

    http://brandonaaron.net/blog/2010/03/4/event-delegation-with-jquery
  • Special Events: The Changes in 1.4.2, published on Thursday, 25 February 2010 4:55AM

    jQuery’s event system has seen incredible feature growth over the years and with that growth came some pains. jQuery 1.4.2 included a much needed overhaul of the event system. This also brought some backwards incompatible changes to the two new special event hooks added in 1.4. I previously blogged about these two new special event hooks: add and remove. These two events hooks brought lots of power by being able to manipulate the event details for each event handler registered. This is different from the existing setup and teardown hooks (added in 1.2.2) that only worked once per an event, per an element.

    I’ve written about Special Events a few times now. I recommend going back and reading the previous posts for more details about Special Events, if you haven’t already done so. First there was the introductory blog post, then the post about the add and remove hooks, and finally a post about automating with special events.

    The Changes

    First lets rundown the changes which at first may seem more complicated than they actually are. It all has to do with how jQuery internally stores the details related to an event per an element (.data('events')). Since the beginning jQuery stored event details on the handler function itself in an object. Now the event details are stored in an object within an array. This provided several benefits, not the least of these being guaranteed event order in all browsers (which has been a pretty highly desired feature). However, the add and remove callback now get the object with the event details passed instead of the individual arguments as before.

    Lets take a look at the two call signatures to make this a little more clear. I’m going to use the same example that I used in the previous blog post about these new event handlers. I’m only showing the add and remove hooks since the setup and teardown are unmodified.

    // In jQuery 1.4 and 1.4.1
    jQuery.event.special.multiclick = {
        add: function( handler, data, namespaces ) {
            // called for each bound handler
        },
    
        remove: function( namespaces ) {
            // called for each bound handler
        }
    };
    
    
    // In jQuery 1.4.2+
    jQuery.event.special.multiclick = {
        add: function( details ) {
            var handler   = details.handler,
                data      = details.data,
                namespace = details.namespace;
        },
    
        remove: function( details ) {
            var handler   = details.handler,
                data      = details.data,
                namespace = details.namespace;
        }
    };
    

    The Updated multiclick Special Event

    If you remember the multiclick event, it had the ability to be configurable per a handler. The usage looks like this.

    $('div')
        .bind("multiclick", { threshold: 5 }, function( event ) {
            alert( "Clicked 5 times" );
        })
        .bind("multiclick", { threshold: 3 }, function( event ) {
            alert( "Clicked 3 times" );
        });
    

    The threshold passed in the data for the event handler is how many times the element must be clicked before the event handler fires. Most of the functionality for this special event was in the add hook. It would replace the handler with a handler that would track the number of clicks. Here is what the add hook for this event looked like before.

    jQuery.event.special.multiclick = {
        add: function( handler, data, namespaces ) {
            // get the required number of clicks from data
            var threshold = data && data.threshold || 1,
                // number of clicks
                clicks = 0;
    
            // return a new function that will become the handler
            return function( event ) {
                // increase number of clicks
                clicks += 1;
                if ( clicks === threshold ) {
                    // required number of clicks reached, reset
                    clicks = 0;
                    // call the actual supplied handler
                    handler.apply( this, arguments );
                }
            }
        },
    
        ...
    };
    

    And here is how it looks in 1.4.2.

    jQuery.event.special.multiclick = {
        add: function( details ) {
            var handler   = details.handler,
                data      = details.data,
                threshold = data && data.threshold || 1,
                clicks    = 0;
    
            // replace the handler
            details.handler = function(event) {
                // increase number of clicks
                clicks += 1;
                if ( clicks === threshold ) {
                  // required number of clicks reached, reset
                  clicks = 0;
                  // call the actual supplied handler
                  handler.apply( this, arguments );
                }
            };
        },
    
        ...
    };
    

    The rest of the special event remains the same. Feel free to take a look at the example which uses 1.4.2.

    Support 1.4+

    If you need to support 1.4, 1.4.1, and the new way in 1.4.2 then here is some sample code to help you make the transition. This sample code was written by Ben Alman (whom I hear is working on an uber Special Events article).

    // 1.4, 1.4.1 and 1.4.2 (existing plugins, update for 1.4.2 this way)
    $.event.special.foo = {
    
      add: function( handleObj ) {
        // Do something when event is bound here!
    
        var old_handler;
    
        function new_handler(event) {
          // Modify event object here!
          old_handler.apply( this, arguments );
        };
    
        // This may seem a little complicated, but it normalizes the special event
        // .add method between jQuery 1.4/1.4.1 and 1.4.2+
        if ( $.isFunction( handleObj ) ) {
          // 1.4, 1.4.1
          old_handler = handleObj;
          return new_handler;
        } else {
          // 1.4.2+
          old_handler = handleObj.handler;
          handleObj.handler = new_handler;
        }
      }
    
    };
    
    http://brandonaaron.net/blog/2010/02/25/special-events-the-changes-in-1-4-2
  • Connecting the Dots with Web Workers, published on Thursday, 18 February 2010 8:16AM

    A little while back I sat down to experiment with Web Workers and decided I would write a zero-sum game. I choose Connect 4 as the game I’d use to explore Web Workers with. It helped that I found an algorithm written by Keith Pomakis in C. First things first, I ported the algorithm to JavaScript and then started my experimentation with Web Workers. I’d like to share a couple of patterns that helped me when working with Web Workers and then explain a little bit about how I organized the code for the Connect4.js game.

    Feel free to go ahead and take a look at the demo of the Connect4.js game. It is a demo of the Connect 4 game with the computer playing against itself. I also provide a simple Web Worker example to base your own projects on.

    Why Web Workers?

    Essentially Web Workers allow us to offload heavy computations from the main thread. That means our heavy computations will not lock up the browser. In the Connect4.js game the computer will attempt to find the best move by looking ahead several moves (always assuming the other player will make the best move possible). These computations are heavy and time consuming and would normally give the dreaded long running script alert. However, with Web Workers we can just tell our worker to find the best move and let us know when it is done.

    Web Worker support is currently quite limited. However, its potential power warrants investigation now, rather than later! The browsers currently supporting Web Workers are: Firefox 3.5+, Safari 4+, and Chrome. I recommend using Modernizr to detect support for modern features such as Web Workers.

    How do they work?

    A Web Worker is just another JavaScript file that is loaded and executed in a sandbox off the main thread. It has very limited access and can only pass strings. Although, Firefox can pass a JSON object and it will convert it to JSON internally. Lookout for this area of the API to fluctuate some. To create a new Web Worker you just create a new instance of a Worker and pass the JavaScript file it should load.

    // create a new worker
    var myWorker = new Worker('myworker.js');
    

    The API for communicating with a Web Worker is super simple. There are basically two primary methods used. One for sending messages called postMessage and one for receiving messages called onmessage. Using the myWorker from above we can communicate with it like this.

    // Event handler for receiving messages from the worker
    myWorker.onmessage = function(event) {
        var data = event.data;
        // do something with the data
    };
    
    // method used to send a message to the worker
    myWorker.postMessage('my message');
    

    The worker actually uses the exact same methods itself for communication. So our worker.js file might look something like this.

    // Event handler for receiving messages from the host
    onmessage = function(event) {
        var data = event.data;
        // do something with the data
        // ...
        // send a result back to the host
        postMessage('42');
    };
    

    For more information about how Web Workers work, I invite you to take a look at the MDC page Using Web Workers.

    Speak JSON

    It just so happens that the browsers that support Web Workers also have native support for JSON. This is great as it allows us to communicate with the worker using JSON. And that makes it easier for our worker to know which task to execute and with what data. We can use the following JSON format to communicate with our worker.

    // data to send a message to the worker
    JSON.stringify({
        action: 'sum', // the action to perform
        args: [1, 2]   // the arguments to pass along
    });
    
    // data to send back the result from the worker
    JSON.stringify({
        action: 'sum',  // the action performed
        returnValue: 3  // the result of the action
    });
    

    Our worker might look something like this in order to handle the multiple actions and JSON. Albeit with a rather trivial action for the example.

    // available actions to run within the worker
    var actions = {
        sum: function(a, b) {
            return a + b;
        }
        // other actions
    };
    
    // handle a new request from the host
    onmessage = function(event) {
        var data   = JSON.parse(event.data), // parse the data
            action = data.action,            // get the requested action
            args   = data.args,              // get the arguments for the action
            result = { action: action };     // prepare the result
    
        // if we understand the action
        if (action in actions) {
            // execute the action and set the returnValue
            result.returnValue = actions[action].apply(this, args);
        } else {
            // otherwise set the returnValue to undefined
            result.returnValue = undefined;
        }
    
        // send back the results as a JSON string
        postMessage(JSON.stringify(result));
    };
    

    Logging from the Worker

    Since the worker executes in a very limited environment it does not have access to the console and cannot log messages. However, we can simply add a new action called log that the host can then use to log the returnValue. Lets assume our worker sends us a log action via the following JSON.

    // a message to log
    JSON.stringify({
        action: 'log',
        returnValue: 'the message to log'
    });
    

    Now we can handle the log action just like any other action and just console.log the data. In the last section we saw what the worker should look like to handle the multiple actions and JSON but now lets look at what the host should look like to handle the multiple actions and JSON.

    // avaialble actions to run from the worker
    var actions = {
        sum: function(result) {
            // do something with the result
            // maybe publish a custom event
            // or fire a callback
        },
        log: function(message) {
            console.log(message);
        }
    };
    
    // create the worker
    var myWorker = new Worker('myworker.js');
    
    // handle a response from the worker
    myWorker.onmessage = function(event) {
        var data        = JSON.parse(event.data), // parse the data
            action      = data.action,            // get the action
            returnValue = data.returnValue;       // get the returnValue
    
        // if we understand the action
        if (action in actions) {
            // handle the returnValue for the action
            actions[action].call(this, returnValue);
        } else {
            // throw an error? our worker isn't communicating properly
        }
    };
    

    Quick Recap

    So, hopefully that wasn’t to confusing. Basically, you have a worker that you pass a message to and it should do something with the data and pass a message back to you with the result. We can utilize the JSON format to make interacting with our workers a bit more manageable. Although the example is rather trivial hopefully it provides the structure and information to make it easier to jump start your own web worker project. I’ve gone ahead and built out the above worker so you can try it for yourself.

    Connect4.js

    Lets dissect the Connect4.js game to see how it uses Web Workers. The Connect4.js game is actually composed of three different JavaScript files.

    • Connect4.js - This is the public facing API and behind the scenes uses the patterns described above to communicate with the worker.
    • Connect4Worker.js - This is the actual worker file and provides a light-weight interface to the actual game logic.
    • Connect4Game.js - This is the actual game logic and is responsible for computing moves and keeping the game state. It is loaded via importScript from within Connect4Worker.js.

    Lets first look at the Connect4Worker.js. Here it is line by line with some comments.

    // this method imports another script into the current context
    importScripts('Connect4Game.js');
    
    // create a local var to store the game instance
    var connect4;
    
    onmessage = function(event) {
        var data   = JSON.parse(event.data), // parse the data
            action = data.action,            // get the action
            args   = data.args,              // get the arguments for the action
            ret    = { action: action };     // prepare the result
    
        if (action === 'new') {
            // create a new instance 
            connect4 = new Connect4Game(args[0]);
            ret.returnValue = connect4;
        }
        else if (connect4[action]) {
            // perform the action and return the results
            ret.returnValue = connect4[action].apply(connect4, args);
        }
        else {
            // undefined action
            ret.returnValue = { error: 'No method for requested action: ' + action };
        }
    
        // send the message back
        postMessage(JSON.stringify(ret));
    };
    

    As you can see it follows the same pattern as described earlier for communicating with the worker. One of the differences is that the actions are actually methods of the connect4 instance.

    For the actual public facing interface I use a simple publish and subscribe pattern (pubsub) to handle the asynchronous behavior. So when I get a response from the worker I publish an event for that particular action. This makes it pretty easy to interact with. Using pubsub I can subscribe multiple functions to the same event. In the demo I use two events: moveend and gameend. On moveend I perform 3 actions. First I visually draw the move in the browser, then I tell the computer to make the next move, and finally if the console is available I log information about the move.

    Lets take a look at a slimmed down version of the game.js file from the demo with some comments.

    // create a new game and make the ai a level 5
    // the callback is called once the game is all setup
    new Connect4({ ai: 5 }, function(game) {
        // draw the move to the screen
        game.subscribe('moveend', drawMove);
        // play the next move
        game.subscribe('moveend', makeNextMove);
        // handle when the game is over
        game.subscribe('gameend', gameOver);
    
        if ('console' in window) {
            // log some information about the move
            game.subscribe('moveend', logMove);
            // log any debug info
            game.subscribe('debug', debug);
        }
    
        // make first move
        // autoMove makes the best possible
        // move for the specified player
        // where player 0 is 1 and 1 is 2
        game.autoMove(0);
    
    
        function drawMove(player, col, row) { /*...*/ }
        function logMove(player, col, row) { /*...*/ }
        function makeNextMove(player, col, row) { /*...*/ }
        function gameOver() { /*...*/ }
        function debug(obj) { /*...*/ }
    });
    

    In the above code when I call game.automove(0) it is sending a message (via postMessage) to the Connect4Worker.js and the Connect4Worker.js is then asking the Connect4Game.js to run the automove method. Once it is finished computing the best move it will send a message back the same way it came with the results of the move. The Connect4Worker.js always passes along the game state as well which the public facing API is then kept in sync.

    More Distributed

    There is nothing keeping you from using more than one worker and spawning new workers from within workers. One of the ways I was thinking that the performance could be enhanced is to use multiple workers when computing the best move. This way each potential move could be calculated in parallel if the computer had the resources. It would add some complication to the organization and code structure but might be worth the performance gains especially with a higher AI level.

    The Demos

    As mentioned earlier the simple worker I created can be viewed in the browser. Also, you can see the demo of the Connect4.js game in action or you can download and/or fork the code which is on github.

    http://brandonaaron.net/blog/2010/02/18/connecting-the-dots-with-web-workers
  • Understanding the Context in jQuery, published on Wednesday, 24 June 2009 7:20PM

    When selecting elements jQuery has an optional second argument called context. This context provides a means to limit the search within a specific node. This is great when you have a very large DOM tree and need to find, for example, all the <a> tags within a specific part of the DOM tree. I’ve seen various articles, usually performance related, stating that you should be using a context for your selectors. While it is true that using a context for your selectors can potentially boost performance, most of these articles misuse the context.

    Finding the Context

    As of jQuery 1.3 the context is exposed as a property on the jQuery collection. Here is how you can check to see what the context is.

    $('a').context; // => document
    

    Running the above code shows that the context is the document element. In fact, the default context for all jQuery collections is the document in which jQuery was loaded. In other words, selectors are run against the whole document by default.

    Changing the Context

    The context needs to be a node to work properly. This is the part that is often overlooked. I’ve overlooked it in the past as well. Lots of examples out there tell you to just pass a second selector to jQuery to act as the reference node for the search. While this seems to work, it is still running the search on the whole document.

    Here is an example of passing a second selector as the context. By looking at the context property you can see that the context is still the document though.

    $('a', '#myContainer').context; // => document
    

    When jQuery encounters another selector for the context it actually converts it to say the following instead.

    $('#myContainer').find('a');
    

    This conversion also happens the same way if you pass a jQuery collection as the context.

    Now lets actually look at how we can change the context for the jQuery collection.

    // get the node for the context
    var context = $('#myContainer')[0];
    
    // pass the context as the second argument
    $('a', context).context; // => <div id="myContainer">
    

    From this example we can see that passing a node as the second argument actually changes the context for the jQuery collection.

    jQuery 1.3.3 and .live()

    In jQuery 1.3.x, the .live() method binds its event handlers to the document. In the upcoming jQuery 1.3.3 release the .live() method will bind its events to the context of the jQuery collection. This means your .live() events can be more focused and potentially faster.

    http://brandonaaron.net/blog/2009/06/24/understanding-the-context-in-jquery
  • Automating with Special Events, published on Wednesday, 17 June 2009 3:44PM

    David Walsh had a great idea to coerce an element that is given a click event to have a pointer for its cursor. The pointer is a universal symbol for users to know that something is clickable. Using this technique it would be “automatic” that any element given a click event would have a pointer. David set out to use the Special Event hooks within jQuery to make this work. Unfortunately for David those hooks are not widely documented, so his first attempt was unsuccessful. So, lets see how we can use the Special Event hooks to make this work as desired.

    If you haven’t done so, I recommend reading the Special Events blog post which explains what special events are and how to use them. In the upcoming jQuery 1.3.31.4 there will be two more event hooks.

    The Special Event

    The code, once you understand the Special Event hooks, is fairly straight forward. When a click event is bound to an element, we want to make sure it has a pointer for its cursor. Here is what the code looks like to make this work.

    jQuery.event.special.click = {
        setup: function() {
            jQuery( this ).css( 'cursor', 'pointer' );
            return false;
        },
    
        teardown: fuction() {
            jQuery( this ).css( 'cursor', '' );
            return false;
        }
    };
    

    So lets break this small piece of code down. There are two hooks, setup and teardown, that are called once per an element. In the setup hook we add the CSS rule to make the cursor a pointer and in the teardown hook we revert the change since it is no longer clickable. The teardown hook is only called after the last click event is unbound.

    The only difference here from David’s example is the return false statements in both hooks. The return value in Special Event hooks is particularly important (and easy to overlook). Special Events assume that you will most likely handle the actual binding of the event yourself. In other words, when you don’t return false jQuery skips the actual native binding (addEventListner or attachEvent) of the event to the element. In this particular case we want jQuery to go ahead and run its native binding code so the event is actually registered with the browser. To do this we need to return false from our hooks.

    Usage

    Using the same example from David’s post, the actual usage stays the same. Just bind a click event handler as you normally would using either .bind() or .click().

    jQuery( document ).ready(function( $ ) {
        $( '#click-me' ).click(function() {
            var red   = Math.floor( Math.random() * 256 ),
                green = Math.floor( Math.random() * 256 ),
                blue  = Math.floor( Math.random() * 256 ),
                color = 'rgb(' + red + ',' + green + ',' + blue + ')';
            $( this ).css( 'background', color );
        });
    });
    

    You can see this in action here.

    Enhancement

    There is one enhancement that I’d like to recommend. Instead of adding the styles via JavaScript, lets just add a class name instead. Maybe the name could be “clickable”. Then we can add a rule in our CSS that says anything with the class of “clickable” should have a pointer. This would make it easier to style specific instances of a clickable element. For example, we could add a three pixel blue border to a clickable image. :) So, the final special event with this enhancement would look like this.

    jQuery.event.special.click = {
        setup: function() {
            jQuery( this ).addClass( 'clickable' );
            return false;
        },
    
        teardown: fuction() {
            jQuery( this ).removeClass( 'clickable' );
            return false;
        }
    };
    

    And in our CSS we would need to add a global rule that adds the pointer to .clickable elements.

    .clickable { cursor: pointer; }
    

    Now we can specialize our .clickable class to handle certain elements differently.

    img.clickable { border: 3px solid blue; cursor: pointer; }
    

    Last thought…

    I had one last thought about this technique. This should not be a replacement for using semantic markup or using progressive enhancement. Most of the time if something should be clickable it should probably be clickable and actually do something when JavaScript is not available.

    http://brandonaaron.net/blog/2009/06/17/automating-with-special-events
  • jQuery Edge: New Special Event Hooks, published on Thursday, 04 June 2009 1:05AM

    These Special Event hooks were changed in 1.4.2. Check out this blog post to see the details about the change.

    In jQuery 1.3.3 1.4 there are two new special event hooks: add and remove. These two hooks, unlike setup and teardown, are called for each event being bound. The add hook receives the handler, data, and namespaces as arguments. The remove hook receives the data and namespaces as arguments. The add and remove hooks enable the creation of more complex, even customizable, events.

    If you haven’t done so, I recommend reading the Special Events blog post which explains what special events are and how to use them. For the following example I assume you already understand the concept of special events in jQuery.

    As an example I’m going to build a new special event called “multiclick”. This event tracks clicks on an element and fires at a given threshold or number of clicks. The required number of clicks will be customizable per a handler.

    First, here is how you’d use the multiclick event.

    $('div')
        .bind("multiclick", { threshold: 5 }, function( event ) {
            alert( "Clicked 5 times" );
        })
        .bind("multiclick", { threshold: 3 }, function( event ) {
            alert( "Clicked 3 times" );
        });
    

    Now lets build it. The skeleton of the multiclick special event looks like this.

    jQuery.event.special.multiclick = {
        add: function( handler, data, namespaces ) {
            // called for each bound handler
        },
    
        setup: function( data, namespaces ) {
            // called once per an element
        },
    
        remove: function( namespaces ) {
            // called for each bound handler
        },
    
        teardown: function( namespaces ) {
            // called once per an element
        },
    
        handler: function( event ) {
    
        }
    };
    

    Behind the scenes we’ll need to use a normal click event. We’ll use the setup hook to bind the initial click event to track the actual clicks. The handler method will be used to properly trigger the multiclick events. Once all the multiclick events are removed, the teardown hook will be triggered. We’ll need to unbind the the click event we used to track the clicks in the setup hook. Here is the updated multiclick event.

    jQuery.event.special.multiclick = {
        add: function( handler, data, namespaces ) {
            // called for each bound handler
        },
    
        setup: function( data, namespaces ) {
            jQuery( this ).bind( "click", jQuery.event.special.multiclick.handler );
        },
    
        remove: function( namespaces ) {
            // called for each bound handler
        },
    
        teardown: function( namespaces ) {
            jQuery( this ).unbind( "click", jQuery.event.special.multiclick.handler );
        },
    
        handler: function( event ) {
            // set correct event type
            event.type = "multiclick";
            // trigger multiclick handlers
            jQuery.event.handle.apply( this, arguments );
        }
    };
    

    Next we need to make sure the bound function to a multiclick event isn’t fired until it reaches the number of clicks specified. We’ll utilize the add hook for this. The add hook has the ability to return a function which will take the place of the provided handler. This is a powerful feature and allows us to do all sorts of fun things with special events. In the case of the multiclick event we are going to return a new function that calls the given handler after the specified number of clicks. Here is the add hook.

    jQuery.event.special.multiclick = {
        add: function( handler, data, namespaces ) {
            // get the required number of clicks from data
            var threshold = data && data.threshold || 1,
                // number of clicks
                clicks = 0;
    
            // return a new function that will become the handler
            return function( event ) {
                // increase number of clicks
                clicks += 1;
                if ( clicks === threshold ) {
                    // required number of clicks reached, reset
                    clicks = 0;
                    // call the actual supplied handler
                    handler.apply( this, arguments );
                }
            }
        },
    
        ...
    };
    

    The add hook makes use of closures to keep track of the required number of clicks (threshold), the actual number of clicks, and the handler to be called once the required number of clicks is reached. The jQuery event system keeps everything in tact so that when you call unbind with the same named function that you supplied to bind, it still unbinds it as expected.

    It ends up that we don’t actually need to use the remove hook for this special event, so we’ll just remove it. Thats it. The multiclick special event is done. You can see it in action here and the complete code follows.

    jQuery.event.special.multiclick = {
        add: function( handler, data, namespaces ) {
            // get the required number of clicks from data
            var threshold = data && data.threshold || 1,
                // number of clicks
                clicks = 0;
    
            // return a new function that will become the handler
            return function( event ) {
                // increase number of clicks
                clicks += 1;
                if ( clicks === threshold ) {
                    // required number of clicks reached, reset
                    clicks = 0;
                    // call the actual supplied handler
                    handler.apply( this, arguments );
                }
            }
        },
    
        setup: function( data, namespaces ) {
            jQuery( this ).bind( "click", jQuery.event.special.multiclick.handler );
        },
    
        teardown: function( namespaces ) {
            jQuery( this ).unbind( "click", jQuery.event.special.multiclick.handler );
        },
    
        handler: function( event ) {
            // set correct event type
            event.type = "multiclick";
            // trigger multiclick handlers
            jQuery.event.handle.apply( this, arguments );
        }
    };
    

    Thanks

    Thanks to Mike Helgeson who helped evolve the special events API to include these two hooks. He also has a handful of special events that he has created: drag, drop, hover, and wheel.

    If you knew about the specialAll event hook, it has been removed in favor of using these two extra hooks.

    http://brandonaaron.net/blog/2009/06/4/jquery-edge-new-special-event-hooks
  • jQuery Edge: Simplified .hover(), published on Thursday, 28 May 2009 4:56PM

    In jQuery 1.3.3 1.4 the .hover() method will optionally accept a single method instead of always requiring two methods. This allows you to contain your logic within one method instead of duplicating it among two. I don’t know about you but I’ve avoided using the .hover() method in favor of just manually binding the mouseenter and mouseleave events so that I could use a single function to handle both events. So, lets look at a typical use-case for hover.

    $('li')
        .hover(function(event) {
            $(this).addClass('test');
        }, function(event) {
            $(this).removeClass('test');
        });
    

    Now, this is how I’d typically handle this without the use of .hover().

    $('li')
        .bind('mouseenter mouseleave', function(event) {
            $(this).toggleClass('test');
        });
    

    Now lets use the new functionality of .hover() instead of manually binding the events.

    $('li')
        .hover(function(event) {
            $(this).toggleClass('test');
        });
    

    Sweet!

    Sometimes though you’ll need to do more complex interactions than just toggling a class. In these cases you can simply check the event type that is passed to your handler. When the user mouses over, the event type sent to the hover function will be mouseenter. When the user mouses out, the event type sent to the hover function will be mouseleave. Here is an example checking the event type.

    $('li')
        .hover(function(event) {
            $(this)[ (event.type == 'mouseenter') ? 'add' : 'remove') + 'Class' ]('test');
        });
    

    In the above example I’m still just toggling the class on the element that was hovered over/out but doing so in a more verbose way to illustrate how you can check the event type.

    http://brandonaaron.net/blog/2009/05/28/jquery-edge-simplified-hover
  • jQuery Edge: Better Support for other Windows and Documents, published on Thursday, 14 May 2009 11:22AM

    jQuery in the past hasn’t always played well with other windows and documents than the one it was loaded in. However, jQuery is moving in the right direction to help ease the pain of cross window/frame development. There have been several commits recently that make previously window/document dependent methods, well… less dependent. So far .bind(), .css(), .width(), and .height() have all been updated to work with other windows and documents.

    So, how might you use this new found jQuery power? First, if you didn’t already know jQuery has a method, called .contents(), that when used on an iframe element returns the document of the iframe.

    // Get a reference to the iframe document
    var iframeDoc = $('iframe').contents().get(0);
    

    Now we can bind an event to it, get the dimensions, get styles of various elements, etc.

    // Get width of iframe document
    $(iframeDoc).width();
    
    // Get height of iframe document
    $(iframeDoc).height();
    
    // Bind event to iframe document
    $(iframeDoc).bind('click', function( event ) {
        // do something
    });
    
    // Get the style of an element in the iframe
    $('div', iframeDoc).css('backgroundColor');
    

    Simple as that.

    http://brandonaaron.net/blog/2009/05/14/jquery-edge-better-support-for-other-windows-and-documents
  • jQuery Edge: Bind with a Different "this", published on Tuesday, 12 May 2009 10:50AM

    Brand new to jQuery SVN is the oft-requested feature of providing a different value for the “this” object in an event callback. Previously jQuery would always send the element as the value of “this” in the callback. You can utilize this new feature by passing the object you’d like to represent “this” in the callback as the last argument to either .bind() or .live(). Lets look at some example code!

    The typical use-case for this functionality is that you’ll have an object (or a class) and you want to call a method of that class when you click on a particular element. However, the “this” object inside the method will be set to the element the event happened on. Usually in these situations you’d want the “this” object to represent the instance of your class, not the element. Here is an example that tries to illustrate this use-case.

    // Create a class
    var MyClass = function() {
        // initialize the class
        this.clicked = false;
        this.element = null;
    };
    MyClass.prototype.click = function( event ) {
        // handle the click event on an element
        // the "this" object should represent the instance of
        // the class, not the element the event happened on.
        // the element can be retreived using the event.target
        // or event.currentTarget
    
        // set the clicked property to true
        this.clicked = true;
    
        // set the element property to the element that was clicked
        this.element = event.target;
    
        // cancel the event
        return false;
    };
    
    // Create an instance of MyClass
    var myInstance = new MyClass();
    
    // Bind an event that calls the click method of myInstance
    // and make sure the "this" object is set to myInstance
    $("li").live("click", myInstance.click, myInstance);
    

    As you can see from the example code the actual element the event happened on can still be retrieved via the event.target or event.currentTarget properties. Both of these properties are normalized/fixed to work cross-browser (as is the event.relatedTarget property). In the example above I used the live method of binding events but the same applies for the bind method.

    http://brandonaaron.net/blog/2009/05/12/jquery-edge-bind-with-a-different-this
  • jQuery Edge: Live Events now with Data, published on Friday, 08 May 2009 10:22AM

    In jQuery 1.3.3 1.4 the .live() method will have the ability to pass data along just like you might do with .bind(). The .bind() (and now .live()) method takes an optional second argument called “data”. The data can then be accessed within the event handler by using the event.data property. Here is what the code would look like to use this feature.

    // The data
    var eventConfig = {
        selectedClass: "selected"
    };
    
    $("li").live("click", eventConfig, function( event ) {
        // Retreive selectedClass from the event data
        var selectedClass = event.data.selectedClass;
    
        // Toggle the class amongst the siblings
        $(this).addClass( selectedClass )
            .siblings().removeClass( selectedClass );
    });
    

    So in the previous example, trivial as it may be, each time you click an “li” element the selected class defined in the eventConfig is assigned to the clicked element and removed from the siblings.

    http://brandonaaron.net/blog/2009/05/8/jquery-edge-live-events-now-with-data

RSS Feed Feed download link: http://feeds.feedburner.com/BrandonAaron