O'Reilly logo

YUI 3 Cookbook by Evan Goer

Stay ahead with the world's most comprehensive technology and business learning platform.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, tutorials, and more.

Start Free Trial

No credit card required

Chapter 4. Events

Browser applications operate inside an event loop. The event loop is a browser thread that collects events such as mouse actions and keypresses and passes these events back to the JavaScript engine. To handle events, applications must register callback functions that listen or subscribe to different event types. Since events are the only way for applications to respond to user actions, they are a fundamental component in almost any client-side JavaScript program.

Event handling has evolved over the years, with browsers accreting different behaviors and quirks around working with events. Despite well-meaning attempts to clean things up, many of these inconsistencies persist to the present day. In fact, the Event API is arguably even more volatile between different browsers than the Node API. Any application that relies on events needs to protect itself against this volatility.

YUI addresses the problem using the same strategy described in Chapter 2: by wrapping event objects in a consistent façade that replicates the W3C DOM Level 2 Event object, with the exception that all native HTMLElement references are replaced with YUI Node references. The YUI event façade normalizes all sorts of browser inconsistencies around event propagation and event object properties.

Beyond offering normalization and more pleasant APIs, the YUI event façade opens up the possibility of defining entirely new event types. YUI supports four basic categories of events:

  • DOM events, which enable your application to respond to user interactions

  • Special DOM events, which enable you to subscribe to interesting moments as a page loads and renders

  • Synthetic events, which enable you to define brand-new DOM events, expanding how users can communicate with your application

  • Custom events, which enable components to communicate with each other by firing and subscribing to application-specific messages

Both synthetic events and custom events behave like ordinary DOM events, with the same API for attaching, detaching, delegating, and so on.

The ability to define new synthetic events and publish new custom events is one of the more powerful facets of YUI, right up there with the Loader (Chapter 1) and the Base object (Chapter 7). Custom events enable you to design your applications so that they harmonize with the browser’s natural event-driven architecture. You can use custom events to implement the Observer pattern and other popular strategies for controlling message flow.

Responding to Mouseovers, Clicks, and Other User Actions explains how to subscribe to basic DOM events, such as clicks and mouseovers.

Responding to Element and Page Lifecycle Events describes how to subscribe to interesting moments in the lifecycle of an element or page, such as the moment when an element becomes available in the DOM.

DOM events propagate through the DOM in a certain prescribed manner, and often include some sort of default behavior, such as adding a character to a text field, or navigating the user away from the page. Recipes and explain how to interfere with these processes, either by stopping an event from bubbling up through the DOM or by preventing the event’s default action.

Delegating Events discusses delegation, a technique for efficiently managing large numbers of event subscriptions by delegating control to a parent container element.

Firing and Capturing Custom Events introduces custom events, which pass information around your application without involving the DOM. Driving Applications with Custom Events demonstrates how to create more complex custom events and use them in a custom bubbling tree.

It is easy to use ordinary named functions or anonymous functions as event handlers, but object methods are tricky because assigning them as a handler causes them to lose their object context. Using Object Methods as Event Handlers explains how to fix this problem by binding the method to the correct context.

Detaching Event Subscriptions lists the many ways you can detach event subscriptions.

Controlling the Order of Event Handler Execution describes the order in which event handlers execute, and introduces the after() method, an alternative event subscriber method that is useful when you are working with custom events.

Creating Synthetic DOM Events introduces synthetic events. Synthetic events behave like DOM events externally, but are internally a wrapper for other DOM events plus some custom logic.

Responding to a Method Call with Another Method explains how to use YUI’s aspect-oriented programming (AOP) API. This API is not strictly event-related, but it does enable you to apply behavior in response to some other behavior…which is kind of like responding to an event. But not really.

Responding to Mouseovers, Clicks, and Other User Actions

Problem

When the user hovers over a <div>, you want to change the element’s background color.

Solution

Load the node rollup, then use Y.one() to select the node, followed by Y.Node’s on() method to set an event handler. The first argument of on() specifies the event to listen for—in this case, a mouseover event. The second argument provides an event handler function for YUI to execute when the event occurs.

Within the event handler function, the argument ev represents the event, and ev.target refers to the node where the event originally occurred. The target enables you to manipulate the target node—in this case, by adding and removing a class. See Example 4-1.

Example 4-1. Changing the background color on mouseover

