Chapter 9. Advanced Events

Ariel Flesler

Introduction

These recipes will deal with edge case problems, advanced optimizations, and certain techniques to make your code cooler. These recipes are mostly for advanced developers who want to take their jQuery code one step further.

As in Chapter 8, I’ll refer to code as plugins, but that doesn’t mean it needs to be an actual plugin. If you don’t structure your code as jQuery plugins, then keep my naming convention in mind.

9.1. Getting jQuery to Work When Loaded Dynamically

Problem

You are including jQuery dynamically into the page, by either adding a <script> element to the DOM or doing it some other way like Ajax.

Once jQuery is loaded, you expect everything to start working, but for some reason, no script starts.

Solution

You need to include an additional script to be executed after jQuery is loaded. This script will simply call jQuery.ready(). After you do this, everything will start working as expected.

Discussion

What is jQuery.ready()?

The jQuery.ready() function is called by jQuery’s core when the document is detected as ready. Once called, all the document.ready handlers are triggered automatically.

Note

You don’t need to worry about whether this function might have been called already (for example, by the original detection), triggering all the document.ready handlers again.

jQuery.ready() includes a check for duplicated executions internally. Further calls will be ignored.

Why was this happening?

The document.ready detection is mostly based on events. Depending on the browser, a certain event is bound, and it’s supposed to be triggered once the document is ready.

In addition, the window.onload event is bound for all browsers as a fallback measure in case the other options fail.

What was happening to you is that jQuery was finally loaded into the page only after the window.onload event; therefore, all the event handlers remained bound, and none got triggered.

By calling jQuery.ready(), you’re “announcing” the document.ready event manually, triggering all the handlers and getting things back to normal.

9.2. Speeding Up Global Event Triggering

Problem

Global event triggering implicates calling all the event handlers bound for a certain event, on all available elements.

It is performed by calling jQuery.trigger() without passing any DOM element as context. It is nearly the same as calling trigger() on all the elements that have one or more bindings to the corresponding event, something like this:

jQuery('#a1,#a2,div.b5').trigger('someEvent'),

Triggering globally is obviously simpler because you don’t need to know all the elements that need to be triggered.

It’s quite useful for certain situations but can also be a slow process at times. Although it’s been optimized since jQuery 1.3, it still requires going through all the elements registered to jQuery’s event system. This can cause short (or not so short) hangs every time an event is triggered like this.

Solution

One possible solution is to have one or more global objects that will act as event listeners. These elements can be DOM elements or not. All global events will be bound and triggered on one of these elements.

Instead of doing something like this:

jQuery('#text1').bind('change-page', function(e, title){
    jQuery(this).text( 'Page is ' + title );
});
jQuery('#text2').bind('change-page', function(e, title){
    jQuery(this).text( 'At ' + title + ' Page' );
});
jQuery.trigger('change-page', 'Inbox'),

you’d do something like this:

jQuery.page = jQuery({}); // Just an empty object
jQuery.page.bind('change', function(e, title){
    jQuery('#text1').text( 'Page is ' + title );
});
jQuery.page.bind('change', function(e, title){
    jQuery('#text2').text( 'At ' + title + ' Page' );
});
jQuery.page.trigger('change', 'Inbox'),

The syntax seems pretty much the same, but each call to trigger won’t be iterating jQuery’s data registry (aka jQuery.cache).

Even if you decide to use a DOM element, the principle is the same. DOM elements can be more appropriate at times. If, for example, you’re creating a table-related plugin, then it’d make sense to use each <table> element as an event listener.

The problem with DOM elements in many browsers is that they’re the main source of memory leaks. Memory leaks occur when there are certain amounts of RAM memory that cannot be freed by the JavaScript engine as the user leaves a page.

You should be much more careful about how you save data into the objects when you use DOM elements. That’s why jQuery provides the data() method.

Still, I’d personally use regular JavaScript objects in most situations. You can add attributes and functions to them, and the likelihood (and magnitude) of memory leaks will be smaller.

Discussion

Pros and cons

As stated by the recipe title, this approach is faster. You will be always triggering events on single objects, instead of the n entries on jQuery.cache.

The downside of this approach is that everyone needs to know the event listener object (jQuery.page in the example) in order to bind or trigger one of its known events.

This can be negative if you’re aiming to keep your code encapsulated.[16]

The concept of encapsulation is highly enforced in object-oriented programming, where this is one of the things you should be very cautious about.

