/* Copyright (c) 2010, Yahoo! Inc. All rights reserved. Code licensed under the BSD License: http://developer.yahoo.com/yui/license.html version: 3.3.0 build: 3167 */ YUI.add('event-synthetic', function(Y) { /** * Define new DOM events that can be subscribed to from Nodes. * * @module event * @submodule event-synthetic */ var DOMMap = Y.Env.evt.dom_map, toArray = Y.Array, YLang = Y.Lang, isObject = YLang.isObject, isString = YLang.isString, query = Y.Selector.query, noop = function () {}; /** *

The triggering mechanism used by SyntheticEvents.

* *

Implementers should not instantiate these directly. Use the Notifier * provided to the event's implemented on(node, sub, notifier) or * delegate(node, sub, notifier, filter) methods.

* * @class SyntheticEvent.Notifier * @constructor * @param handle {EventHandle} the detach handle for the subscription to an * internal custom event used to execute the callback passed to * on(..) or delegate(..) * @param emitFacade {Boolean} take steps to ensure the first arg received by * the subscription callback is an event facade * @private * @since 3.2.0 */ function Notifier(handle, emitFacade) { this.handle = handle; this.emitFacade = emitFacade; } /** *

Executes the subscription callback, passing the firing arguments as the * first parameters to that callback. For events that are configured with * emitFacade=true, it is common practice to pass the triggering DOMEventFacade * as the first parameter. Barring a proper DOMEventFacade or EventFacade * (from a CustomEvent), a new EventFacade will be generated. In that case, if * fire() is called with a simple object, it will be mixed into the facade. * Otherwise, the facade will be prepended to the callback parameters.

* *

For notifiers provided to delegate logic, the first argument should be an * object with a "currentTarget" property to identify what object to * default as 'this' in the callback. Typically this is gleaned from the * DOMEventFacade or EventFacade, but if configured with emitFacade=false, an * object must be provided. In that case, the object will be removed from the * callback parameters.

* *

Additional arguments passed during event subscription will be * automatically added after those passed to fire().

* * @method fire * @param e {EventFacade|DOMEventFacade|Object|any} (see description) * @param arg* {any} additional arguments received by all subscriptions * @private */ Notifier.prototype.fire = function (e) { // first arg to delegate notifier should be an object with currentTarget var args = toArray(arguments, 0, true), handle = this.handle, ce = handle.evt, sub = handle.sub, thisObj = sub.context, delegate = sub.filter, event = e || {}; if (this.emitFacade) { if (!e || !e.preventDefault) { event = ce._getFacade(); if (isObject(e) && !e.preventDefault) { Y.mix(event, e, true); args[0] = event; } else { args.unshift(event); } } event.type = ce.type; event.details = args.slice(); if (delegate) { event.container = ce.host; } } else if (delegate && isObject(e) && e.currentTarget) { args.shift(); } sub.context = thisObj || event.currentTarget || ce.host; ce.fire.apply(ce, args); sub.context = thisObj; // reset for future firing }; /** *

Wrapper class for the integration of new events into the YUI event * infrastructure. Don't instantiate this object directly, use * Y.Event.define(type, config). See that method for details.

* *

Properties that MAY or SHOULD be specified in the configuration are noted * below and in the description of Y.Event.define.

* * @class SyntheticEvent * @constructor * @param cfg {Object} Implementation pieces and configuration * @since 3.1.0 * @in event-synthetic */ function SyntheticEvent() { this._init.apply(this, arguments); } Y.mix(SyntheticEvent, { Notifier: Notifier, /** * Returns the array of subscription handles for a node for the given event * type. Passing true as the third argument will create a registry entry * in the event system's DOM map to host the array if one doesn't yet exist. * * @method getRegistry * @param node {Node} the node * @param type {String} the event * @param create {Boolean} create a registration entry to host a new array * if one doesn't exist. * @return {Array} * @static * @protected * @since 3.2.0 */ getRegistry: function (node, type, create) { var el = node._node, yuid = Y.stamp(el), key = 'event:' + yuid + type + '_synth', events = DOMMap[yuid] || (DOMMap[yuid] = {}); if (!events[key] && create) { events[key] = { type : '_synth', fn : noop, capture : false, el : el, key : key, domkey : yuid, notifiers : [], detachAll : function () { var notifiers = this.notifiers, i = notifiers.length; while (--i >= 0) { notifiers[i].detach(); } } }; } return (events[key]) ? events[key].notifiers : null; }, /** * Alternate _delete() method for the CustomEvent object * created to manage SyntheticEvent subscriptions. * * @method _deleteSub * @param sub {Subscription} the subscription to clean up * @private * @since 3.2.0 */ _deleteSub: function (sub) { if (sub && sub.fn) { var synth = this.eventDef, method = (sub.filter) ? 'detachDelegate' : 'detach'; this.subscribers = {}; this.subCount = 0; synth[method](sub.node, sub, this.notifier, sub.filter); synth._unregisterSub(sub); delete sub.fn; delete sub.node; delete sub.context; } }, prototype: { constructor: SyntheticEvent, /** * Construction logic for the event. * * @method _init * @protected */ _init: function () { var config = this.publishConfig || (this.publishConfig = {}); // The notification mechanism handles facade creation this.emitFacade = ('emitFacade' in config) ? config.emitFacade : true; config.emitFacade = false; }, /** *

Implementers MAY provide this method definition.

* *

Implement this function if the event supports a different * subscription signature. This function is used by both * on() and delegate(). The second parameter * indicates that the event is being subscribed via * delegate().

* *

Implementations must remove extra arguments from the args list * before returning. The required args for on() * subscriptions are

*
[type, callback, target, context, argN...]
* *

The required args for delegate() * subscriptions are

* *
[type, callback, target, filter, context, argN...]
* *

The return value from this function will be stored on the * subscription in the '_extra' property for reference elsewhere.

* * @method processArgs * @param args {Array} parmeters passed to Y.on(..) or Y.delegate(..) * @param delegate {Boolean} true if the subscription is from Y.delegate * @return {any} */ processArgs: noop, /** *

Implementers MAY override this property.

* *

Whether to prevent multiple subscriptions to this event that are * classified as being the same. By default, this means the subscribed * callback is the same function. See the subMatch * method. Setting this to true will impact performance for high volume * events.

* * @property preventDups * @type {Boolean} * @default false */ //preventDups : false, /** *

Implementers SHOULD provide this method definition.

* * Implementation logic for subscriptions done via node.on(type, * fn) or Y.on(type, fn, target). This * function should set up the monitor(s) that will eventually fire the * event. Typically this involves subscribing to at least one DOM * event. It is recommended to store detach handles from any DOM * subscriptions to make for easy cleanup in the detach * method. Typically these handles are added to the sub * object. Also for SyntheticEvents that leverage a single DOM * subscription under the hood, it is recommended to pass the DOM event * object to notifier.fire(e). (The event name on the * object will be updated). * * @method on * @param node {Node} the node the subscription is being applied to * @param sub {Subscription} the object to track this subscription * @param notifier {SyntheticEvent.Notifier} call notifier.fire(..) to * trigger the execution of the subscribers */ on: noop, /** *

Implementers SHOULD provide this method definition.

* *

Implementation logic for detaching subscriptions done via * node.on(type, fn). This function should clean up any * subscriptions made in the on() phase.

* * @method detach * @param node {Node} the node the subscription was applied to * @param sub {Subscription} the object tracking this subscription * @param notifier {SyntheticEvent.Notifier} the Notifier used to * trigger the execution of the subscribers */ detach: noop, /** *

Implementers SHOULD provide this method definition.

* *

Implementation logic for subscriptions done via * node.delegate(type, fn, filter) or * Y.delegate(type, fn, container, filter). Like with * on() above, this function should monitor the environment * for the event being fired, and trigger subscription execution by * calling notifier.fire(e).

* *

This function receives a fourth argument, which is the filter * used to identify which Node's are of interest to the subscription. * The filter will be either a boolean function that accepts a target * Node for each hierarchy level as the event bubbles, or a selector * string. To translate selector strings into filter functions, use * Y.delegate.compileFilter(filter).

* * @method delegate * @param node {Node} the node the subscription is being applied to * @param sub {Subscription} the object to track this subscription * @param notifier {SyntheticEvent.Notifier} call notifier.fire(..) to * trigger the execution of the subscribers * @param filter {String|Function} Selector string or function that * accepts an event object and returns null, a Node, or an * array of Nodes matching the criteria for processing. * @since 3.2.0 */ delegate : noop, /** *

Implementers SHOULD provide this method definition.

* *

Implementation logic for detaching subscriptions done via * node.delegate(type, fn, filter) or * Y.delegate(type, fn, container, filter). This function * should clean up any subscriptions made in the * delegate() phase.

* * @method detachDelegate * @param node {Node} the node the subscription was applied to * @param sub {Subscription} the object tracking this subscription * @param notifier {SyntheticEvent.Notifier} the Notifier used to * trigger the execution of the subscribers * @param filter {String|Function} Selector string or function that * accepts an event object and returns null, a Node, or an * array of Nodes matching the criteria for processing. * @since 3.2.0 */ detachDelegate : noop, /** * Sets up the boilerplate for detaching the event and facilitating the * execution of subscriber callbacks. * * @method _on * @param args {Array} array of arguments passed to * Y.on(...) or Y.delegate(...) * @param delegate {Boolean} true if called from * Y.delegate(...) * @return {EventHandle} the detach handle for this subscription * @private * since 3.2.0 */ _on: function (args, delegate) { var handles = [], extra = this.processArgs(args, delegate), selector = args[2], method = delegate ? 'delegate' : 'on', nodes, handle; // Can't just use Y.all because it doesn't support window (yet?) nodes = (isString(selector)) ? query(selector) : toArray(selector); if (!nodes.length && isString(selector)) { handle = Y.on('available', function () { Y.mix(handle, Y[method].apply(Y, args), true); }, selector); return handle; } Y.Array.each(nodes, function (node) { var subArgs = args.slice(), filter; node = Y.one(node); if (node) { if (delegate) { filter = subArgs.splice(3, 1)[0]; } // (type, fn, el, thisObj, ...) => (fn, thisObj, ...) subArgs.splice(0, 4, subArgs[1], subArgs[3]); if (!this.preventDups || !this.getSubs(node, args,null,true)) { handle = this._getNotifier(node, subArgs, extra,filter); this[method](node, handle.sub, handle.notifier, filter); handles.push(handle); } } }, this); return (handles.length === 1) ? handles[0] : new Y.EventHandle(handles); }, /** * Creates a new Notifier object for use by this event's * on(...) or delegate(...) implementation. * * @method _getNotifier * @param node {Node} the Node hosting the event * @param args {Array} the subscription arguments passed to either * Y.on(...) or Y.delegate(...) * after running through processArgs(args) to * normalize the argument signature * @param extra {any} Extra data parsed from * processArgs(args) * @param filter {String|Function} the selector string or function * filter passed to Y.delegate(...) (not * present when called from Y.on(...)) * @return {SyntheticEvent.Notifier} * @private * @since 3.2.0 */ _getNotifier: function (node, args, extra, filter) { var dispatcher = new Y.CustomEvent(this.type, this.publishConfig), handle = dispatcher.on.apply(dispatcher, args), notifier = new Notifier(handle, this.emitFacade), registry = SyntheticEvent.getRegistry(node, this.type, true), sub = handle.sub; handle.notifier = notifier; sub.node = node; sub.filter = filter; if (extra) { this.applyArgExtras(extra, sub); } Y.mix(dispatcher, { eventDef : this, notifier : notifier, host : node, // I forget what this is for currentTarget: node, // for generating facades target : node, // for generating facades el : node._node, // For category detach _delete : SyntheticEvent._deleteSub }, true); registry.push(handle); return handle; }, /** *

Implementers MAY provide this method definition.

* *

Implement this function if you want extra data extracted during * processArgs to be propagated to subscriptions on a per-node basis. * That is to say, if you call Y.on('xyz', fn, xtra, 'div') * the data returned from processArgs will be shared * across the subscription objects for all the divs. If you want each * subscription to receive unique information, do that processing * here.

* *

The default implementation adds the data extracted by processArgs * to the subscription object as sub._extra.

* * @method applyArgExtras * @param extra {any} Any extra data extracted from processArgs * @param sub {Subscription} the individual subscription */ applyArgExtras: function (extra, sub) { sub._extra = extra; }, /** * Removes the subscription from the Notifier registry. * * @method _unregisterSub * @param sub {Subscription} the subscription * @private * @since 3.2.0 */ _unregisterSub: function (sub) { var notifiers = SyntheticEvent.getRegistry(sub.node, this.type), i; if (notifiers) { for (i = notifiers.length - 1; i >= 0; --i) { if (notifiers[i].sub === sub) { notifiers.splice(i, 1); break; } } } }, /** * Removes the subscription(s) from the internal subscription dispatch * mechanism. See SyntheticEvent._deleteSub. * * @method _detach * @param args {Array} The arguments passed to * node.detach(...) * @private * @since 3.2.0 */ _detach: function (args) { // Can't use Y.all because it doesn't support window (yet?) // TODO: Does Y.all support window now? var target = args[2], els = (isString(target)) ? query(target) : toArray(target), node, i, len, handles, j; // (type, fn, el, context, filter?) => (type, fn, context, filter?) args.splice(2, 1); for (i = 0, len = els.length; i < len; ++i) { node = Y.one(els[i]); if (node) { handles = this.getSubs(node, args); if (handles) { for (j = handles.length - 1; j >= 0; --j) { handles[j].detach(); } } } } }, /** * Returns the detach handles of subscriptions on a node that satisfy a * search/filter function. By default, the filter used is the * subMatch method. * * @method getSubs * @param node {Node} the node hosting the event * @param args {Array} the array of original subscription args passed * to Y.on(...) (before * processArgs * @param filter {Function} function used to identify a subscription * for inclusion in the returned array * @param first {Boolean} stop after the first match (used to check for * duplicate subscriptions) * @return {Array} detach handles for the matching subscriptions */ getSubs: function (node, args, filter, first) { var notifiers = SyntheticEvent.getRegistry(node, this.type), handles = [], i, len, handle; if (notifiers) { if (!filter) { filter = this.subMatch; } for (i = 0, len = notifiers.length; i < len; ++i) { handle = notifiers[i]; if (filter.call(this, handle.sub, args)) { if (first) { return handle; } else { handles.push(notifiers[i]); } } } } return handles.length && handles; }, /** *

Implementers MAY override this to define what constitutes a * "same" subscription. Override implementations should * consider the lack of a comparator as a match, so calling * getSubs() with no arguments will return all subs.

* *

Compares a set of subscription arguments against a Subscription * object to determine if they match. The default implementation * compares the callback function against the second argument passed to * Y.on(...) or node.detach(...) etc.

* * @method subMatch * @param sub {Subscription} the existing subscription * @param args {Array} the calling arguments passed to * Y.on(...) etc. * @return {Boolean} true if the sub can be described by the args * present * @since 3.2.0 */ subMatch: function (sub, args) { // Default detach cares only about the callback matching return !args[1] || sub.fn === args[1]; } } }, true); Y.SyntheticEvent = SyntheticEvent; /** *

Defines a new event in the DOM event system. Implementers are * responsible for monitoring for a scenario whereby the event is fired. A * notifier object is provided to the functions identified below. When the * criteria defining the event are met, call notifier.fire( [args] ); to * execute event subscribers.

* *

The first parameter is the name of the event. The second parameter is a * configuration object which define the behavior of the event system when the * new event is subscribed to or detached from. The methods that should be * defined in this configuration object are on, * detach, delegate, and detachDelegate. * You are free to define any other methods or properties needed to define your * event. Be aware, however, that since the object is used to subclass * SyntheticEvent, you should avoid method names used by SyntheticEvent unless * your intention is to override the default behavior.

* *

This is a list of properties and methods that you can or should specify * in the configuration object:

* *
*
on
*
function (node, subscription, notifier) The * implementation logic for subscription. Any special setup you need to * do to create the environment for the event being fired--E.g. native * DOM event subscriptions. Store subscription related objects and * state on the subscription object. When the * criteria have been met to fire the synthetic event, call * notifier.fire(e). See Notifier's fire() * method for details about what to pass as parameters.
* *
detach
*
function (node, subscription, notifier) The * implementation logic for cleaning up a detached subscription. E.g. * detach any DOM subscriptions added in on.
* *
delegate
*
function (node, subscription, notifier, filter) The * implementation logic for subscription via Y.delegate or * node.delegate. The filter is typically either a selector * string or a function. You can use * Y.delegate.compileFilter(selectorString) to create a * filter function from a selector string if needed. The filter function * expects an event object as input and should output either null, a * matching Node, or an array of matching Nodes. Otherwise, this acts * like on DOM event subscriptions. Store subscription * related objects and information on the subscription * object. When the criteria have been met to fire the synthetic event, * call notifier.fire(e) as noted above.
* *
detachDelegate
*
function (node, subscription, notifier) The * implementation logic for cleaning up a detached delegate subscription. * E.g. detach any DOM delegate subscriptions added in * delegate.
* *
publishConfig
*
(Object) The configuration object that will be used to instantiate * the underlying CustomEvent. See Notifier's fire method * for details.
* *
processArgs
*

function (argArray, fromDelegate) Optional method * to extract any additional arguments from the subscription * signature. Using this allows on or * delegate signatures like * node.on("hover", overCallback, * outCallback).

*

When processing an atypical argument signature, make sure the * args array is returned to the normal signature before returning * from the function. For example, in the "hover" example * above, the outCallback needs to be spliced * out of the array. The expected signature of the args array for * on() subscriptions is:

*
 *              [type, callback, target, contextOverride, argN...]
 *          
*

And for delegate():

*
 *              [type, callback, target, filter, contextOverride, argN...]
 *          
*

where target is the node the event is being * subscribed for. You can see these signatures documented for * Y.on() and Y.delegate() respectively.

*

Whatever gets returned from the function will be stored on the * subscription object under * subscription._extra.

*
subMatch
*
*

function (sub, args) Compares a set of * subscription arguments against a Subscription object to determine * if they match. The default implementation compares the callback * function against the second argument passed to * Y.on(...) or node.detach(...) etc.

*
*
* * @method Event.define * @param type {String} the name of the event * @param config {Object} the prototype definition for the new event (see above) * @param force {Boolean} override an existing event (use with caution) * @static * @return {SyntheticEvent} the subclass implementation instance created to * handle event subscriptions of this type * @for Event * @since 3.1.0 * @in event-synthetic */ Y.Event.define = function (type, config, force) { if (!config) { config = {}; } var eventDef = (isObject(type)) ? type : Y.merge({ type: type }, config), Impl, synth; if (force || !Y.Node.DOM_EVENTS[eventDef.type]) { Impl = function () { SyntheticEvent.apply(this, arguments); }; Y.extend(Impl, SyntheticEvent, eventDef); synth = new Impl(); type = synth.type; Y.Node.DOM_EVENTS[type] = Y.Env.evt.plugins[type] = { eventDef: synth, on: function () { return synth._on(toArray(arguments)); }, delegate: function () { return synth._on(toArray(arguments), true); }, detach: function () { return synth._detach(toArray(arguments)); } }; } return synth; }; }, '3.3.0' ,{requires:['node-base', 'event-custom']});