2
Copyright (c) 2010, Yahoo! Inc. All rights reserved.
3
Code licensed under the BSD License:
4
http://developer.yahoo.com/yui/license.html
8
YUI.add('event-synthetic', function(Y) {
11
* Define new DOM events that can be subscribed to from Nodes.
14
* @submodule event-synthetic
16
var DOMMap = Y.Env.evt.dom_map,
19
isObject = YLang.isObject,
20
isString = YLang.isString,
21
query = Y.Selector.query,
22
noop = function () {};
25
* <p>The triggering mechanism used by SyntheticEvents.</p>
27
* <p>Implementers should not instantiate these directly. Use the Notifier
28
* provided to the event's implemented <code>on(node, sub, notifier)</code> or
29
* <code>delegate(node, sub, notifier, filter)</code> methods.</p>
31
* @class SyntheticEvent.Notifier
33
* @param handle {EventHandle} the detach handle for the subscription to an
34
* internal custom event used to execute the callback passed to
35
* on(..) or delegate(..)
36
* @param emitFacade {Boolean} take steps to ensure the first arg received by
37
* the subscription callback is an event facade
41
function Notifier(handle, emitFacade) {
43
this.emitFacade = emitFacade;
47
* <p>Executes the subscription callback, passing the firing arguments as the
48
* first parameters to that callback. For events that are configured with
49
* emitFacade=true, it is common practice to pass the triggering DOMEventFacade
50
* as the first parameter. Barring a proper DOMEventFacade or EventFacade
51
* (from a CustomEvent), a new EventFacade will be generated. In that case, if
52
* fire() is called with a simple object, it will be mixed into the facade.
53
* Otherwise, the facade will be prepended to the callback parameters.</p>
55
* <p>For notifiers provided to delegate logic, the first argument should be an
56
* object with a "currentTarget" property to identify what object to
57
* default as 'this' in the callback. Typically this is gleaned from the
58
* DOMEventFacade or EventFacade, but if configured with emitFacade=false, an
59
* object must be provided. In that case, the object will be removed from the
60
* callback parameters.</p>
62
* <p>Additional arguments passed during event subscription will be
63
* automatically added after those passed to fire().</p>
66
* @param e {EventFacade|DOMEventFacade|Object|any} (see description)
67
* @param arg* {any} additional arguments received by all subscriptions
70
Notifier.prototype.fire = function (e) {
71
// first arg to delegate notifier should be an object with currentTarget
72
var args = toArray(arguments, 0, true),
76
thisObj = sub.context,
77
delegate = sub.filter,
80
if (this.emitFacade) {
81
if (!e || !e.preventDefault) {
82
event = ce._getFacade();
84
if (isObject(e) && !e.preventDefault) {
85
Y.mix(event, e, true);
93
event.details = args.slice();
96
event.container = ce.host;
98
} else if (delegate && isObject(e) && e.currentTarget) {
102
sub.context = thisObj || event.currentTarget || ce.host;
103
ce.fire.apply(ce, args);
104
sub.context = thisObj; // reset for future firing
109
* <p>Wrapper class for the integration of new events into the YUI event
110
* infrastructure. Don't instantiate this object directly, use
111
* <code>Y.Event.define(type, config)</code>. See that method for details.</p>
113
* <p>Properties that MAY or SHOULD be specified in the configuration are noted
114
* below and in the description of <code>Y.Event.define</code>.</p>
116
* @class SyntheticEvent
118
* @param cfg {Object} Implementation pieces and configuration
120
* @in event-synthetic
122
function SyntheticEvent() {
123
this._init.apply(this, arguments);
126
Y.mix(SyntheticEvent, {
130
* Returns the array of subscription handles for a node for the given event
131
* type. Passing true as the third argument will create a registry entry
132
* in the event system's DOM map to host the array if one doesn't yet exist.
134
* @method getRegistry
135
* @param node {Node} the node
136
* @param type {String} the event
137
* @param create {Boolean} create a registration entry to host a new array
138
* if one doesn't exist.
144
getRegistry: function (node, type, create) {
147
key = 'event:' + yuid + type + '_synth',
148
events = DOMMap[yuid] || (DOMMap[yuid] = {});
150
if (!events[key] && create) {
160
detachAll : function () {
161
var notifiers = this.notifiers,
162
i = notifiers.length;
165
notifiers[i].detach();
171
return (events[key]) ? events[key].notifiers : null;
175
* Alternate <code>_delete()</code> method for the CustomEvent object
176
* created to manage SyntheticEvent subscriptions.
179
* @param sub {Subscription} the subscription to clean up
183
_deleteSub: function (sub) {
185
var synth = this.eventDef,
186
method = (sub.filter) ? 'detachDelegate' : 'detach';
188
this.subscribers = {};
191
synth[method](sub.node, sub, this.notifier, sub.filter);
192
synth._unregisterSub(sub);
201
constructor: SyntheticEvent,
204
* Construction logic for the event.
210
var config = this.publishConfig || (this.publishConfig = {});
212
// The notification mechanism handles facade creation
213
this.emitFacade = ('emitFacade' in config) ?
216
config.emitFacade = false;
220
* <p>Implementers MAY provide this method definition.</p>
222
* <p>Implement this function if the event supports a different
223
* subscription signature. This function is used by both
224
* <code>on()</code> and <code>delegate()</code>. The second parameter
225
* indicates that the event is being subscribed via
226
* <code>delegate()</code>.</p>
228
* <p>Implementations must remove extra arguments from the args list
229
* before returning. The required args for <code>on()</code>
230
* subscriptions are</p>
231
* <pre><code>[type, callback, target, context, argN...]</code></pre>
233
* <p>The required args for <code>delegate()</code>
234
* subscriptions are</p>
236
* <pre><code>[type, callback, target, filter, context, argN...]</code></pre>
238
* <p>The return value from this function will be stored on the
239
* subscription in the '_extra' property for reference elsewhere.</p>
241
* @method processArgs
242
* @param args {Array} parmeters passed to Y.on(..) or Y.delegate(..)
243
* @param delegate {Boolean} true if the subscription is from Y.delegate
249
* <p>Implementers MAY override this property.</p>
251
* <p>Whether to prevent multiple subscriptions to this event that are
252
* classified as being the same. By default, this means the subscribed
253
* callback is the same function. See the <code>subMatch</code>
254
* method. Setting this to true will impact performance for high volume
257
* @property preventDups
261
//preventDups : false,
264
* <p>Implementers SHOULD provide this method definition.</p>
266
* Implementation logic for subscriptions done via <code>node.on(type,
267
* fn)</code> or <code>Y.on(type, fn, target)</code>. This
268
* function should set up the monitor(s) that will eventually fire the
269
* event. Typically this involves subscribing to at least one DOM
270
* event. It is recommended to store detach handles from any DOM
271
* subscriptions to make for easy cleanup in the <code>detach</code>
272
* method. Typically these handles are added to the <code>sub</code>
273
* object. Also for SyntheticEvents that leverage a single DOM
274
* subscription under the hood, it is recommended to pass the DOM event
275
* object to <code>notifier.fire(e)</code>. (The event name on the
276
* object will be updated).
279
* @param node {Node} the node the subscription is being applied to
280
* @param sub {Subscription} the object to track this subscription
281
* @param notifier {SyntheticEvent.Notifier} call notifier.fire(..) to
282
* trigger the execution of the subscribers
287
* <p>Implementers SHOULD provide this method definition.</p>
289
* <p>Implementation logic for detaching subscriptions done via
290
* <code>node.on(type, fn)</code>. This function should clean up any
291
* subscriptions made in the <code>on()</code> phase.</p>
294
* @param node {Node} the node the subscription was applied to
295
* @param sub {Subscription} the object tracking this subscription
296
* @param notifier {SyntheticEvent.Notifier} the Notifier used to
297
* trigger the execution of the subscribers
302
* <p>Implementers SHOULD provide this method definition.</p>
304
* <p>Implementation logic for subscriptions done via
305
* <code>node.delegate(type, fn, filter)</code> or
306
* <code>Y.delegate(type, fn, container, filter)</code>. Like with
307
* <code>on()</code> above, this function should monitor the environment
308
* for the event being fired, and trigger subscription execution by
309
* calling <code>notifier.fire(e)</code>.</p>
311
* <p>This function receives a fourth argument, which is the filter
312
* used to identify which Node's are of interest to the subscription.
313
* The filter will be either a boolean function that accepts a target
314
* Node for each hierarchy level as the event bubbles, or a selector
315
* string. To translate selector strings into filter functions, use
316
* <code>Y.delegate.compileFilter(filter)</code>.</p>
319
* @param node {Node} the node the subscription is being applied to
320
* @param sub {Subscription} the object to track this subscription
321
* @param notifier {SyntheticEvent.Notifier} call notifier.fire(..) to
322
* trigger the execution of the subscribers
323
* @param filter {String|Function} Selector string or function that
324
* accepts an event object and returns null, a Node, or an
325
* array of Nodes matching the criteria for processing.
331
* <p>Implementers SHOULD provide this method definition.</p>
333
* <p>Implementation logic for detaching subscriptions done via
334
* <code>node.delegate(type, fn, filter)</code> or
335
* <code>Y.delegate(type, fn, container, filter)</code>. This function
336
* should clean up any subscriptions made in the
337
* <code>delegate()</code> phase.</p>
339
* @method detachDelegate
340
* @param node {Node} the node the subscription was applied to
341
* @param sub {Subscription} the object tracking this subscription
342
* @param notifier {SyntheticEvent.Notifier} the Notifier used to
343
* trigger the execution of the subscribers
344
* @param filter {String|Function} Selector string or function that
345
* accepts an event object and returns null, a Node, or an
346
* array of Nodes matching the criteria for processing.
349
detachDelegate : noop,
352
* Sets up the boilerplate for detaching the event and facilitating the
353
* execution of subscriber callbacks.
356
* @param args {Array} array of arguments passed to
357
* <code>Y.on(...)</code> or <code>Y.delegate(...)</code>
358
* @param delegate {Boolean} true if called from
359
* <code>Y.delegate(...)</code>
360
* @return {EventHandle} the detach handle for this subscription
364
_on: function (args, delegate) {
367
method = delegate ? 'delegate' : 'on',
370
// Can't just use Y.all because it doesn't support window (yet?)
371
nodes = (isString(selector)) ? query(selector) : toArray(selector);
373
if (!nodes.length && isString(selector)) {
374
handle = Y.on('available', function () {
375
Y.mix(handle, Y[method].apply(Y, args), true);
381
Y.each(nodes, function (node) {
382
var subArgs = args.slice(),
388
extra = this.processArgs(subArgs, delegate);
391
filter = subArgs.splice(3, 1)[0];
394
// (type, fn, el, thisObj, ...) => (fn, thisObj, ...)
395
subArgs.splice(0, 4, subArgs[1], subArgs[3]);
397
if (!this.preventDups || !this.getSubs(node, args,null,true)) {
398
handle = this._getNotifier(node, subArgs, extra,filter);
400
this[method](node, handle.sub, handle.notifier, filter);
402
handles.push(handle);
407
return (handles.length === 1) ?
409
new Y.EventHandle(handles);
413
* Creates a new Notifier object for use by this event's
414
* <code>on(...)</code> or <code>delegate(...)</code> implementation.
416
* @method _getNotifier
417
* @param node {Node} the Node hosting the event
418
* @param args {Array} the subscription arguments passed to either
419
* <code>Y.on(...)</code> or <code>Y.delegate(...)</code>
420
* after running through <code>processArgs(args)</code> to
421
* normalize the argument signature
422
* @param extra {any} Extra data parsed from
423
* <code>processArgs(args)</code>
424
* @param filter {String|Function} the selector string or function
425
* filter passed to <code>Y.delegate(...)</code> (not
426
* present when called from <code>Y.on(...)</code>)
427
* @return {SyntheticEvent.Notifier}
431
_getNotifier: function (node, args, extra, filter) {
432
var dispatcher = new Y.CustomEvent(this.type, this.publishConfig),
433
handle = dispatcher.on.apply(dispatcher, args),
434
notifier = new Notifier(handle, this.emitFacade),
435
registry = SyntheticEvent.getRegistry(node, this.type, true),
438
handle.notifier = notifier;
447
host : node, // I forget what this is for
448
currentTarget: node, // for generating facades
449
target : node, // for generating facades
450
el : node._node, // For category detach
452
_delete : SyntheticEvent._deleteSub
455
registry.push(handle);
461
* Removes the subscription from the Notifier registry.
463
* @method _unregisterSub
464
* @param sub {Subscription} the subscription
468
_unregisterSub: function (sub) {
469
var notifiers = SyntheticEvent.getRegistry(sub.node, this.type),
473
for (i = notifiers.length - 1; i >= 0; --i) {
474
if (notifiers[i].sub === sub) {
475
notifiers.splice(i, 1);
483
* Removes the subscription(s) from the internal subscription dispatch
484
* mechanism. See <code>SyntheticEvent._deleteSub</code>.
487
* @param args {Array} The arguments passed to
488
* <code>node.detach(...)</code>
492
_detach: function (args) {
493
// Can't use Y.all because it doesn't support window (yet?)
494
// TODO: Does Y.all support window now?
495
var target = args[2],
496
els = (isString(target)) ?
497
query(target) : toArray(target),
498
node, i, len, handles, j;
500
// (type, fn, el, context, filter?) => (type, fn, context, filter?)
503
for (i = 0, len = els.length; i < len; ++i) {
504
node = Y.one(els[i]);
507
handles = this.getSubs(node, args);
510
for (j = handles.length - 1; j >= 0; --j) {
519
* Returns the detach handles of subscriptions on a node that satisfy a
520
* search/filter function. By default, the filter used is the
521
* <code>subMatch</code> method.
524
* @param node {Node} the node hosting the event
525
* @param args {Array} the array of original subscription args passed
526
* to <code>Y.on(...)</code> (before
527
* <code>processArgs</code>
528
* @param filter {Function} function used to identify a subscription
529
* for inclusion in the returned array
530
* @param first {Boolean} stop after the first match (used to check for
531
* duplicate subscriptions)
532
* @return {Array} detach handles for the matching subscriptions
534
getSubs: function (node, args, filter, first) {
535
var notifiers = SyntheticEvent.getRegistry(node, this.type),
541
filter = this.subMatch;
544
for (i = 0, len = notifiers.length; i < len; ++i) {
545
handle = notifiers[i];
546
if (filter.call(this, handle.sub, args)) {
550
handles.push(notifiers[i]);
556
return handles.length && handles;
560
* <p>Implementers MAY override this to define what constitutes a
561
* "same" subscription. Override implementations should
562
* consider the lack of a comparator as a match, so calling
563
* <code>getSubs()</code> with no arguments will return all subs.</p>
565
* <p>Compares a set of subscription arguments against a Subscription
566
* object to determine if they match. The default implementation
567
* compares the callback function against the second argument passed to
568
* <code>Y.on(...)</code> or <code>node.detach(...)</code> etc.</p>
571
* @param sub {Subscription} the existing subscription
572
* @param args {Array} the calling arguments passed to
573
* <code>Y.on(...)</code> etc.
574
* @return {Boolean} true if the sub can be described by the args
578
subMatch: function (sub, args) {
579
// Default detach cares only about the callback matching
580
return !args[1] || sub.fn === args[1];
585
Y.SyntheticEvent = SyntheticEvent;
588
* <p>Defines a new event in the DOM event system. Implementers are
589
* responsible for monitoring for a scenario whereby the event is fired. A
590
* notifier object is provided to the functions identified below. When the
591
* criteria defining the event are met, call notifier.fire( [args] ); to
592
* execute event subscribers.</p>
594
* <p>The first parameter is the name of the event. The second parameter is a
595
* configuration object which define the behavior of the event system when the
596
* new event is subscribed to or detached from. The methods that should be
597
* defined in this configuration object are <code>on</code>,
598
* <code>detach</code>, <code>delegate</code>, and <code>detachDelegate</code>.
599
* You are free to define any other methods or properties needed to define your
600
* event. Be aware, however, that since the object is used to subclass
601
* SyntheticEvent, you should avoid method names used by SyntheticEvent unless
602
* your intention is to override the default behavior.</p>
604
* <p>This is a list of properties and methods that you can or should specify
605
* in the configuration object:</p>
608
* <dt><code>on</code></dt>
609
* <dd><code>function (node, subscription, notifier)</code> The
610
* implementation logic for subscription. Any special setup you need to
611
* do to create the environment for the event being fired--E.g. native
612
* DOM event subscriptions. Store subscription related objects and
613
* state on the <code>subscription</code> object. When the
614
* criteria have been met to fire the synthetic event, call
615
* <code>notifier.fire(e)</code>. See Notifier's <code>fire()</code>
616
* method for details about what to pass as parameters.</dd>
618
* <dt><code>detach</code></dt>
619
* <dd><code>function (node, subscription, notifier)</code> The
620
* implementation logic for cleaning up a detached subscription. E.g.
621
* detach any DOM subscriptions added in <code>on</code>.</dd>
623
* <dt><code>delegate</code></dt>
624
* <dd><code>function (node, subscription, notifier, filter)</code> The
625
* implementation logic for subscription via <code>Y.delegate</code> or
626
* <code>node.delegate</code>. The filter is typically either a selector
627
* string or a function. You can use
628
* <code>Y.delegate.compileFilter(selectorString)</code> to create a
629
* filter function from a selector string if needed. The filter function
630
* expects an event object as input and should output either null, a
631
* matching Node, or an array of matching Nodes. Otherwise, this acts
632
* like <code>on</code> DOM event subscriptions. Store subscription
633
* related objects and information on the <code>subscription</code>
634
* object. When the criteria have been met to fire the synthetic event,
635
* call <code>notifier.fire(e)</code> as noted above.</dd>
637
* <dt><code>detachDelegate</code></dt>
638
* <dd><code>function (node, subscription, notifier)</code> The
639
* implementation logic for cleaning up a detached delegate subscription.
640
* E.g. detach any DOM delegate subscriptions added in
641
* <code>delegate</code>.</dd>
643
* <dt><code>publishConfig</code></dt>
644
* <dd>(Object) The configuration object that will be used to instantiate
645
* the underlying CustomEvent. See Notifier's <code>fire</code> method
648
* <dt><code>processArgs</code></dt
650
* <p><code>function (argArray, fromDelegate)</code> Optional method
651
* to extract any additional arguments from the subscription
652
* signature. Using this allows <code>on</code> or
653
* <code>delegate</code> signatures like
654
* <code>node.on("hover", overCallback,
655
* outCallback)</code>.</p>
656
* <p>When processing an atypical argument signature, make sure the
657
* args array is returned to the normal signature before returning
658
* from the function. For example, in the "hover" example
659
* above, the <code>outCallback</code> needs to be <code>splice</code>d
660
* out of the array. The expected signature of the args array for
661
* <code>on()</code> subscriptions is:</p>
663
* <code>[type, callback, target, contextOverride, argN...]</code>
665
* <p>And for <code>delegate()</code>:</p>
667
* <code>[type, callback, target, filter, contextOverride, argN...]</code>
669
* <p>where <code>target</code> is the node the event is being
670
* subscribed for. You can see these signatures documented for
671
* <code>Y.on()</code> and <code>Y.delegate()</code> respectively.</p>
672
* <p>Whatever gets returned from the function will be stored on the
673
* <code>subscription</code> object under
674
* <code>subscription._extra</code>.</p></dd>
675
* <dt><code>subMatch</code></dt>
677
* <p><code>function (sub, args)</code> Compares a set of
678
* subscription arguments against a Subscription object to determine
679
* if they match. The default implementation compares the callback
680
* function against the second argument passed to
681
* <code>Y.on(...)</code> or <code>node.detach(...)</code> etc.</p>
685
* @method Event.define
686
* @param type {String} the name of the event
687
* @param config {Object} the prototype definition for the new event (see above)
688
* @param force {Boolean} override an existing event (use with caution)
690
* @return {SyntheticEvent} the subclass implementation instance created to
691
* handle event subscriptions of this type
694
* @in event-synthetic
696
Y.Event.define = function (type, config, force) {
701
var eventDef = (isObject(type)) ? type : Y.merge({ type: type }, config),
704
if (force || !Y.Node.DOM_EVENTS[eventDef.type]) {
706
SyntheticEvent.apply(this, arguments);
708
Y.extend(Impl, SyntheticEvent, eventDef);
713
Y.Node.DOM_EVENTS[type] = Y.Env.evt.plugins[type] = {
717
return synth._on(toArray(arguments));
720
delegate: function () {
721
return synth._on(toArray(arguments), true);
724
detach: function () {
725
return synth._detach(toArray(arguments));
735
}, '3.2.0' ,{requires:['node-base', 'event-custom']});