This is generally not such a great concern with jQuery programming, because it is not object oriented and most users don’t get too worried about code encapsulation. Still, it’s worth mentioning.

Making the listeners functional

The listener objects mentioned don’t have to be simple dummy objects with nothing but bind(), unbind(), and trigger() (as far as we’re concerned).

These objects could actually have methods and attributes that would make them much more useful.

The only problem, though, is that if we do something like this:

jQuery.page = jQuery({ number:1 });

to access the number attribute, we would be forced to do this:

jQuery.page.number; // undefined
 jQuery.page[0].number; // 1

This is how jQuery works on HTML nodes and anything else.

But don’t give up on me yet! It’s easy to work around this. Let’s make a small plugin:

(function( $ ){

    // These methods will be copied from jQuery.fn to our prototype
    var copiedMethods = 'bind unbind one trigger triggerHandler'.split(' '),

    // Empty constructor
    function Listener(){
    };

    $.each(copiedMethods, function(i,name){
        Listener.prototype[name] = $.fn[name];
    });

    // Our "jQuery.fn.each" needs to be replaced
    Listener.prototype.each = function(fn) {
        fn.call(this);
        return this;
    };

    $.listener = function( data ){
        return $.extend(new Listener(), data);
    };

})( jQuery );

Now we can create objects that will have all the jQuery methods we need that are related to events, but the scope of the functions we pass to bind(), unbind(), etc., will be the object itself (jQuery.page in our example).

Note that our listener objects won’t have all jQuery methods but just the ones we copied. While you could add some more methods, most of them won’t work. That would require a more complex implementation; we’ll stick to this one, which satisfies our needs for events.

Now that we have this mini plugin, we can do this:

jQuery.page = jQuery.listener({
    title: 'Start',
    changeTo: function( title ){
        this.title = title;
        this.trigger('change'),
    }
});
jQuery.page.changeTo('Inbox'),

Because you can now access the object from within the handlers, using the this, you don’t need to pass certain values like the title as arguments to the handler. Instead, you can simply use this.title to access the value:

jQuery.page.bind('change', function(e){
    jQuery('#text1').text( 'Page is ' + this.title );
});

9.3. Creating Your Own Events

Problem

You want to provide certain behaviors to an element when it’s bound to an event.

Solution

Use jQuery.event.special to do this. This feature requires an object with, at least, a function that will be called each time your event is bound for the time on each element and another function to clean up what you did in the first place.

The syntax looks something like this:

jQuery.event.special.myEvent = {
    // Bind the custom event
    setup:function( data, namespaces ){
        this; // The element being bound
        // return false to get the native binding, otherwise,
        // it will be skipped
    },
    // Clean up
    teardown:function( namespaces ){
        this; // The element being bound
        // Ditto about returning false
    }
 };

After you add your event behavior, you can do something like this:

jQuery('#some_element').bind('myEvent', {foo:'bar'}, function(){...});

After this, your setup() function will be called.

Discussion

Handling every binding to your event

As explained, your setup() function will be called only when adding the first handler.

This is enough if the logic you’re encapsulating on this event doesn’t require some operations to be run each time a new binding is performed.

This option is provided by jQuery, but the approach has changed since jQuery 1.3.3.

If you’re using an older version, then you just need to use jQuery.event.specialAll instead of jQuery.event.special. It will accept the same kind of object, and your callbacks will receive the same arguments. The only difference is that returning false won’t bring any change.

As of jQuery 1.3.3, jQuery.event.specialAll is gone. To intercept all bindings for an event, you need to include an add() (and optionally remove()) function in your jQuery.event.special namespace. The functions will receive the handler that is about to be bound, and can optionally return a new handler function to be used instead.

A real-world example

Let’s make sure this is clear by writing a simple example; I’ll use the 1.3.3+ notation.

Let’s suppose you want to have an event triggered when an element is selected (clicked) and it isn’t disabled. We’ll assume that the item is disabled when it has the CSS class disabled.

Here’s a way of doing that:

// Save these to make the code shorter
// Don't do this within the global scope
var event = jQuery.event;
var $selected = event.special.selected = {
    setup:function( data ){
        event.add(this, 'click', $selected.handler);
        return false;
    },
    teardown:function(){
        event.remove(this, 'click', $selected.handler);
        return false;
    },
    handler:function(){
        var $elem = jQuery(this);
        if( !$elem.hasClass('disabled') )
            $elem.triggerHandler('selected'),
    }
};

