+ The YUI Custom Event System enables you to define and use events beyond + those available in the DOM — events that are specific to and of + interest in your own application. Custom Events are designed to work + much like DOM events. They can bubble, pass event facades, have their + propagation and default behaviors suppressed, etc. +
+ +
+ The APIs for working with custom events are provided by the
+ EventTarget class. All other infrastructure classes extend
+ EventTarget, but if you just need the custom event APIs, you can
+ extend or augment your classes with EventTarget directly.
+
DEPRECATION NOTE: The subscribers and afters properties which
+ used to sit on CustomEvent object instances have been deprecated and
+ removed for performance reasons as of the 3.7.0 release.
+
If you're referring to the subscribers or afters properties directly just
+ to access the set of subscribers, consider switching to the public getSubs()
+ method instead which hides you from the implementation details.
If you have a use case which requires you to access the above properties
+ directly you can set Y.CustomEvent.keepDeprecatedSubs to true, to restore
+ them, but you will incur a performance hit if you enable this flag.
+
Getting Started
+ ++To include the source files for EventTarget and its dependencies, first load +the YUI seed file if you haven't already loaded it. +
+ +<script src="http://yui.yahooapis.com/3.10.3/build/yui/yui-min.js"></script>+ + +
+Next, create a new YUI instance for your application and populate it with the
+modules you need by specifying them as arguments to the YUI().use() method.
+YUI will automatically load any dependencies required by the modules you
+specify.
+
<script>
+// Create a new YUI instance and populate it with the required modules.
+YUI().use('event-custom', function (Y) {
+ // EventTarget is available and ready for use. Add implementation
+ // code here.
+});
+</script>
+
+
+
+For more information on creating YUI instances and on the
+use() method, see the
+documentation for the YUI Global Object.
+
Video Overview
+ + + ++ This video from YUIConf 2009 gives a good overview of the YUI event system + API. The content covers DOM and custom events. Note: the synthetic event system was + updated since this video. +
+ +The Basics
+ +
+ You can get started using custom events and the EventTarget API without
+ creating your own class. The YUI instance (typically Y) is an
+ EventTarget, as is pretty much every other class in YUI. We'll go over
+ the basics using Y, then move into creating your own EventTargets.
+
+ If you've looked over the DOM
+ Event system docs, this should look very familiar. That's because
+ Nodes are also EventTargets.
+
Subscribing to Events
+ +// Custom events can have any name you want
+Y.on('anyOldNameYouWant', function () {
+ alert("Looky there!");
+});
+
+// Group subscriptions by passing an object or array to on()
+Y.on({
+ somethingImportant: updateCalendar,
+ birthday : eatCake,
+ weekendEnd : backToTheGrindstone
+});
+
+// Some events have prefixes
+Y.once("fuji:available", climb);
+
+// Custom events have distinct "after" moments
+Y.after("spa-category|pedicure", gelatoTime);
+
+
+
+ All EventTargets host methods
+ on,
+ once,
+ after, and
+ onceAfter.
+ Both once and onceAfter will automatically detach the subscription
+ after the callback is executed the first time. All subscription methods
+ return a subscription object called an
+ EventHandle. The
+ distinction between on and after is discussed in the
+ "after" phase section below.
+
Firing Events
+ +// All subscribers to the myapp:ready event will be executed
+Y.fire('myapp:ready');
+
+// Pass along relevant data to the callbacks as arguments
+Y.fire('birthday', {
+ name: 'Walt Disney',
+ birthdate: new Date(1901, 11, 5)
+});
+
+
+
+ Notify event subscribers by calling fire( eventName ), passing any
+ extra data about the event as additional arguments. Though fire
+ accepts any number of arguments, it is preferable to send all event data
+ in an object passed as the second argument. Doing so avoids locking your
+ code into a specific fire and callback signature.
+
// Subscription callbacks receive fire() arguments
+Y.on('birthday', function (name, birthdate) {
+ var age = new Date().getFullYear() - birthdate.getFullYear();
+ alert('Happy ' + age + ', ' + name + '!');
+});
+
+// Possible, but not recommended
+Y.fire('birthday', 'A. A. Milne', new Date(1882, 0, 18));
+
+// Instead, try to always pass only one object with all data
+Y.on('birthday', function (e) {
+ var age = new Date().getFullYear() - e.birthdate.getFullYear();
+ alert('Happy ' + age + ', ' + e.name + '!');
+});
+
+Y.fire('birthday', {
+ name: '"Uncle" Walt Whitman',
+ birthdate: new Date(1819, 4, 31)
+});
+
+
+
+ In the world of DOM events, the fire step is something the browser is
+ responsible for. A typical model involves the browser receiving keyboard
+ input from the user and firing keydown, keyup, and keypress events.
+ Custom events put your code in the position of dispatching events in
+ response to criteria that are relavant to your objects or application.
+
Callback arguments and event facades
+ +// Simple notification events don't send event objects, only fire() data
+Y.on('talkie', function (data) {
+ alert('(' + data.time + ') Walkie ' + data.message);
+ // data.preventDefault is not defined. data is just a plain object
+});
+
+Y.fire('talkie', {
+ message: 'roger, over.',
+ time: new Date()
+});
+
+// Events configured to emitFacade will send an event object, merged with
+// fire() data
+Y.publish('bill:due', {
+ emitFacade: true,
+ defaultFn : payUp
+});
+
+Y.on('bill:due', function (e) {
+ // Event facades have standard properties and methods as well as properties
+ // from payload data passed to fire()
+ if (e.payee === 'Rich Uncle Sal') {
+ e.preventDefault(); // the `payUp` method won't be called (Sal can wait)
+ }
+});
+
+// Objects passed as the second argument to fire() for facade events will have
+// their properties merged onto the facade received by the callback.
+Y.fire('bill:due', {
+ payee: 'Great Aunt Myra',
+ amount: 20
+});
+
+
+
+ Custom event callbacks are usually, but not always passed an
+ EventFacade as their
+ first argument. Custom events can be configured to send event facades or
+ only the data they were fired with. Always
+ passing event data in an object as the second argument to fire allows
+ you to write all your callbacks to expect event data as a single first
+ argument, whether it's an EventFacade or just a plain object. The
+ emitFacade and defaultFn configurations are detailed below, in
+ Publishing Events.
+
Detaching Event Subscriptions
+ +// Subscription methods return a subscription handle...
+var subscription = Y.on('myapp:ready', initComponents);
+
+// ...with a detach method
+subscription.detach();
+
+// Or detach by signature
+Y.detach('myapp:ready', initComponents);
+
+// Or by subscription category
+Y.on('spa-category|pedicure', gelatoTime);
+
+// Detach subscriptions to all events in the spa-category subscription group
+Y.detach('spa-category|*');
+
+
+
+ The preferred method of detaching subscriptions is to use the
+ EventHandle that is
+ returned from the subscription methods. Alternately you can use the
+ detach or
+ detachAll methods which work as described in the
+ Event user guide.
+
Extending EventTarget
+ +Add the EventTarget APIs onto any class using Y.augment().
function MyClass() {
+ /* insert constructor logic here */
+}
+
+MyClass.prototype = {
+ add: function (item) {
+ // You can assume the APIs are available from your class instances
+ this.fire("addItem", { item: item });
+ },
+ ...
+};
+
+// Make MyClass an EventTarget
+Y.augment(MyClass, Y.EventTarget);
+
+var instance = new MyClass();
+instance.on('addItem', function (e) {
+ alert("Yay, I'm adding " + e.item);
+});
+
+instance.add('a new item'); // ==> "Yay, I'm adding a new item"
+
+
+
+ Y.augment works like a lazy extend or a mixin. It adds the APIs to the
+ host class, but on the first call to any of the methods, it calls the
+ EventTarget constructor on the instance to make sure the necessary
+ internal objects are ready for use. If your class extends another,
+ augmenting it won't interfere with that inheritance hierarchy.
+
+ EventTargets can be set up with a number of default configurations for
+ the events they fire. Pass the defaults as the fourth argument to
+ Y.augment.
+
// Make all events fired from MyClass instances send an event facade
+Y.augment(MyClass, Y.EventTarget, true, null, {
+ emitFacade: true
+});
+
+
+Publishing Events
+ +
+ Some custom event configurations can be defaulted
+ from class configuration, but others need to be specified on a per-event
+ basis. Use the publish method to do this.
+
// publish takes an event name and a configuration object
+Y.publish('somethingSpecial', {
+ emitFacade: true,
+ broadcast: 2,
+ defaultFn: clapClapHallelujah,
+ fireOnce: true
+});
+
+
+Event Facades
+ +
+ The most common configuration for custom events is emitFacade. This is
+ because with the event facades comes a lot of additional functionality,
+ such as preventable default behaviors and bubbling.
+
function Recipe() {
+ // publishing events is typically done at instantiation
+ this.publish('add', {
+ emitFacade: true,
+ defaultFn: this._defAddFn
+ });
+}
+
+
+
+ Event facades mirror the event objects
+ you're familiar with from
+ the DOM. They have properties like e.type and e.target and
+ the same methods, allowing you to call e.preventDefault() to disable
+ default behavior you've configured for the event or e.stopPropagation()
+ to stop the event from bubbling.
+
var gruel = new Recipe();
+
+gruel.on('add', function (e) {
+ if (e.item === "brussel sprouts") {
+ // call e.preventDefault() just as you would for DOM events
+ e.preventDefault(); // brussel sprouts? eww!
+ }
+});
+
+
+
+ emitFacade is typically passed as a default configuration to Y.augment.
+ All other YUI infrastructure classes extend EventTarget and set
+ emitFacade to true for you.
+
Y.extend(MyClass, Y.Base, {
+ add: function (item) {
+ // This will fire with an event facade because Y.Base sets emitFacade to true
+ this.fire('addItem', { item: item });
+ },
+ ...
+});
+
+
+fireOnce Events
+
+
+ Important, typically system-level or lifecycle related events can be
+ configured as fireOnce. These events mimic things like window.onload
+ or the domready event.
+
Widget.prototype.render = function (where) {
+ ...
+
+ // Widget rendering only happens once
+ this.publish('render', {
+ defaultFn: this._defRenderFn,
+ fireOnce: true,
+ ...
+ });
+
+ this.fire('render', ...);
+};
+
+
+
+ After fireOnce events have been fire()d, any subsequent (late)
+ subscriptions are immediately executed. This can introduce race
+ conditions, however, since subscribers might expect to be called at a later
+ time, after the code that follows the subscription has also executed. In
+ this case, you can configure fireOnce events with the async flag
+ and post-fire subscriptions will be executed in a setTimeout,
+ allowing all subsequent code to run before the late subscriber is notified.
+
// BEFORE
+Y.publish('myapp:ready', {
+ fireOnce: true
+});
+
+// ... elsewhere in the code
+// If myapp:ready has been fired, setStuffUp executes right now, but might
+// expect MyApp.Stuff to be created already. So, boom.
+Y.on('myapp:ready', setStuffUp);
+
+MyApp.Stuff = {};
+
+// AFTER
+Y.publish('myapp:ready', {
+ fireOnce: true,
+ async : true
+});
+
+// ... elsewhere in the code
+// Even if myapp:ready has been fired, setStuffUp will execute later. So, no boom
+Y.on('myapp:ready', setStuffUp);
+
+MyApp.Stuff = {};
+
+
+Bubbling Events
+ +
+ Events that are configured with emitFacade support bubbling to other
+ EventTargets, allowing you to subscribe to them from other objects, much
+ like DOM event bubbling. Add other EventTargets to an instance's bubble
+ path with addTarget.
+
function LeafNode() { ... }
+
+LeafNode.prototype.rename = function (newName) {
+ var oldName = this.name;
+ this.name = newName;
+
+ this.fire("update", {
+ prevVal: oldName,
+ newVal : newName
+ });
+};
+
+function TreeNode() { ... }
+
+TreeNode.prototype.add = function (node) {
+ this._items.push(node);
+
+ // The new child node's events will bubble to this TreeNode
+ node.addTarget(this);
+};
+
+Y.augment(LeafNode, Y.EventTarget, true, null, { emitFacade: true });
+Y.augment(TreeNode, Y.EventTarget, true, null, { emitFacade: true });
+
+var rootNode = new TreeNode("ROOT"),
+ branchA = new TreeNode("branchA"),
+ leaf1 = new LeafNode("leaf1");
+
+rootNode.add(branchA); // ROOT
+rootNode.add( new LeafNode("leaf2") ); // / \
+ // branchA leaf2
+branchA.add(leaf1); // / \
+branchA.add( new LeafNode("leaf3") ); // leaf1 leaf3
+
+// Subscribe to 'update' events from any leaf or tree node under root
+rootNode.on('update', function (e) {
+ alert(e.prevVal + " has been renamed " + e.newVal);
+});
+
+leaf1.rename("Flower!"); // ==> "leaf1 has been renamed Flower!"
+
+
+
+Event Prefixes
+ +
+ Individual events or all events fired by an EventTarget can be configured
+ to include a prefix to help filter subscriptions to common event names by
+ their origin. Prefixed event names look like 'prefix:eventName'.
+
+ Taking the code snippet above, configuring a default
+ prefix while augmenting the classes will allow for subscription to
+ only LeafNode updates.
+
// All events fired by LeafNode instances will be prefixed with "leaf:"
+Y.augment(LeafNode, Y.EventTarget, true, null, {
+ emitFacade: true,
+ prefix: 'leaf'
+});
+// ...and for TreeNodes, "tree:"
+Y.augment(TreeNode, Y.EventTarget, true, null, {
+ emitFacade: true,
+ prefix: 'tree'
+});
+
+...
+
+// Listen specifically for changes from LeafNodes
+rootNode.on('leaf:update', function (e) {
+ alert(e.prevVal + " has been renamed " + e.newVal);
+});
+
+leaf1.rename("Flower!"); // ==> "leaf1 has been renamed Flower!"
+branchA.rename("Chewbacca!"); // (nothing)
+
+
+
+ Subscribing with prefixes is similar to
+ using DOM event delegation, though it
+ is done using on() rather than delegate().
+
+ Optionally, you can omit the prefix when subscribing on the object that + fires the event. +
+ +// prefix is optional when subscribing on the firing object...
+leaf1.on('leaf:update', worksJustLike);
+leaf1.on('update', function (e) {
+ e.type; // 'leaf:update' -- the event type will remain prefixed
+ ...
+});
+
+// ...but prefixes are required from other objects
+rootNode.on('update', function (e) {
+ // will not capture leaf:update events
+});
+
+
+
+ Subscribe to all events of a specific type, regardless of prefix, using the
+ wildcard prefix *:eventName.
+
// Execute the callback if either the group object or one of its items fires an
+// `update` event
+rootNode.on('*:update', function (e) {
+ switch (e.type) {
+ case "leaf:update": ...
+ case "tree:update": ...
+ }
+});
+
+
+Adding Default Behavior
+ ++ Custom events can be bound to behaviors just like DOM events (e.g. clicking + on a link causes navigation to a new page). This is especially useful when + doing + CRUD + operations that you want to expose to other objects in your system to + prevent, alter, or enhance. +
+ +
+ Add a default behavior to an event by configuring the event's defaultFn.
+ By convention, default functions are named _def(the name of the event)Fn.
+
function TreeNode(name) {
+ this.name = name;
+ this._items = [];
+
+ // Event publishing is typically done during instantiation
+ this.publish('add', {
+ defaultFn: this._defAddFn
+ });
+}
+
+// Adding a child node is an interesting mutation operation. Move the mutation
+// logic from the method to a mutation event's default behavior
+TreeNode.prototype.add = function (node) {
+ this.fire('add', { newNode: node });
+};
+
+// Default functions receive the event facade like other subscribers
+TreeNode.prototype._defAddFn = function (e) {
+ this._items.push(e.newNode);
+
+ e.newNode.addTarget(this);
+};
+
+...
+
+branchA.add(leaf1); // without 'add' subscriptions, the behavior is the same
+
+
+
+ Unless configured with preventable: false, default behaviors can be
+ disabled with e.preventDefault() just like the DOM. Unlike their DOM
+ counterparts, though, event subscribers can change facade
+ properties to alter the default behavior by way of effectively changing
+ its input.
+
TreeNode.prototype.add = function (node) {
+ this.fire('add', {
+ newNode: node,
+ bubbleEvents: true
+ });
+};
+
+// Default functions receive the event facade like other subscribers
+TreeNode.prototype._defAddFn = function (e) {
+ this._items.push(e.newNode);
+
+ if (e.bubbleEvents) {
+ e.newNode.addTarget(this);
+ }
+};
+
+...
+
+// You can prevent default behavior from anywhere along the bubble path
+rootNode.on('tree:add', function (e) {
+ if (e.newNode.name === "Leafy") {
+ e.preventDefault();
+ } else if (e.newNode.name === "James Bond") {
+ e.bubbleEvents = false; // Shhhh
+ }
+});
+
+rootNode.add( new LeafNode("Leafy") ); // Node NOT added
+rootNode.add( new LeafNode("James Bond") ); // Node added without event bubbling
+
+
+Broadcasting Events to Y or Between YUI instances
+ ++ Event broadcasting is very similar to bubbling, but with some important + distinctions: +
+ +-
+
-
+ Broadcasting is specific to the YUI instance and the
Y.Globalshared +EventTarget+
+ - Events don't need to be configured with
emitFacadeto broadcast
+ - Broadcasting happens after the default behavior, which also means... +
- Event behavior can't be prevented from broadcast subscribers +
- Broadcast can be defaulted for all events for an
EventTarget
+
+ Broadcasting is essentially a "fast track" bubbling configuration allowing
+ you to specify that events can be subscribed to from the YUI instance (with
+ broadcast: 1) or from Y.Global (with broadcast: 2).
+
// All events from instances of MyClass can be subscribed from Y.on()
+Y.augment(MyClass, Y.EventTarget, true, null, {
+ emitFacade: true,
+ prefix: 'awesome',
+ broadcast: 1
+});
+
+// Respond to a 'thing' event from any instance of MyClass in the YUI sandbox
+Y.on('awesome:song', partyOn);
+
+var instance = new MyClass()
+
+instance.fire("song", { which: "Bohemian Rhapsody", whom: "Wayne" });
+
+
+
+ Y.Global is an EventTarget that is shared between all YUI instances,
+ allowing cross-sandbox communication. To avoid feedback loops, it's best
+ to add an instance identity to outgoing events and only respond to
+ incoming events from other identities.
+
YUI().use('node', 'event-custom', function (Y) {
+ var id = "Alpha Beta Base"; // probably Y.guid() would be safer
+
+ Y.Global.on('message', function (e) {
+ if (e.origin !== id) {
+ alert("message received from " + e.origin + ": " + e.message);
+
+ murdock.fire("message", {
+ message: "We'll get you down. And down safe.",
+ origin: id
+ });
+ }
+ });
+
+ function Character() {
+ this.publish('message', { broadcast: 2 });
+ ...
+ }
+
+ Y.augment(Character, Y.EventTarget, true, null, {
+ emitFacade: true
+ });
+
+ var murdock = new Character();
+
+ Y.one('#status').on('click', function () {
+ murdock.fire("message", {
+ message: "You're coming in too fast!",
+ origin: id
+ });
+ });
+});
+
+YUI().use('node', 'event-custom', function (OtherY) {
+ var id = "Lunar Shuttle";
+
+ OtherY.Global.on('message', function (e) {
+ if (e.origin !== id) {
+ alert("message received from " + e.origin + ": " + e.message);
+ }
+ });
+
+ function Character() {
+ this.publish('message', { broadcast: 2 });
+ }
+
+ OtherY.augment(Character, OtherY.EventTarget, true, null, {
+ emitFacade: true
+ });
+
+ var striker = new Character()
+
+ OtherY.one('#report').on('click', function () {
+ striker.fire("message", {
+ message: "She's beginning to crack up",
+ origin: id
+ });
+ });
+});
+
+
+
+
+
+
+
+
+
+Available Event Configurations and Defaults
+ +
+ Events can be configured with the following properties. Properties marked
+ as "Class Configurable" can be passed to the EventTarget constructor
+ configuration to default for all events.
+
| Configuration | +Description | +Default | +Class Configurable? | +
|---|---|---|---|
prefix |
+
+ e.type will always include the configured prefix.
+ Details above.
+ |
+ (empty) | +YES | +
context |
+
+ The default this object to execute callbacks with. Rarely set.
+ |
+ The instance | +YES | +
emitFacade |
+
+ If true, sends event facades to callbacks, allows bubbling and
+ default functions, etc. This is commonly set to true for a class.
+ Details above.
+ |
+ false |
+ YES | +
fireOnce |
+
+ If true, events will only fire once. Subscriptions made after
+ firing will be immediately executed.
+ Details above.
+ |
+ false |
+ YES | +
broadcast |
+
+ Details above. Fire the event from:
+
|
+ 0 | +YES | +
bubbles |
+
+ For events configured to emitFacade allow bubbling events to
+ other EventTargets.
+ |
+ true |
+ YES | +
defaultFn |
+
+ Behavior associated with the event. Usually this is preventable
+ (see preventable below). Details above.
+ |
+ (none) | ++ |
preventable |
+
+ If set to false, e.preventDefault() will not disable execution
+ of the event's defaultFn.
+ |
+ true |
+ + |
preventedFn |
+
+
+ Behavior associated with the event when Incompatible with |
+ (none) | ++ |
stoppedFn |
+
+ Behavior associated with the event when e.stopPropagation() is
+ called from a subscriber. Seldom used.
+ |
+ (none) | ++ |
async |
+
+ Only applicable to events also configured with fireOnce: true.
+ If true, new subscriptions to this event after it has already
+ been fired will be queued to execute in a setTimeout instead of
+ immediately (synchronously).
+ |
+ false | ++ |
The "after" phase
+ +
+ Unlike DOM events, custom events also expose an "after" phase that
+ corresponds to the time immediately after an event's default behavior executes. Subscribe to an event's
+ "after" phase with the after(...) method. The signature is the same as
+ on(...).
+
rootNode.after('tree:add', calc.updateTotals, calc);
+
+
+
+ The primary benefit of using after() subscriptions over on()
+ subscriptions is that if any on() subscribers call e.preventDefault(),
+ neither the event's configured defaultFn nor the after()
+ subscribers will be executed. If an after() subscription is
+ executed, you know that the defaultFn did as well.
+
+ Use after() to subscribe to events with a default behavior when
+ you want to react to the event with a side effect.
+
+ Use on() to subscribe to events if you need to prevent or alter
+ the default behavior or if they don't have default behavior.
+
Event Lifecycle
+ +The order of operations when firing an event is as follows:
+ +Simple Events (no facade)
+ +-
+
on()subscribers are executed
+ after()subscribers are executed
+ Y.on()broadcast subscribers are executed.
+ Y.after()broadcast subscribers are executed.
+ Y.Global.on()broadcast subscribers are executed.
+ Y.Global.after()broadcast subscribers are executed.
+
+ If an on() or after() subscriber returns false, no more subscribers
+ will be notified.
+
Complex Events (with facade)
+ +-
+
on()subscribers are executed
+ -
+
on()subscribers for each bubble target and their respective targets + are executed until all targets' bubble paths are walked or a subscriber + stops the propagation of the event. +
+ -
+ If the event was prevented, any configured
preventedFnwill execute. +
+ - If not prevented, any configured
defaultFnwill execute.
+ - If bubbling was stopped, any configured
stoppedFnwill execute.
+ Y.on()broadcast subscribers are executed.
+ Y.after()broadcast subscribers are executed.
+ Y.Global.on()broadcast subscribers are executed.
+ Y.Global.after()broadcast subscribers are executed.
+ after()subscribers are executed.
+ -
+
after()subscribers for each bubble target and their respective + targets are executed. +
+
+ The flow can be interrupted by on() subscribers doing any of these
+ things:
+
-
+
e.preventDefault()
+ -
+
-
+
- The
defaultFnwill not be executed
+ - The
preventedFnwill execute
+ - No
after()subscriptions will be executed
+
+ - The
e.stopPropagation()
+ -
+
-
+
- The remainder of subscribers at this
EventTargetWILL execute
+ - No bubble targets of this
EventTargetwill be notified
+ - The
stoppedFnwill execute
+ - The
defaultFnandafter()subscribers will execute
+
+ - The remainder of subscribers at this
e.stopImmediatePropagation()
+ -
+ Same as
e.stopPropagation()except no more subscribers at this +EventTargetwill execute. +
+ e.halt()
+ -
+ Same as
e.preventDefault()pluse.stopPropagation(). +
+ e.halt(true)
+ -
+ Same as
e.preventDefault()pluse.stopImmediatePropagation(). +
+ return false
+ - Same as
e.halt(true). Not recommended. Use the API methods.
+