<!DOCTYPE html>
<title>Changing the background color on mouseover</title>
<style>
div { border: 1px #000 solid; background: #a22; height: 100px; width: 100px; }
.over { background: #2a2; }
</style>

<div id="demo"></div>

<script src="http://yui.yahooapis.com/3.5.0/build/yui/yui-min.js"></script>
<script>
YUI().use('node-base', function (Y) {
    Y.one('#demo').on('mouseover', function (ev) {
        ev.target.addClass('over');
    });
    Y.one('#demo').on('mouseout', function (ev) {
        ev.target.removeClass('over');
    });
});
</script>

Although node and node-base perform DOM manipulation, they also pull in basic event handling support. For the most part, you need to load event-* modules only if you need specialized event features, such as synthetic events.

Note

Within the event handler function, by default YUI sets the this object to be the same node as ev.currentTarget, discussed next. You could therefore rewrite Example 4-1 to call this.addClass() instead. To override the value of this in the event handler, refer to Using Object Methods as Event Handlers.

Discussion

The event object ev contains a variety of useful properties, depending on the type of event.

For example, charCode represents a character generated during a keyboard event, while keyCode represents a key pressed during a keyboard event. Browsers can be wildly inconsistent about the values they report for keyCode and charCode in response to keydown, keypress, and keyup events. The YUI event façade harmonizes these differences away.

Events may also include pageX and pageY, which represent the coordinates of the user’s mouse. Example 4-2 uses pageX and pageY to create a <div> that jumps to wherever the user clicks on the page. The setXY() method moves the node on YUI’s normalized coordinate system, which avoids cross-browser confusion over left, scrollLeft, offsetLeft, clientLeft, and different box models.

Example 4-2. Following the user’s click

YUI().use('node', function (Y) {
    Y.one('document').on('click', function (ev) {
        Y.one('#demo').setXY([ev.pageX, ev.pageY]);
    });
});

As described in Controlling Event Propagation and Bubbling, events start at their originating element and bubble upward through the DOM. All event objects carry two (and sometimes three) properties that track which nodes were involved:

ev.target

Refers to the node where the event originated. When using native methods, browsers disagree on whether to return ev.target as a text node or an element node under certain circumstances. YUI always normalizes ev.target to refer to a YUI element Node, never a text node.

ev.currentTarget

Refers to the node where the event handler function was listening. Alternatively, you can think of ev.currentTarget as the element where the event has bubbled to. See also Delegating Events, which resets ev.currentTarget in an interesting way.

ev.relatedTarget

Refers to a secondary target, if any. This property is relevant only for events that involve motion across the screen, such as a mouse move, drag, or swipe. For example, in a mouseover, this property represents the node where the mouse exited, while in a mouseout, it represents the node where the mouse entered.

In Example 4-2, ev.target is either the <div> or the <body> depending on where you click, while ev.currentTarget is always the document node, since that’s where the listener was set. For more information about how events propagate through the DOM, refer to Controlling Event Propagation and Bubbling.

While you can count on most browsers supporting a core set of popular DOM events, use caution when listening for unusual or proprietary DOM events. YUI maintains a whitelist of supported DOM events in the static property Y.Node.DOM_EVENTS. If a native DOM event does not appear in Y.Node.DOM_EVENTS, or if the browser does not natively support that DOM event, the YUI event simply won’t trigger when that event is fired. If necessary, you can always mix additional native DOM event names into the whitelist.

You can also invent your own DOM events, as described in Creating Synthetic DOM Events. YUI provides a number of highly useful premade synthetic events, including valueChange, mouseenter, mouseleave, hover, and touch. YUI automatically registers synthetic events in the Y.Node.DOM_EVENTS whitelist.

Beyond DOM events, YUI also has a powerful custom event infrastructure that enables you to handle events that don’t necessarily have anything to do with the DOM. For more information, refer to Recipes and .

See Also

The YUI DOM event whitelist; Peter Paul Koch’s event compatibility tables, which attempts to catalog which DOM events are available in which browsers.

Responding to Element and Page Lifecycle Events

Problem

Rather than waiting for the entire page to load, you want to run some JavaScript on an element as soon as that element is available.

Solution

Set an event handler for the available event. The available event triggers as soon as the element is present in the DOM.

In the previous recipe, Example 4-1 fetched a node with Y.one() and then called the resulting YUI Node’s on() method. But if the document hasn’t loaded yet, this approach fails—the first call to Y.one() will fail to find the node, and just return null.

Example 4-3 solves this problem by listening for the available event on the top-level Y object, using Y.on(). To specify where it should listen, Y.on() takes a third argument that can be a CSS selector, similar to Y.one().

Example 4-3. Changing an element immediately on availability

<!DOCTYPE html>
<title>Changing an element immediately on availability</title>

<script src="http://yui.yahooapis.com/combo?3.5.0/build/yui-base/yui-base-min.js
&3.5.0/build/oop/oop-min.js&3.5.0/build/event-custom-base/event-custom-base-min.js
&3.5.0/build/features/features-min.js&3.5.0/build/dom-core/dom-core-min.js
&3.5.0/build/dom-base/dom-base-min.js&3.5.0/build/selector-native/selector-native-min.js
&3.5.0/build/selector/selector-min.js&3.5.0/build/node-core/node-core-min.js
&3.5.0/build/node-base/node-base-min.js&3.5.0/build/event-base/event-base-min.js"></script>
<script>
YUI().use('*', function (Y) {    
    if (Y.one('#demo') === null) {
        Y.log("We're sorry, the #demo node is currently not available.");
        Y.log('Your function() call is very important to us. Please try again later.');
    }
    
    Y.on('available', function () {
        Y.one('#demo').setHTML('Sorry, I changed the div as fast as I could!');
    }, '#demo');
});
</script>

<div id="demo"></div>

Example 4-3 is constructed specifically so that JavaScript loads and executes before the browser has a chance to parse the demo <div>. First, the JavaScript appears near the top of the page, rather than the bottom as is the norm for YUI. Second, rather than using the Loader to dynamically construct a combo load URL, the example explicitly includes the combo URL in the static HTML. Calling use('*') then statically attaches whatever modules are already on the page, namely node-base and its dependencies. This is the same pattern shown in Implementing Static Loading.

If the example had used the standard pattern of “load the small YUI seed, then use() the node-base module,” node-base would have loaded asynchronously, most likely giving the browser enough time to parse the rest of the document, which would make waiting for the available event unnecessary.

Discussion

Browsers already provide a load event, but sometimes you might want to begin interacting before that event fires. For example:

  • The page contains a great deal of complex markup that takes a long time to render, but you want to interact with an element very early.

  • The page loads some large image files, and you want to interact with the page before all these resources finish loading.

  • Your site serves its markup in stages: first sending over the heading and navigation markup, then sending over the content. This improves perceived performance, as the user now has something to look at while the backend is busy retrieving data. However, you also want to modify certain elements on the page as soon as they become available.

To help you interact with the page earlier, YUI provides three additional lifecycle events:

  • available fires as soon as YUI can detect its presence in the DOM. This is the earliest moment when you can interact with an element in the DOM.

  • contentready fires as soon as YUI can detect an element and its nextSibling in the DOM. This ensures that the element’s children are in the DOM tree as well.

  • domready fires as soon as the entire DOM has loaded and is ready to modify. This event fires before image files and other resources have loaded, while the native load event waits until all page resources are finally available.

    If you use the standard YUI sandbox pattern with scripts at the bottom, there is a good chance that the domready moment will occur after it is time to attach event handlers, and possibly even after the load event. domready is more likely to be useful in situations where you choose to load blocking scripts at the top of the page.

Note

Internet Explorer 7 and below can crash if you modify content before the DOM is complete. In these situations, YUI ensures that available and contentready fire after domready.

Y.on() provides a unified interface for assigning event handlers in YUI, while the on() method for Y.Node and Y.NodeList is a useful shortcut for assigning event handlers to nodes.

Y.on() is particularly useful for events that are not related to specific nodes, such as the domready lifecycle event, and custom events that are configured to bubble or broadcast to Y. For more information about controlling how custom events bubble and broadcast, refer to Driving Applications with Custom Events.

Y.on() can also assign event handlers to nodes that do not yet exist. For example, if you call Y.on('click', callback, '#myelement') in the <head> of the document, Y.on() polls for the existence of myelement in the DOM for several seconds before finally giving up. Note that calling Y.one('#myelement').on( ... ) before myelement exists would fail, since Y.one('#myelement') would just return null. Take care to avoid assigning many listeners for nonexistent elements, as excessive polling can affect performance.

Controlling Event Propagation and Bubbling

Problem

You would like to stop an event from bubbling up to a certain element in the DOM.

Solution

At some lower level in the DOM tree, assign an event handler to catch the event and call ev.stopPropagation() to prevent the event from bubbling up any further (see Example 4-4).

Example 4-4. Controlling event propagation and bubbling

<!DOCTYPE html>
<title>Controlling event propagation and bubbling</title>

<div id="i-want-candy">
    <ul id="candy-filter">
        <li class="veggie">Broccoli</li>
        <li class="candy">Chocolate Bar</li>
        <li class="veggie">Eggplant</li>
        <li class="candy">Lollipops</li>
    </ul>
</div>

<script src="http://yui.yahooapis.com/3.5.0/build/yui/yui-min.js"></script>
<script>
YUI().use('node-base', function (Y) {
    Y.one('#candy-filter').on('click', function (ev) {
        if (! ev.target.hasClass('candy')) {
            ev.stopPropagation();
        }
    });
    Y.one('#i-want-candy').on('click', function (ev) {
        Y.one('body').append('<b>Yum! </b>');
    });
});
</script>

Ordinarily, any click event that happens within the <div> would bubble up to the top of the DOM tree, causing the <div> to respond with a “Yum!” as the event passes through.

However, the <ul>’s click event handler interferes with the bubbling. If the original target node does not have a class of "candy", the <ul>’s event handler calls ev.stopPropagation(), which prevents the parent <div> from ever receiving the click event.

Discussion

When a user clicks on an element, the element’s container also receives a click, as does that element’s container, and so on out to the document. All of these should receive a click event, but in what order should the browser report these events? Early on, Internet Explorer chose to report events inside-to-out, which we now call bubbling. Netscape initially reported events outside-to-in, which we now call capturing, but shortly thereafter adopted IE’s bubbling model as well.

The benefit of bubbling is that it enables you to efficiently handle events by placing event handlers on containers. Consider a table with 100 draggable rows. You could assign 100 event handlers to each individual row, or you could set a single event handler on the common container. Asking the question, “which of my children is of interest?” is more efficient than assigning many individual event handler functions, and takes advantage of commonality between instances. Bubbling also means that the contents of the container can change without forcing you to add and remove more event listeners. YUI events support an advanced version of this concept called delegation. For more information, refer to Delegating Events.

Child elements can use stopPropagation() to prevent their parents from discovering events that occurred lower down in the tree. However, any other event handlers on the current target still execute for that event. To stop bubbling upward and prevent other event handlers at the same level from executing, call stopImmediatePropagation().

While stopPropagation() and stopImmediatePropagation() affect how the event bubbles through the DOM, they do not prevent any default behaviors associated with the event. For more information, refer to Preventing Default Behavior.

See Also

More information about bubbling, capturing, and stopPropagation() in Ilya Kantor’s tutorial, “Bubbling and capturing”.

Preventing Default Behavior

Problem

When a user clicks a link, you want to handle the click event in your own application and prevent the user from navigating away.

Solution

Use ev.preventDefault() to prevent the default behavior of the link from taking effect, as shown in Example 4-5.

Example 4-5. Preventing default behavior

<!DOCTYPE html>
<title>Preventing default behavior</title>

<a href="http://www.endoftheinternet.com/">The End of the Internet</a>

<script src="http://yui.yahooapis.com/3.5.0/build/yui/yui-min.js"></script>
<script>
YUI().use('node-base', function (Y) {    Y.one('a').on('click', function (ev) {
        ev.preventDefault();
        Y.one('body').append('<p>Now why would you ever go there?</p>');
    });
});
</script>

Discussion

Once an event finishes bubbling, the browser might also carry out some default behavior associated with the originating element. For example:

  • Clicking a form submit button submits the form data to the server.

  • Clicking a form reset button resets all form fields to their default values.

  • Pushing a key when focused on a textarea adds that character to the textarea.

JavaScript enables you to trap these behaviors and do something different. For example, if the default browser behavior would be to submit a form, you can call ev.preventDefault() to keep the user on the page and perhaps do some other work instead.

The key thing to remember is that bubbling and default behaviors occur in separate phases and can be canceled separately. To completely stop an event, call the convenience method ev.halt(), which is the equivalent of calling both ev.stopPropagation() and ev.preventDefault().

Delegating Events

Problem

You have a region on the page whose content changes frequently, but which contains elements that need to respond to user interaction. You want to avoid manually detaching old subscriptions and attaching new event subscriptions as the content changes.

Solution

Use the node’s delegate() method to assign the event handler. delegate()’s first two parameters are the same as on()’s, specifying the name of the event and the handler function to call. The third parameter is a filter that specifies which child elements the handler should be listening for.

Example 4-6 implements Controlling Event Propagation and Bubbling with fewer lines of code. It also adds two buttons that enable the user to dynamically add more candy or veggies to the list. Thanks to event delegation, there is no need to attach new event subscriptions to newly created list items—all “candy” list items automatically gain the correct click behavior for free.

Example 4-6. Delegating with a CSS selector

<!DOCTYPE html>
<title>Delegating with a CSS selector</title>

<div id="i-want-candy">
    <ul>
        <li class="veggie">Broccoli</li>
        <li class="candy">Chocolate Bar</li>
        <li class="veggie">Eggplant</li>
        <li class="candy">Lollipops</li>
    </ul>
</div>

<p><button name="candy">+ candy</button> <button name="veggie">+ veggie</button></p>

<script src="http://yui.yahooapis.com/3.5.0/build/yui/yui-min.js"></script>
<script>
YUI().use('node-event-delegate', function (Y) {
    Y.one('#i-want-candy').delegate('click', function () {
        Y.one('body').append('<b>Yum! </b>');
    }, 'li.candy');
    
    Y.all('button').on('click', function (ev) {
        var name = ev.target.get('name'),
            item = '<li class="' + name + '">' + name + '</li>';
        Y.one('#i-want-candy ul').append(item);
    });
});
</script>

As with on(), the event handler triggers for click events that have bubbled up to the <div>. However, the CSS selector 'li.candy' causes the event handler to trigger only for events from an <li> element with a class of candy. In the handler function, YUI also automatically sets the this object and ev.currentTarget to be the element matched by the filter. The overall effect is that delegate() makes the handler function behave as if it were subscribed on the list item, even though in reality, there is only one subscription on the parent element.

Note

Even though Examples 4-4 and 4-6 appear to behave the same way to the user, there is a key difference in that the former actually calls ev.stopPropagation() to stop the event from bubbling.

Discussion

Delegation in YUI is a kind of advanced treatment of bubbling that offers extra convenience and performance over assigning individual listeners.

As described earlier, bubbling enables you to handle many child events with a single event subscription on a parent container. Delegation takes this concept one step further by providing a handy filtering mechanism for designating the child elements of interest, and by setting ev.currentTarget to be the matched child element. The latter helps create the illusion that the event handler is subscribed directly on the child element instead of the container. If you end up needing a reference to the container anyway, delegate() stores that in the event property ev.container.

Internally, delegate() assigns a single event handler to the container element. When an event bubbles up to the container, YUI invokes a test function on the event, only calling the event handler function if the test passes. The default test function compares the child element against the CSS selector you provided. If you provide a custom test function instead of a CSS selector string, YUI executes that test function instead, as shown in Example 4-7.

Example 4-7. Delegating with a function

YUI().use('node-event-delegate', function (Y) {
    function isCandy(node, ev) {
        return node.hasClass('candy');
    }
    
    Y.one('#i_want_candy').delegate('click', function (ev) {
        Y.one('body').append('<b>Yum! </b>');
    }, isCandy);
});

For each node that the event bubbles through on its way to the parent, the test function receives the currentTarget node and the event as parameters. Of course, there’s no need to create a custom test function if a CSS selector will do the trick.

Besides being much more efficient than assigning lots of individual event handlers, delegate() is ideal for dynamic content. As Example 4-6 illustrates, when you add another child element to the container, it gets a “subscription” for free, since the element will pass the test just like its siblings. Likewise, if you remove a child element, you don’t need to worry about cleaning up its event handler.

Firing and Capturing Custom Events

Problem

When something interesting in your application occurs, you want to send a message to some other component in your application.

Solution

Use Y.on() to listen for a particular custom event. Then use Y.fire() to generate and fire a custom event.

Y.fire()’s first argument is the name of the event. YUI custom event names may include a prefix with a colon to help identify the origin of the event, although this is not strictly necessary.

All subsequent arguments to Y.fire() are optional and get passed into the event handler function as additional arguments. Example 4-8 passes custom data as fields on a single object in order to look more like a familiar DOM event, but you may pass data (or not) any way you like.

For obvious reasons, take care to declare all your event handlers before actually firing the event.

Example 4-8. Firing and capturing a custom event

<!DOCTYPE html>
<title>Firing and capturing a custom event</title>

<div id="demo"></div>

<script src="http://yui.yahooapis.com/3.5.0/build/yui/yui-min.js"></script>
<script>
YUI().use('node', 'event-custom', function (Y) {
    function theEagleHasLanded() {
        return true;
    }
    
    Y.on('moon:landing', function (ev) {
        var msg = Y.Lang.sub("{first} {last}: That's one small step for [a] man...", ev);
        Y.one('#demo').setHTML(msg);
    });
    
    if (theEagleHasLanded()) {
        Y.fire('moon:landing', {first: 'Neil', last: 'Armstrong'});
    }
});
</script>

The example uses Y.Lang.sub() to substitute the values of ev.first and ev.last into the message string. This is equivalent to:

var msg = ev.first + " " + ev.last + ": That's one small step for [a] man...";

For more information about Y.Lang.sub() templating, refer to Templating with Simple String Substitution.

Discussion

YUI’s custom event system is designed for creating event-driven applications. After all, the DOM itself is an event-driven architecture; custom events just extend this idea to be more general, enabling you to program “with the grain” of the system.

A custom event can represent any interesting moment you like. At their simplest, they are easy to generate; Y.fire('foo:bar') is often all you need. However, in general, custom events have all the behaviors and flexibility of DOM events. You can change how custom events bubble and propagate, as shown in Example 4-9. You can set default behaviors for custom events, and users of your event can then choose to suppress those default behaviors.

As with DOM events, if you have multiple event handlers listening for an event, YUI executes the event handlers in the order in which they were subscribed. In addition to on(), custom events also provide an after() subscriber that can execute handlers after the event’s default behavior executes. For more information, refer to Controlling the Order of Event Handler Execution.

A common pattern in YUI is to use custom events with Base, as shown in Driving Applications with Custom Events. When you extend Base, you must provide a NAME static property, which then becomes the prefix for any custom events that the object fires. For more information about the Base family of objects, refer to Chapter 7.

Take care not to confuse custom event prefixes with event categories, discussed in Detaching Event Subscriptions.

Driving Applications with Custom Events

Problem

You want to create relationships between objects in your system that allow events to bubble from child to parent like DOM events.

Solution

Create your application components by extending Y.Base using the Y.Base.create() method (discussed in Creating Base Components with Y.Base.create()). Objects that extend Base gain the EventTarget interface, which adds methods for firing events and hosting event subscriptions. These methods include:

  • on() for defining listeners and detach() for removing listeners

  • fire() for firing custom events

  • publish() for defining custom events that can bubble and have other behaviors

  • addTarget() and removeTarget() for controlling which objects events will bubble to

Example 4-9 illustrates how to use these methods to create a system of objects that pass messages using custom events.

Note

Example 4-9 shows off only a subset of the Base object’s functionality relating to events. For more information about this very important object, refer to Chapter 7.

Example 4-9. Driving applications with custom events

<!DOCTYPE html>
<title>Driving applications with custom events</title>

<script src="http://yui.yahooapis.com/3.5.0/build/yui/yui-min.js"></script>
<script>
YUI.add('apollo', function (Y) {
    var Apollo = Y.namespace('Apollo');
    
    Apollo.LunarModule = Y.Base.create('eagle', Y.Base, [], {
        initializer: function () {
            this.publish('landing', {
                broadcast: 2,
                defaultFn: function () {
                    Y.log("ARMSTRONG -- That's one small step for [a] man...");
                }
            });
        },
        reportLanding: function (status) {
            this.fire('landing', { ok: status });
        },
        tellJoke: function () {
            this.fire('joke');
        }
    });
    
    Apollo.CommandModule = Y.Base.create('columbia', Y.Base, [], {
        initializer: function () {
            this.on('eagle:joke', function (ev) {
                ev.stopPropagation();
                Y.log('COLLINS -- Haha Buzz, you crack me up!');
            });
        }
    });
    
    Apollo.MissionControl = Y.Base.create('houston', Y.Base, [], {
        initializer: function () {
            this.on('eagle:landing', function (ev) {
                if (ev.ok) {
                    Y.log('HOUSTON -- We copy you down, Eagle.');
                }
                else {
                    ev.halt();
                }
            });
            this.on('eagle:joke', function () {
                Y.log('HOUSTON -- Stop goofing around and get back to work.');
            });
        }
    });
}, '11', {requires: ['base-build']});

YUI().use('event-custom', function (Y){
    Y.Global.on('eagle:landing', function () {
        Y.log('WORLD -- Yay!');
    });    
});

YUI().use('apollo', function (Y) {
    var lunarModule = new Y.Apollo.LunarModule(),
        commandModule = new Y.Apollo.CommandModule(),
        missionControl = new Y.Apollo.MissionControl();
    
    lunarModule.addTarget(commandModule);
    commandModule.addTarget(missionControl);
    
    lunarModule.tellJoke();          // => COLLINS -- Haha Buzz, you crack me up!

    lunarModule.reportLanding(true); // => HOUSTON -- We copy you down, Eagle.
                                     // => ARMSTRONG -- That's one small step for [a] man.
                                     // => WORLD -- Yay!
});
</script>

Y.Base.create() is covered in Creating Base Components with Y.Base.create(). For now, the most important things to know are that Y.Base.create():

  • Creates a new class derived from Base, which includes the EventTarget API

  • Provides a prefix for events fired from that class, such as eagle

  • Enables you to define extra methods and add them to the class’s prototype

The module code defined inside YUI.add() uses Y.Base.create() to create a LunarModule object that can fire two events: eagle:landing and eagle:joke. When a Base-derived object fires an event, the custom event name automatically includes the NAME property as a prefix, which identifies the source of the event.

eagle:joke is a vanilla custom event. To define an event with any specialized behavior, you must call the publish() method. When LunarModule initializes itself, it publishes an eagle:landing custom event with:

  • A broadcast of 2, indicating that event:landing should be broadcast globally. If broadcast is 0, the event is received only by objects in the event’s bubble tree. A value of 1 means that YUI also broadcasts the event to the top-level Y object, which means Y.on() can handle the custom event. A value of 2 means that the event is also broadcast to the Y.Global object, which means any YUI instance on the page can respond to the event. Events fired from an EventTarget have a default broadcast of 0.

  • A defaultFn to trigger for the event. A default function is analogous to the default actions that browsers take in response to DOM events, such as link clicks and form submits. As with DOM events, you can suppress the default function for custom events.

The YUI.add() callback also defines objects for the CommandModule and MissionControl. These objects don’t publish or fire any events of their own, but they do define some event listeners for eagle:joke and eagle:landing.

The page then creates two YUI sandboxes with YUI().use(). The first sandbox defines a listener using Y.Global, so it receives any events with a broadcast of 2.

The second sandbox creates an instance for each of these three objects and then uses addTarget() to wire up a chain of event targets. The LunarModule instance sends its events to the CommandModule instance, and the CommandModule instance sends its events onward to the MissionControl instance. This works exactly like event bubbling in the DOM; an event fired by an <a> bubbles up to its parent <p>, which in turn bubbles up to its parent <div>. addTarget() is how you set up the default flow of information within an event-driven application.

Finally, the second YUI sandbox fires both events in turn. The browser console displays how the objects respond:

  1. The eagle:joke event bubbles to the CommandModule instance…and then stops. As with DOM events, you can control the bubbling behavior of custom events using ev.halt() and ev.stopPropagation(). If you comment out the call to stopPropagation(), eagle:joke continues on up to MissionControl, which responds with disapproval.

  2. The eagle:landing event bubbles up to CommandModule, which has no particular response, and then up to MissionControl. As with DOM events, custom events can carry payloads of additional information. In this case, the event façade passed to eagle:landing subscribers will also have a Boolean ok property indicating success or failure:

    • If the landing succeeds, MissionControl acknowledges the landing, the default function fires, and the event also gets broadcast to Y.Global. Because the first YUI sandbox set a listener using Y.Global.on(), it responds as well.

    • If the landing fails, MissionControl calls ev.halt(), which is the equivalent of calling ev.stopPropagation() and ev.preventDefault(). The eagle:landing default function does not fire and Y.Global never receives the event at all. (Let the conspiracy theories begin.)

Discussion

As shown in Firing and Capturing Custom Events, with only a few lines of code, you can pass messages around an application by firing off simple “throwaway” custom events and catching them with Y.on().

However, EventTarget’s publish() and addTarget() methods enable you to take the event-driven concept much further. If you have worked with the DOM, it is natural to think of applications in the same way: components wired together in a tree, firing off events with various payloads and default behaviors, catching these events as they bubble through the tree, and so on. Components in the core YUI library such as Cache, DataSource, and Y.log() make heavy use of custom events and default functions. For that matter, Node instances are also EventTargets—the event façade is the same whether you are working with DOM events or custom events.

Exposing custom events decouples component code from its use in implementation code. Component designers should call publish() and fire(), but component users should rarely fire() events themselves. Instead, component users should call component methods that internally call fire(). Likewise, component designers are encouraged to publish custom events with default functions, but should rarely need to prevent those functions. Calling preventDefault() on a custom event is a hook meant for component users.

Unlike elements in the DOM, it is not immediately obvious where a custom object should bubble its events to. This is why addTarget() exists—to help you define your own bubbling hierarchy. Alternatively, you can use broadcast and rely on Y.on() or Y.Global.on() to handle events. Y.on() is useful if you want to use Y as a “master switch” for handling custom events, while Y.Global.on() is useful for passing messages between sandboxes. For example, if you have a “dashboard” page that runs multiple applications, setting broadcast to 2 would enable you to pass information to a master control component that uses Y.Global. Using addTarget() gives you more fine-grained control, while using broadcast is a bit simpler.

As mentioned in Controlling Event Propagation and Bubbling, bubbling is a mechanism for the order in which you report events that affect all objects in a tree. One way to think about addTarget() is as a way to help represent that one object is a part of another. Thus, if you store objectChild as a property of objectParent or in a collection that belongs to objectParent, you might set up a bubble chain by calling objectChild.addTarget(objectParent).

The publish() method provides a great deal of flexibility for defining custom events. Example 4-9 demonstrates broadcast and defaultFn, but many other configuration options exist. For example:

  • The emitFacade field controls whether the custom event sets emitFacade to true, which in turn means that the event can have more complex behaviors that allow it to bubble, execute default functions, and so on. In a simple Y.fire(), eventFacade is false. However, if you publish and fire events from an object derived from Base, eventFacade defaults to true.

  • The bubbles field controls whether the event is permitted to bubble. By default, bubbles is true. If bubbles is false, the event ignores the chain created by addTarget(). In that case, the only way to allow the event to be caught by another object is to set broadcast to 1 or 2.

  • The preventedFn field specifies a function to fire if and only if ev.preventDefault() is called. This provides a fallback action in case the default action is prevented.

You can also configure custom objects to have a different prefix, to be unpreventable, to execute a function when something stops their propagation, and more. To learn more about defining custom events, refer to the EventTarget API documentation.

So why does Example 4-9 show off using Base instead of just extending EventTarget directly? The main reason is that in the real world, experienced YUI developers tend to prefer using Base and its children over using EventTarget by itself. In addition to EventTarget, Base also includes the important Attribute API and other methods that work together to create a stable foundation for constructing application components. However, if you are sure you only need EventTarget, feel free to use Y.augment() or Y.extend() to add just that functionality. For more information about how the core YUI objects all work together, refer to Chapter 7.

One subtle point to think about is whether to define event behavior for an entire class, or only for particular instances. In the example, CommandModule and MissionControl set their event listeners in their own initializer() function, which ensures that all instances of CommandModule listen for eagle:joke. To define an event listener only for a particular instance of CommandModule, you could call commandModule.on() in the YUI().use().

Using Object Methods as Event Handlers

Problem

You want to use an object method as an event handler. The method works fine when you call it directly, but fails mysteriously when called as an event handler.

Solution

The method fails because when used as an event handler, the method is bound to the wrong context.

Ordinarily within a method, the this object contains a reference to the method’s parent object. This enables the method to use other properties and methods on the object.

However, assigning an object method as an event handler changes the value of this. In a native DOM addEventListener(), this gets set to be the DOM node where the event occurred, while in Internet Explorer’s attachEvent() method, this gets set to the global window object. In a YUI on() subscriber, YUI sets this to be the Y.Node instance where the event occurred. In all these cases, the method will now fail if it makes any internal reference to this.

Fortunately, the on() method provides a simple fix. After you specify the handler function, the next parameter to on() overrides the value of this within the handler function. See Example 4-10.

Example 4-10. Using object methods as event handlers

<!DOCTYPE html>
<title>Using object methods as event handlers</title>
<style>
.notice  { color: #00c; }
.warning { color: #e80; }
.caution { color: #f00; }
</style>

<p>"Though this be madness, there is method in it."</p>

<script src="http://yui.yahooapis.com/3.5.0/build/yui/yui-min.js"></script>
<script>
YUI().use('node', function (Y) {
    var notifier = {
        msgType: 'caution',
        mark: function (ev) {
            ev.target.addClass(this.msgType);
        }
    };
    Y.one('p').on('click', notifier.mark, notifier);
});
</script>

Example 4-10 uses the third parameter in on() to bind the handler function to the correct context—in this case, the method’s parent object.

To see the example fail, remove the last parameter. this.type falls back to looking for a type property on the Node instance, rather than on the notifier object.

Discussion

The reason this changes inside event handlers is a fundamental behavior of JavaScript. When you pass a reference to a method like notifier.mark to some other function, the JavaScript engine:

  1. Finds an object named notifier.

  2. Finds an object property on that object named mark.

  3. Extracts the value of that property. In this case, function(ev) { ev.target.addClass(this.msgType); }

  4. Passes that value in to the function.

In other words, JavaScript rips the method free of its initial context and passes it in as a simple function. It is as if the code were:

Y.one('p').on('click', function (ev) {
    ev.target.addClass(this.msgType);
});

which is bad, because this.msgType is undefined for the paragraph node.

After the context override parameter, any extra parameters provided to on() get passed in as arguments to the event handler function. Example 4-11 is a variation of the previous example where notifier now maintains an array of message types, and the mark() method now takes an integer level to pick out the right type.

Example 4-11. Passing arguments to an object method in an event handler

YUI().use('node', function (Y) {
    var notifier = {
        msgType: ['notice', 'warning', 'caution'],
        mark: function (ev, level) {
            ev.target.addClass(this.msgType[level]);
        }
    };
    Y.one('p').on('click', notifier.mark, notifier, 2);
});

Since correcting the context is a problem that goes beyond just event handlers, YUI provides a general solution. The Y.bind() method takes a function and a context object, and returns a wrapped function, with the wrapper now properly bound to the new context. Example 4-12 demonstrates an equivalent solution to Example 4-10.

Example 4-12. Fixing the context with Y.bind()

YUI().use('node', function (Y) {
    var notifier = {
        msgType: 'caution',
        mark: function (ev, level) {
            ev.target.addClass(this.msgType);
        }
    };
    var fn = Y.bind(notifier.mark, notifier);
    Y.one('p').on('click', fn);
});

Like the extended syntax for on(), Y.bind() also supports passing in additional arguments:

var fn = Y.bind(notifier.mark, notifier, 2);

However, these arguments get passed into the callback before the event argument, ev. To pass in extra arguments after the ev argument, use Y.rbind().

For event handlers, you can use on()’s extended signature or Y.bind(), depending on which syntax you prefer.

Also note that the ECMAScript 5 standard defines a native Function.prototype.bind() method. Y.bind() enables you to cover your bases in both older and newer browsers.

Detaching Event Subscriptions

Problem

You want to remove an event handler from an event target.

Solution

Calling on() returns the subscription’s handle object. Saving this object enables you to call detach() on the handle later to remove the subscription. See Example 4-13.

Example 4-13. Detaching event subscriptions

<!DOCTYPE html>
<title>Detaching event subscriptions</title>

<button id="annoying_patron">Boy Howdy!</button>
<button id="librarian">Sssh!</button>

<script src="http://yui.yahooapis.com/3.5.0/build/yui/yui-min.js"></script>
<script>
YUI().use('node-base', function (Y) { 
    var handle = Y.on('click', function () {
        Y.one('body').append('<p>Boy howdy, this sure is a nice library!</p>');
    }, '#annoying_patron');
    
    Y.on('click', function () {
        Y.one('body').append('<p>Sssh!</p>');
        handle.detach();
    }, '#librarian');
});
</script>

YUI also provides once(), a sugar method for creating single-use event subscriptions. As written, Example 4-13 allows the librarian to say “Sssh!” multiple times. You could use once() as an easy way to configure the librarian event handler to fire once, then detach itself.

Discussion

YUI provides a great variety of ways to detach events—possibly more than it should.

In old browsers, it was important to detach event subscriptions in order to avoid memory leaks. This is a mostly solved problem today, but it is still possible to create pages that consume lots of memory because they fail to clean up node references and other objects.

One common reason to detach event subscriptions is when you are implementing an object destructor, such as the destroy() method of a Widget or Plugin. To make mass detachments easier, YUI allows you to add an arbitrary prefix to the event type when subscribing to events. For example, ordinarily you might subscribe to a click event by calling:

someNode.on('click', ...)

but you are also free to add a prefix foo, separated by a vertical bar:

someNode.on('foo|click', ...)

This prefix is called an event category. If you assign many event listeners under the same category, you can detach them in one step by supplying a wildcard:

someNode.detach('foo|*');

Other YUI detaching techniques include, but are by no means limited to:

node.remove(true)

Removes that node from the DOM. Passing in true destroys that node as well, nulling out internal node references and removing all plugins and event listeners.

node.empty()

Destroys all of a node’s child nodes.

node.detach(type)

Removes any event handlers on the node that match the specified type.

node.detach(type, function)

Removes any event handlers that match the specified type and handler function. This requires duplicating the signature of the original subscription, so it is usually easier to just save the subscription handle in the first place.

Y.detach(type, function, selector)

Removes any DOM event handlers in the sandbox that match the specified type and handler function, and that reside on a node that matches the CSS selector.

See Also

Tony Gentilcore’s blog post “Finding memory leaks”.

Controlling the Order of Event Handler Execution

Problem

You have multiple event handlers listening for an event on the same event target, and you want to make sure the handlers all execute in a particular order.

Solution

Specify your on() event listeners in the order in which you would like the handlers to execute, as shown in Example 4-14.

Example 4-14. Controlling event handler execution order (DOM events)

<!DOCTYPE html>
<title>Controlling event handler execution order (DOM events)</title>

<p>Click me, then check your browser error console for exciting log action!</p>

<script src="http://yui.yahooapis.com/3.5.0/build/yui/yui-min.js"></script>
<script>
YUI().use('node-base', function (Y) {
    var p = Y.one('p');
    p.on('click', function () { Y.log('I will execute first.') });
    p.on('click', function () { Y.log('I will execute second.') });
    p.on('click', function () { Y.log('I will execute third.') });
});
</script>

For custom events, you can also use the after() method to execute handlers in a separate sequence that runs after the ordinary sequence of on() handlers. In Example 4-15, the two on() handlers execute in the order they were assigned, and then the after() handler executes. after() handlers also have special behavior around preventDefault(), as described in the upcoming Discussion.

Example 4-15. Controlling event handler execution order (custom events)

<!DOCTYPE html>
<title>Controlling event handler execution order (custom events)</title>

<p>Check your browser error console for exciting log action!</p>

<script src="http://yui.yahooapis.com/3.5.0/build/yui/yui-min.js"></script>
<script>
YUI().use('event-base', function(Y) {
    Y.on('my:example', function () { Y.log('I will execute first.') });
    Y.after('my:example', function () { Y.log('I will execute third.') });
    Y.on('my:example', function () { Y.log('I will execute second.') });
    
    Y.fire('my:example');
});
</script>

Unlike custom events, for DOM events after() is just a synonym for on() with no special behavior. To avoid confusion, do not use after() with DOM events.

Discussion

For custom events, and custom events only, after() has two key features. First, if your on() handlers are scattered around your code, after() can help create order out of chaos. Second, if you call preventDefault() from an on() subscriber to prevent a custom event’s default function, no after() handlers are notified about the event.

In general, for custom events (not DOM events!), you can think of the relationship as:

  1. on() handlers act before a state change.

  2. The default function carries out a state change.

  3. after() handlers view and respond to a state change, if that state change occurs.

This is a fundamental pattern when you are using Base-derived objects. Calling set() to change an attribute fires a change event, but calling preventDefault() cancels the requested value change. This means that if you need to respond to the attribute’s value actually changing (as opposed to a change attempt), you should set an after() listener rather than an on() listener.

Note

If you need a single-use after() listener, use the onceAfter() sugar method. This is the equivalent of the once() method, which creates a single-use on() listener.

For DOM events, the overall execution sequence is:

  1. All on() and after() event handlers on the target execute in order of specification, unless an event handler calls stopImmediatePropagation().

  2. If there is another event target to bubble to and no event handler has called stopPropagation() or stopImmediatePropagation() yet, the event bubbles upward. Return to the previous step.

  3. The default behavior for that DOM event executes, unless an event handler calls preventDefault().

For custom events, the overall execution sequence is:

  1. All on() event handlers on the target execute in order of specification, unless an event handler calls stopImmediatePropagation().

  2. If there is another event target to bubble to and no event handler has called stopPropagation() or stopImmediatePropagation() yet, the event bubbles upward. Return to the previous step.

  3. The default function for that custom event executes, unless an event handler calls preventDefault().

  4. If preventDefault() was not called, bubbling starts again for all the after() handlers:

    1. All after() event handlers on the target execute in order of specification, unless an event handler calls stopImmediatePropagation() or preventDefault() had been called earlier.

    2. If there is another event target to bubble to and no event handler has called stopPropagation() or stopImmediatePropagation() yet, the event bubbles upward. Return to the previous step.

Note

There is also a before() method, but it is just a synonym for on() for both DOM events and custom events. Y.before() should not be confused with Y.Do.before(). For more information about Y.Do, refer to Responding to a Method Call with Another Method.

Creating Synthetic DOM Events

Problem

You want to handle a DOM event that the browser does not support directly.

Solution

Use Y.Event.define() to define a synthetic event, an event composed of ordinary DOM events (or other synthetic events) and custom logic to determine when to actually fire the event. A YUI synthetic event behaves like an ordinary DOM event and can be handled with the same API. A synthetic event must define its own on() method to define how to listen for the event, its own detach() method to define how to remove its event handlers, and so on.

Example 4-16 defines a middleclick event. The synthetic event is built using mouseup rather than click because, in many browsers, click events do not report accurate information in event.button. To demonstrate that ordinary subscription and delegation both work, the example sets an on() listener and a delegate() listener.

Note

If your mouse does not have a middle button, you can convert this example to a rightclick synthetic event by changing the conditional to ev.button === 3. Suppressing the browser context menu is left as an exercise for the reader.

Example 4-16. Defining a middleclick synthetic event

<!DOCTYPE html>
<title>Defining a middleclick synthetic event</title>

<div id="container">
<p id="demo">Middle-click this paragraph.</p>
<p>Or this paragraph.</p>
<p>Or perhaps even this one.</p>
</div>

<script src="http://yui.yahooapis.com/3.5.0/build/yui/yui-min.js"></script>
<script>
YUI().use('node', 'event-synthetic', function (Y) {
    Y.Event.define('middleclick', {
        _handler: function (ev, notifier) {
            if (ev.button === 2) {
                notifier.fire(ev);
            }
        },
        on: function (node, sub, notifier) {
            sub.handle = node.on('mouseup', this._handler, this, notifier);
        },
        delegate: function (node, sub, notifier, filter) {
            sub.delegateHandle = node.delegate('mouseup', this._handler, 
                filter, this, notifier);
        },
        detach: function (node, sub, notifier) {
            sub.handle.detach();
        },
        detachDelegate: function (node, sub, notifier) {
            sub.delegateHandle.detach();
        }
    });

    Y.one('#demo').on('middleclick', function () {
        Y.one('body').append('<b>Awesome! </b>');
    });
    Y.one('#container').delegate('middleclick', function () {
        Y.one('body').append('<b>Thanks! </b>');
    }, 'p');
});
</script>

The on() method receives three parameters:

node

The node where the caller subscribed to the event. Often (but not always!) you will attach ordinary event listeners to this node. The middleclick example assigns a mouseup event to the target node. Some synthetic events need to assign multiple listeners to a node, or assign listeners to the node’s children, parents, or even the document object.

sub

An object that represents the subscription to the synthetic event. Since synthetic events often involve multiple DOM events interacting with each other, the sub object is a handy place for sharing information between events and for storing event handles, so that the event is easy to detach later on.

notifier

The object from which to fire the synthetic event. For any synthetic event, there is some set of conditions that indicate the synthetic event has occurred. In Example 4-16, the conditions are very simple—a single DOM event and a single conditional. Once the conditions are satisfied, on() must call notifier.fire() to indicate that the synthetic event has occurred.

The detach() method receives the same three parameters—including sub, which should ideally contain all the handles required to detach the event. In this case, there is only a single mouseup event to detach, but in general, a synthetic event may have event handlers scattered all over the document.

The middleclick event also supports event delegation. Since middleclick is so simple, its delegate() is almost identical to on(), with common logic factored out into a “protected” _handler() method. However, some synthetic events require different logic for delegation versus basic subscription. If a synthetic event does not implement a delegate() method, Y.delegate() and node.delegate() will not work for that event. The same is true for detach() and other methods in the interface.

Discussion

At first glance, synthetic events might seem esoteric. Shouldn’t click, mouseover, and submit be good enough for anybody? Synthetic events turn out to have an enormous range of use cases, such as:

  • Correctly implementing tricky edge behavior that browsers handle poorly. For example, YUI provides a synthetic valueChange event that handles atomic changes to input fields and textareas. Unlike the standard DOM change event, valueChange fires when the field value changes, not when the field loses focus. Unlike the input event and the various key events, valueChange reliably handles multikeystroke inputs, copy-and-paste operations, mouse operations, and a variety of input method editors. valueChange was invented for the AutoComplete widget, but is a useful component in its own right.

  • Harmonizing between touch events and mouse events. Rather than creating a specialized “YUI Mobile” library to program against, YUI’s philosophy around mobile device support is to present a single unified API. To that end, YUI provides an assortment of synthetic events such as gesturemovestart and gesturemoveend that encapsulate touch events and mouse events in a single interface.

  • Bringing newly standardized DOM events to older browsers. For example, HTML now defines an invalid event for form elements. A synthetic invalid event would enable you to use a consistent scheme for client-side error checking.

  • Handling complex combinations of clicks, drags, swipes, and keyboard combinations for power users.

Responding to a Method Call with Another Method

Problem

Each time your application creates a node with Y.Node.create(), you want to log this information to the browser console using Y.log().

Solution

Use Y.Do.after() to configure YUI to automatically call a function after each call to Y.Node.create(). Automatically inserting a method before or after another method is a technique borrowed from a software methodology named aspect-oriented programming (AOP). As Example 4-17 shows, the first parameter of Y.Do.after() specifies the advice function to call, and the second and third parameters specify the object and the method name where the advice function should be inserted, known in AOP as the joinpoint.

Example 4-17. Using AOP to log node creation

<!DOCTYPE html>
<title>Using AOP to log node creation</title>

<script src='http://yui.yahooapis.com/3.5.0/build/yui/yui-min.js'></script>
<script>
YUI().use('node', function (Y) {    
    var logCreate = function (fragment) {
        Y.log('CREATED: ' + fragment);
    };
    var logHandle = Y.Do.after(logCreate, Y.Node, 'create');
    
    var musketeers = Y.Node.create('<ul></ul>');
    Y.Node.create('<li>Athos</li>').appendTo(musketeers);
    Y.Node.create('<li>Porthos</li>').appendTo(musketeers);
    Y.Node.create('<li>Aramis</li>').appendTo(musketeers);    
    
    logHandle.detach();
    Y.Node.create("<li>d'Artagnan</li>").appendTo(musketeers);
    
    musketeers.appendTo(Y.one('body'));
});
</script>

Like assigning an event handler with on(), calling Y.Do.after() returns a handle that you can use to tear down the configuration. Example 4-17 adds “d’Artagnan” after detaching the handle, so the browser console displays only four entries: one for the empty <ul> and one each for Athos, Porthos, and Aramis, but nothing for d’Artagnan.

As the example illustrates, logCreate() (the advice function) receives the same arguments as Y.Node.create() (the method called at the joinpoint). As with the on() method, you can provide the advice function a different execution context with the fourth parameter, or pass in extra arguments with the fifth and subsequent parameters. For more information about binding functions to a new context, refer to Using Object Methods as Event Handlers.

There is also a Y.Do.before() method with the same signature as Y.Do.after().

Discussion

Although good developers try to neatly encapsulate the separate concerns of their code, programs often have crosscutting concerns that foil these efforts, such as data persistence or logging. AOP is a strategy for dealing with crosscutting concerns by altering the program’s behavior. In AOP, you apply advice (typically a method) at certain points of interest called joinpoints (typically some other method), as mentioned earlier.

For example, you have a variety of objects designed to hold data. Each time one of these objects calls a set() method to change an internal value, you want to save off the old value so that it is possible to undo the change. You could try manually hacking this extra “save” behavior into each object’s set() method, but the AOP approach would be to inject the save() behavior as an advice function, right before the set() joinpoint.

Y.Do.before() and Y.Do.after() are useful for situations where you want to add behavior around some method that, by its nature, might be scattered through your application. You can also use them just to add behavior around a method when you can’t or don’t want to change the method’s internals (either by pasting new behavior directly in the method, or by altering the method to fire off a custom event).

For example, you are using a DataSource to fetch some remote data. If the fetch succeeds, you need to call another function in response. You could fire a custom event on success and listen for that, but AOP provides a clean, concise way to call your reactToSuccess() function immediately afterward:

myDataSource.on('request', function (ev) {
    Y.Do.after(reactToSuccess, ev.callback, 'success');
});

You can also use Y.Do.before() to modify the arguments the intercepted method will receive or prevent an event from executing, and you can use Y.Do.after() to read and possibly modify the return value. This can be a simpler way to modify an object—rather than creating an extension, you can use AOP to just modify a few of the object’s instances. This technique is particularly useful in plugins that change the behavior of their host object. For an example, refer to Creating a Plugin That Alters Host Behavior.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, interactive tutorials, and more.

Start Free Trial

No credit card required