As you can see, we provide our own handler for selected. Within the handler, we used triggerHandler() instead of trigger() because we don’t need event bubbling, and there’s no default action to prevent, so we save some needless processing.

Existing uses for this feature

jQuery.event.special is a great way of adding new behaviors without polluting the jQuery namespace.

It doesn’t suit any situation, but it usually comes in handy when you need a custom event that is based on another one (click in our example). It’s also useful if you have a plugin that simply binds events or simulates them; then you can “mask” that plugin as a regular event.

jQuery’s core uses jQuery.event.special to handle events bound to the document.ready event. Actually, they’re stored as regular event handlers, but the first time you bind to this event, you’re actually activating the (hacky) detection code.

It is also used to transparently handle mouseenter/mouseleave events (those used by hover()). All the DOM traversal operations needed to achieve this are nicely hidden within the setup() handlers.

There are also plugins that take advantage of jQuery.event.special. Some of these are as follows:

mousewheel

Provides support for mouse wheel changes.[17]

drag, drop

Drag and drop support masked as simple events.[18]

focusin, focusout

This snippet (not an actual plugin) originally written by Jörn Zaefferer was later added via plugins to achieve event delegation of focus and blur events.

Checking these plugins can be a good start if you’re planning on adding new events to jQuery.

9.4. Letting Event Handlers Provide Needed Data

Problem

You need to allow other plugins (or just simple jQuery code) to chime in and modify certain variables before you perform the requested action.

Solution

Use events to notify other scripts about the action that is about to be carried out.

It is possible to get data that is gathered from the actioned event handlers.

If none is provided, you could then use some default option of your choice.

You’ll see how to do this according to what jQuery version you are using.

Discussion

How can we do this with jQuery 1.3+?

Since jQuery 1.3, with the addition of jQuery.Event, this can be achieved in a nicer way. The old way still works for triggerHandler() but not for jQuery.trigger().

For the code we’ll see now, we will need to create a jQuery.Event:

var e = jQuery.Event('updateName'),

Now, to call the handlers and retrieve the value, we’ll pass the event object to trigger() and then fetch the data from the event object:

jQuery('#element').trigger(e);
 alert( e.result ); // Charles

As I said at the beginning, this doesn’t work nicely when many handlers are bound and is, in general terms, a little unreliable and fragile.

So, how can we communicate event handlers and the function triggering the event?

The answer is, through the event object that we’re passing.

The jQuery.Event object passed to trigger() will be the same that is received by each handler as its first argument.

This means that we can do this:

jQuery('#name').bind('updateName', function(e){
    e.name = this.value;
 });

 var e = jQuery.Event('updateName'),
 jQuery('#name').trigger(e);
 alert( e.name );

This example doesn’t differ much from simply accessing e.result, but what about multiple event handlers that operate on the same event object?

jQuery('#first').bind('update', function(e){
    e.firstName = this.value;
 });
 jQuery('#last').bind('update', function(e){
    e.lastName = this.value;
 });

 var e = jQuery.Event('update'),
 jQuery('#first, #last').trigger(e);
 alert( e.firstName );
 alert( e.lastName );

We now have a way of allowing any number of event handlers to provide needed information for a function to run. Needless to say, you can call trigger() several times, passing the same event object.

As said before, it’d be wise to preset the event object with default values (if applicable). Your code shouldn’t rely on the fact that others did subscribe to a certain event.

If no default value can be used, then you can always abort the call or throw an error.

How this was achieved before jQuery 1.3

Older versions of jQuery only allowed the users to get a single value, which would be returned when calling jQuery.trigger() and/or triggerHandler().

It looked something like this:

jQuery('#element').bind('updateName', function(){
    return 'Charles';
 });

 var name = jQuery('#element').triggerHandler('updateName'),
 alert( name ); // Charles

This was OK until you had more than one event handler returning data. At that point, it was a matter of “who comes last” to decide which one’s data would be returned.

Allowing event handlers to prevent actions

This is really a specialization of what we just saw. Event objects, by design, have a method called preventDefault(). This method is used on native events to abort common actions like clicks on links, but it has no real use on custom events.

We could take advantage of this and use this method to allow other scripts to prevent actions that are about to be performed.

I’ll now show you an example of an action. I’ll use the mini plugin introduced on Recipe 9.2, but that is certainly not a requirement to use this:

var remote = jQuery.listener({
     request:function( url, callback ){
        jQuery.ajax({ url:url, success:callback });
     }
 });

 // Do a request
 remote.request('contact.html', function( html ){
     alert( html );
 });

