/* YUI 3.17.2 (build 9c3c78e) Copyright 2014 Yahoo! Inc. All rights reserved. Licensed under the BSD License. http://yuilibrary.com/license/ */ YUI.add('event-synthetic', function (Y, NAME) { /** * Define new DOM events that can be subscribed to from Nodes. * * @module event * @submodule event-synthetic */ var CustomEvent = Y.CustomEvent, DOMMap = Y.Env.evt.dom_map, toArray = Y.Array, YLang = Y.Lang, isObject = YLang.isObject, isString = YLang.isString, isArray = YLang.isArray, 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.
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 {EventFacade|DOMEventFacade|any} e (see description) * @param {any[]} [arg*] 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 || {}, ret; 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; ret = ce.fire.apply(ce, args); // have to handle preventedFn and stoppedFn manually because // Notifier CustomEvents are forced to emitFacade=false if (e.prevented && ce.preventedFn) { ce.preventedFn.apply(ce, args); } if (e.stopped && ce.stoppedFn) { ce.stoppedFn.apply(ce, args); } sub.context = thisObj; // reset for future firing // to capture callbacks that return false to stopPropagation. // Useful for delegate implementations return ret; }; /** * Manager object for synthetic event subscriptions to aggregate multiple synths on the * same node without colliding with actual DOM subscription entries in the global map of * DOM subscriptions. Also facilitates proper cleanup on page unload. * * @class SynthRegistry * @constructor * @param el {HTMLElement} the DOM element * @param yuid {String} the yuid stamp for the element * @param key {String} the generated id token used to identify an event type + * element in the global DOM subscription map. * @private */ function SynthRegistry(el, yuid, key) { this.handles = []; this.el = el; this.key = key; this.domkey = yuid; } SynthRegistry.prototype = { constructor: SynthRegistry, // A few object properties to fake the CustomEvent interface for page // unload cleanup. DON'T TOUCH! type : '_synth', fn : noop, capture : false, /** * Adds a subscription from the Notifier registry. * * @method register * @param handle {EventHandle} the subscription * @since 3.4.0 */ register: function (handle) { handle.evt.registry = this; this.handles.push(handle); }, /** * Removes the subscription from the Notifier registry. * * @method _unregisterSub * @param sub {Subscription} the subscription * @since 3.4.0 */ unregister: function (sub) { var handles = this.handles, events = DOMMap[this.domkey], i; for (i = handles.length - 1; i >= 0; --i) { if (handles[i].sub === sub) { handles.splice(i, 1); break; } } // Clean up left over objects when there are no more subscribers. if (!handles.length) { delete events[this.key]; if (!Y.Object.size(events)) { delete DOMMap[this.domkey]; } } }, /** * Used by the event system's unload cleanup process. When navigating * away from the page, the event system iterates the global map of element * subscriptions and detaches everything using detachAll(). Normally, * the map is populated with custom events, so this object needs to * at least support the detachAll method to duck type its way to * cleanliness. * * @method detachAll * @private * @since 3.4.0 */ detachAll : function () { var handles = this.handles, i = handles.length; while (--i >= 0) { handles[i].detach(); } } }; /** *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
.
_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 = [];
if (CustomEvent.keepDeprecatedSubs) {
this.subscribers = {};
}
synth[method](sub.node, sub, this.notifier, sub.filter);
this.registry.unregister(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.
Implementers SHOULD provide this method definition.
* * Implementation logic for subscriptions done vianode.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.
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)
.
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.
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 = [],
originalArgs = args.slice(),
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 || Y.one(Y.config.win));
if (!nodes.length && isString(selector)) {
handle = Y.on('available', function () {
Y.mix(handle, Y[method].apply(Y, originalArgs), 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))
{
handles.push(this._subscribe(node, method, subArgs, extra, filter));
}
}
}, 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
* and register the custom event proxy in the DOM system for cleanup.
*
* @method _subscribe
* @param node {Node} the Node hosting the event
* @param method {String} "on" or "delegate"
* @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 {EventHandle}
* @private
* @since 3.2.0
*/
_subscribe: function (node, method, 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;
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);
handle.notifier = notifier;
registry.register(handle);
// Call the implementation's "on" or "delegate" method
this[method](node, sub, notifier, filter);
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
.
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 {EventHandle[]} detach handles for the matching subscriptions
*/
getSubs: function (node, args, filter, first) {
var registry = SyntheticEvent.getRegistry(node, this.type),
handles = [],
allHandles, i, len, handle;
if (registry) {
allHandles = registry.handles;
if (!filter) {
filter = this.subMatch;
}
for (i = 0, len = allHandles.length; i < len; ++i) {
handle = allHandles[i];
if (filter.call(this, handle.sub, args)) {
if (first) {
return handle;
} else {
handles.push(allHandles[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.
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
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 splice
d
* 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.