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.
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.
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 normalizesev.target
to refer to a YUI elementNode
, 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 resetsev.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 amouseout
, 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 .
The YUI DOM event whitelist; Peter Paul Koch’s event compatibility tables, which attempts to catalog which DOM events are available in which browsers.
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.
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.
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 itsnextSibling
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 nativeload
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 theload
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.
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.stop
Propagation()
, which prevents the parent
<div>
from ever receiving the
click
event.
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.
More information about bubbling, capturing, and stopPropagation()
in Ilya Kantor’s tutorial,
“Bubbling
and capturing”.
When a user clicks a link, you want to handle the
click
event in your own application and prevent the
user from navigating away.
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>
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.prevent
Default()
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()
.
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.
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.
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.
When something interesting in your application occurs, you want to send a message to some other component in your application.
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.
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.
You want to create relationships between objects in your system that allow events to bubble from child to parent like DOM events.
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:
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()
:
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
of2
, indicating thatevent:landing
should be broadcast globally. Ifbroadcast
is0
, the event is received only by objects in the event’s bubble tree. A value of1
means that YUI also broadcasts the event to the top-levelY
object, which meansY.on()
can handle the custom event. A value of2
means that the event is also broadcast to theY.Global
object, which means any YUI instance on the page can respond to the event. Events fired from anEventTarget
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:
The
eagle:joke
event bubbles to theCommandModule
instance…and then stops. As with DOM events, you can control the bubbling behavior of custom events usingev.halt()
andev.stopPropagation()
. If you comment out the call tostopPropagation()
,eagle:joke
continues on up toMissionControl
, which responds with disapproval.The
eagle:landing
event bubbles up toCommandModule
, which has no particular response, and then up toMissionControl
. As with DOM events, custom events can carry payloads of additional information. In this case, the event façade passed toeagle:landing
subscribers will also have a Booleanok
property indicating success or failure:If the landing succeeds,
MissionControl
acknowledges the landing, the default function fires, and the event also gets broadcast toY.Global
. Because the first YUI sandbox set a listener usingY.Global.on()
, it responds as well.If the landing fails,
MissionControl
callsev.halt()
, which is the equivalent of callingev.stopPropagation()
andev.preventDefault()
. Theeagle:landing
default function does not fire andY.Global
never receives the event at all. (Let the conspiracy theories begin.)
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
EventTarget
s—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 setsemitFacade
totrue
, 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 simpleY.fire()
,event
Facade
isfalse
. However, if you publish and fire events from an object derived fromBase
,eventFacade
defaults totrue
.The
bubbles
field controls whether the event is permitted to bubble. By default,bubbles
istrue
. Ifbubbles
isfalse
, the event ignores the chain created byadd
Target()
. In that case, the only way to allow the event to be caught by another object is to setbroadcast
to1
or2
.The
preventedFn
field specifies a function to fire if and only ifev.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 Event
Target
,
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()
.
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.
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.
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:
Finds an object named
notifier
.Finds an object property on that object named
mark
.Extracts the value of that property. In this case,
function(ev) { ev.target.addClass(this.msgType); }
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.
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.
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.
Tony Gentilcore’s blog post “Finding memory leaks”.
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.
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.
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:
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:
All
on()
andafter()
event handlers on the target execute in order of specification, unless an event handler callsstopImmediatePropagation()
.If there is another event target to bubble to and no event handler has called
stopPropagation()
orstopImmediatePropagation()
yet, the event bubbles upward. Return to the previous step.The default behavior for that DOM event executes, unless an event handler calls
preventDefault()
.
For custom events, the overall execution sequence is:
All
on()
event handlers on the target execute in order of specification, unless an event handler callsstopImmediatePropagation()
.If there is another event target to bubble to and no event handler has called
stopPropagation()
orstopImmediatePropagation()
yet, the event bubbles upward. Return to the previous step.The default function for that custom event executes, unless an event handler calls
preventDefault()
.If
preventDefault()
was not called, bubbling starts again for all theafter()
handlers:All
after()
event handlers on the target execute in order of specification, unless an event handler callsstopImmediatePropagation()
orpreventDefault()
had been called earlier.If there is another event target to bubble to and no event handler has called
stopPropagation()
orstopImmediatePropagation()
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.
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 amouseup
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 thedocument
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 callnotifier.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.
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 andtextarea
s. Unlike the standard DOMchange
event,valueChange
fires when the field value changes, not when the field loses focus. Unlike theinput
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
andgesturemoveend
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 syntheticinvalid
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.
Each time your application creates a node with Y.Node.create()
, you want to log this
information to the browser console using
Y.log()
.
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()
.
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.
Get YUI 3 Cookbook now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.