Now suppose we want to allow an external script to abort certain requests when needed. We need to modify remote.request like this:

var remote = jQuery.listener({
     request:function( url, callback ){
        var e = jQuery.Event('beforeRequest'),
        e.url = url;
        this.trigger(e);

        if( !e.isDefaultPrevented() )
            jQuery.ajax({ url:url, success:callback });
     }
 });

e.isDefaultPrevented() will return whether e.preventDefault() was ever called on this object.

Any external script can now do something like this:

remote.bind('beforeRequest', function(e){
    if( e.url == 'contact.html' )
        e.preventDefault();
 });

Returning false (within the function) would have nearly the same effect as calling e.preventDefault(). It will also stop the event propagation, which could be desirable.

Needless to say, in a situation like this, we could use what we learned before to allow the URL (or post data if added) to be modified by handlers.

9.5. Creating Event-Driven Plugins

Problem

You want your plugin to be controllable from the outside. One should be able to “tell” the plugin to do something at any time. There could be many instances (calls) of a plugin, but our action should only be executed in the context we provide.

Solution

One way to do this is indeed using events.

When a plugin is called, it binds functions on each matched element that once triggered, and it will perform the desired actions.

As a possible side solution, each time the plugin is called, instead of respecting the chaining, it could return an object that contains the bindings and allows external manipulation (use the plugin from Recipe 9.2).

This allows you to call the plugin many times on the same element without messing up the events.

Discussion

An example

We’ll now create a simple slideshow plugin. I’ll base this plugin on an existing plugin of mine called jQuery.SerialScroll.[19] I first thought of this approach when coding this plugin, and, I must say, it worked pretty well.

We’ll name our plugin slideshow. It will receive an <img> element and an array of URLs, and it will cycle the images. It will allow previous and next movement, jumping to a certain image, and also autocycling.

Let’s start with the basics:

(function( $ ){
    $.fn.slideshow = function(options){

        return this.each(function(){
            var $img = $(this),
                current = 0;

            // Add slideshow behavior...
        });
    };
 })( jQuery );

Now we’ll add a few local functions that will allow us to move to different images (URLs) in the collection:

function show( index ){
   var total = options.images.length;

    while( index < 0 )
        index += total;

    while( index >= total )
        index −= total;

    current = index;
    $img.attr('src', options.images[index]);
 }

 function prev(){
    show( current − 1 );
 }

 function next(){
    show( current + 1 );
 }

We can now expose this functionality with events:

$img.bind('prev', prev).bind('next', next).bind('goto',function(e, index){
    show( index );
 });

What about autocycling? Let’s add a few more functions:

var auto = false, id;

 function start(){
    stop();
    auto = true;
    id = setTimeout(next, options.interval || 2000);
 }

 function stop(){
    auto = false;
    clearTimeout(id);
 }

Here are the events:

$img.bind('start', start).bind('stop', stop);

We now need to add a few lines to show() to keep autocycling if needed:

function show( index ){
    // Same as before...

    if( auto )
        start();
 }

And that’s it! We now have a full slideshow with prev, next, and autocycling.

In order to make the example clear, I made this plugin completely dependent on the outside manipulation.

Here’s a model implementation:

<ul>
   <li><img id="prev" src="prev.png" /></li>
   <li><img id="slideshow" /></li>
   <li><img id="next" src="next.png" /></li>
 </ul>

...

(function( $ ){
    var $image = $('#slideshow'),

    $image.slideshow({
        images: ['1.jpg', '2.jpg', '3.jpg', '4.jpg'],
        interval: 3000
    });

    $('#prev').click(function(){
        $image.trigger('prev'),
    });

    $('#next').click(function(){
        $image.trigger('next'),
    });

    $image.trigger('goto', 0); // Initialize on 0
    $image.trigger('start'), // We want auto cycling

})( jQuery );

Note

I used trigger() because it’s nice and short, but the truth is that it would be faster if you used triggerHandler() because trigger() will generate event bubbling (since jQuery 1.3) and you probably don’t need it.

What happens if an element already has one of these events?

It could happen (although it’s strange) that the #slideshow element could already have a binding on an event by the name of prev, next, goto, start, or stop.

If this is the case, check Recipe 8.4 for a solution.

How can I allow others to clean up the added event handlers?

Because I didn’t expose the bound functions, any other external code won’t be able to unbind them.

In most cases, you could simply unbind the whole events from the element, something like this:

jQuery('#slideshow').unbind('prev next goto start stop'), // Enumerate each event
// or
jQuery('#slideshow').unbind(); // Blindly remove them all

If you need a cautious unbinding or if you simply want to unbind all related events, check Recipe 8.3.

What’s the difference with other approaches?

There are other existing techniques to allow external manipulation. I’ll compare some:

Allowing the plugin to accept commands

This pattern is used by jQuery UI (among others). It consists of executing actions when the plugin is passed a string as the first argument, for example:

jQuery('#image').slideshow('goto', 1);

This is a little shorter than using events, but the whole approach requires you to save all the needed data (the current index in our case) in a public way, so it can be retrieved afterward. People who use this pattern also tend to use data() to store the variables.

If you use events, you can simply use local variables because your event handlers have access to the plugin’s local scope.

Returning an object with methods

This pattern is used by the validate plugin from Jörn Zaefferer (and others too).

Depending on how it is coded, the object’s methods could be able to access local variables. To do so, it must use closures,[20] which aren’t a nice thing to abuse. Also, you need to store this object somewhere (globally). This also requires you to do pseudo-object-oriented code (which you may like or not).

You could create some kind of hybrid between this approach and the one I explained. Instead of binding the events (prev, next, etc.) to the DOM element, you could create an object (using jQuery.listener) and bind the events to it; then it could be returned. As we saw in Recipe 9.2, this listener object wouldn’t be restricted to events. It could have methods and even data saved into its attributes.

9.6. Getting Notified When jQuery Methods Are Called

Problem

You want to perform a certain action when a DOM element gets modified using jQuery. This could involve changing an attribute such as a CSS property, removing it from the document, etc.

Some browsers already support mutation events,[21] which would serve this need, but they’re not something you can use in a cross-browser fashion yet, and they aren’t integrated with jQuery.

Another thing you might ever need is to modify the arguments passed to jQuery methods before they’re executed. On the same principle, you could need to alter the data returned by a method after the executing of the function itself.

Solution

This is somehow related to aspect-oriented programming,[22] but here we won’t be nesting functions; instead, we’ll overload the desired method once and trigger events every time the method is called.

We’ll need one event to be triggered before the function is run to allow the arguments to be changed. We’ll also need an event after the function is run so we can retrieve the returned data and even change it if necessary.

Let’s see how to code this as a plugin. I’ll show you each step separately.

Discussion

Overloading the desired method

First, let’s create a function that replaces jQuery methods with our own function. I’ll name it jQuery.broadcast(); you can change its name if you prefer something else:

(function($){

    $.broadcast = function(name){
        // Save the original method
        var old = $.fn[name];

        $.fn[name] = function(){
            // Broadcast
        };
    };

})(jQuery);

name needs to be the method name we want to override, for example:

jQuery.broadcast('addClass'),

Triggering an event prior to the execution

Now that we have put our own function as the jQuery method, let’s see how to trigger an event that will allow us to change the incoming arguments:

// Create an event object
 var e = $.Event('before-'+name);
 // Save the arguments into the object
 e.args = $.makeArray(arguments);
 // Trigger the event
 this.trigger(e);

Assuming you’re broadcasting addClass(), we can now do this:

jQuery('body').bind('before-addClass',function(e){
  e.args[0]; // The CSS class
});

Executing the original method

An event is now triggered, but we still have to call the old addClass(). We’ll save the returned data into the event object as well so we can expose it later when we trigger the other event.

e.ret = old.apply(this, e.args);

As you can see, we don’t pass the original arguments array; instead, we use the one we exposed in case it was modified in some way.

Triggering an event after the execution

We now have the returned data saved in our event object. We can now trigger the final event, allowing external modification of the returned data.

We’ll reuse the same event object, but we’ll change the event’s name.

e.type = 'after-'+name;
this.trigger(e);

Returning the result

All what’s left now is to return the resulting data and continue with the normal execution. We’ll give out what we saved on e.ret that could have been modified by an event handler:

return e.ret;

Putting it all together

This is the completed code we’ve developed:

(function($){

    $.broadcast = function(name){
        var old = $.fn[name];

        $.fn[name] = function(){
            var e = $.Event('before-'+name);

            e.args = $.makeArray(arguments);
            this.trigger(e);

            e.ret = old.apply(this, e.args);

            e.type = 'after-'+name;
            this.trigger(e);

            return e.ret;
        };
    };

})(jQuery);

Where to go from here?

I tried to keep the example short to illustrate the point. There are a couple of things you can do to improve it; here are a few ideas:

  • Use triggerHandler() instead of trigger(): if you don’t need the events to bubble, you could simply use triggerHandler(). This will make the whole process faster; note that triggerHandler() only triggers the event on the first element of the collection.

  • Run the process on each element separately: in the previous example, trigger() is called on the whole collection at once. That will be OK for most cases but can yield unexpected results when used on collections with multiple elements.

    You could wrap what we put inside the function with a call to map(). That should make the code work once per element.

    The downside is that it will be slightly slower and will also generate an (unexpected) stack entry (pushStack()) due to the call to map().

  • Allow external code to prevent normal execution: if you’re using jQuery 1.3 or higher, you could take advantage of the methods for jQuery.Event.

    You can “ask” the event object whether someone called its preventDefault() method using e.isDefaultPrevented().

    If this returns true, then you don’t call the original function.

  • Avoid multiple overloads of the same jQuery method: this one is pretty simple; just create an internal object literal where you keep track of which methods were overloaded. Then you just ignore repeated calls.

  • Integrate this with jQuery.event.special: this one will save you from calling jQuery.broadcast() for each method you want to overload.

    Instead, you add an entry to jQuery.event.special for each method, and you internally call jQuery.broadcast() when someone binds an event. This should be combined with the check for duplicated calls.

9.7. Using Objects’ Methods as Event Listeners

Problem

You have objects with methods and attributes, and you want to pass those methods (functions) as event handlers. The problem is that once you do this, the method will “lose the reference” to the object, and you have no way of referencing the object within the event handlers.

Solution

This used to be quite complicated to achieve. It required you to generate closures that would encapsulate the object and then pass them to bind().

Since jQuery 1.3.3, a new parameter has been added to bind(). It allows you to specify an object as the scope or this of the event handler without using function closures.

This makes the required code both shorter and faster. You can now pass the object’s method as the function and the object itself as the scope.

Discussion

Where did the node go?

You will surely wonder this sooner or later. I said before that when you pass a scope object to bind(), the this of the event handler will be overridden. This means we can’t retrieve the node as we always do...but the node is not lost.

When you pass a scope object to the bind() method, events will be delivered with the this of the event handler set to the scope object. You can still determine the element being delivered to the event by using the event.currentTarget property, which contains a reference to the DOM element.

It’s usually not needed because using the this is shorter, but in situations like this, it’s the only way around.

The example

I’ll create a small example that should illustrate how to use the scope parameter and also show you a situation where it is useful.

The objects

For the example, we’ll need two objects. Each will have a method that we want to bind as an event handler.

These are the objects:

function Person(name){
  this.name = name;
  this.married = false;
}

jQuery.extend( Person.prototype, {
    whatIsYourName: function(){
        alert(this.name);
    },
    updateMarriedState: function(e){
        var checkbox = e.currentTarget;
        this.married = checkbox.checked;
    }
});

var peter = new Person('Peter'),
var susan = new Person('Susan'),
Binding the methods

Let’s suppose we have some sort of form and it has two checkboxes (#c1 and #c2). Each will manipulate the married state of one of our previous objects.

jQuery('#c1').bind('change', peter.updateMarriedState, peter);
jQuery('#c2').bind('change', susan.updateMarriedState, susan);

Thanks to the scope attribute, we don’t need to create new functions for each binding; we can use the objects’ methods instead.

The methods don’t even need to be attached to the objects in the first place. You could do something like this:

function updatePersonMarriedState(e){
   var checkbox = e.currentTarget;
   this.married = checkbox.checked;
}
jQuery('#c1').bind('change', updatePersonMarriedState, peter);
jQuery('#c2').bind('change', updatePersonMarriedState, susan);

As you can see, you’re not really forced to put those functions into the objects’ prototype, and it could actually make more sense to keep them separated. Why should a method belonging to Person know about checkboxes and the node? It’s probably nicer to keep all the specific DOM manipulation apart from the data.

In some cases, the object’s method won’t need to know about the node or the event object at all. When this happens, we can bind a method directly, and we won’t be mixing DOM and data at all.

If we had to have make two buttons (#b1 and #b2) to display the name of one person when clicked, then it’d be as simple as this:

jQuery('#b1').bind('click', peter.whatIsYourName, peter);
jQuery('#b2').bind('click', susan.whatIsYourName, susan);

It’s worth mentioning that both methods are actually the same:

peter.whatIsYourName == susan.whatIsYourName; // true

The function is created only once and saved into Person.prototype.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.133.123.34