Version 3.17.2
Show:

File: event/js/synthetic.js

  1. /**
  2. * Define new DOM events that can be subscribed to from Nodes.
  3. *
  4. * @module event
  5. * @submodule event-synthetic
  6. */
  7. var CustomEvent = Y.CustomEvent,
  8. DOMMap = Y.Env.evt.dom_map,
  9. toArray = Y.Array,
  10. YLang = Y.Lang,
  11. isObject = YLang.isObject,
  12. isString = YLang.isString,
  13. isArray = YLang.isArray,
  14. query = Y.Selector.query,
  15. noop = function () {};
  16.  
  17. /**
  18. * <p>The triggering mechanism used by SyntheticEvents.</p>
  19. *
  20. * <p>Implementers should not instantiate these directly. Use the Notifier
  21. * provided to the event's implemented <code>on(node, sub, notifier)</code> or
  22. * <code>delegate(node, sub, notifier, filter)</code> methods.</p>
  23. *
  24. * @class SyntheticEvent.Notifier
  25. * @constructor
  26. * @param handle {EventHandle} the detach handle for the subscription to an
  27. * internal custom event used to execute the callback passed to
  28. * on(..) or delegate(..)
  29. * @param emitFacade {Boolean} take steps to ensure the first arg received by
  30. * the subscription callback is an event facade
  31. * @private
  32. * @since 3.2.0
  33. */
  34. function Notifier(handle, emitFacade) {
  35. this.handle = handle;
  36. this.emitFacade = emitFacade;
  37. }
  38.  
  39. /**
  40. * <p>Executes the subscription callback, passing the firing arguments as the
  41. * first parameters to that callback. For events that are configured with
  42. * emitFacade=true, it is common practice to pass the triggering DOMEventFacade
  43. * as the first parameter. Barring a proper DOMEventFacade or EventFacade
  44. * (from a CustomEvent), a new EventFacade will be generated. In that case, if
  45. * fire() is called with a simple object, it will be mixed into the facade.
  46. * Otherwise, the facade will be prepended to the callback parameters.</p>
  47. *
  48. * <p>For notifiers provided to delegate logic, the first argument should be an
  49. * object with a &quot;currentTarget&quot; property to identify what object to
  50. * default as 'this' in the callback. Typically this is gleaned from the
  51. * DOMEventFacade or EventFacade, but if configured with emitFacade=false, an
  52. * object must be provided. In that case, the object will be removed from the
  53. * callback parameters.</p>
  54. *
  55. * <p>Additional arguments passed during event subscription will be
  56. * automatically added after those passed to fire().</p>
  57. *
  58. * @method fire
  59. * @param {EventFacade|DOMEventFacade|any} e (see description)
  60. * @param {any[]} [arg*] additional arguments received by all subscriptions
  61. * @private
  62. */
  63. Notifier.prototype.fire = function (e) {
  64. // first arg to delegate notifier should be an object with currentTarget
  65. var args = toArray(arguments, 0, true),
  66. handle = this.handle,
  67. ce = handle.evt,
  68. sub = handle.sub,
  69. thisObj = sub.context,
  70. delegate = sub.filter,
  71. event = e || {},
  72. ret;
  73.  
  74. if (this.emitFacade) {
  75. if (!e || !e.preventDefault) {
  76. event = ce._getFacade();
  77.  
  78. if (isObject(e) && !e.preventDefault) {
  79. Y.mix(event, e, true);
  80. args[0] = event;
  81. } else {
  82. args.unshift(event);
  83. }
  84. }
  85.  
  86. event.type = ce.type;
  87. event.details = args.slice();
  88.  
  89. if (delegate) {
  90. event.container = ce.host;
  91. }
  92. } else if (delegate && isObject(e) && e.currentTarget) {
  93. args.shift();
  94. }
  95.  
  96. sub.context = thisObj || event.currentTarget || ce.host;
  97. ret = ce.fire.apply(ce, args);
  98.  
  99. // have to handle preventedFn and stoppedFn manually because
  100. // Notifier CustomEvents are forced to emitFacade=false
  101. if (e.prevented && ce.preventedFn) {
  102. ce.preventedFn.apply(ce, args);
  103. }
  104.  
  105. if (e.stopped && ce.stoppedFn) {
  106. ce.stoppedFn.apply(ce, args);
  107. }
  108.  
  109. sub.context = thisObj; // reset for future firing
  110.  
  111. // to capture callbacks that return false to stopPropagation.
  112. // Useful for delegate implementations
  113. return ret;
  114. };
  115.  
  116. /**
  117. * Manager object for synthetic event subscriptions to aggregate multiple synths on the
  118. * same node without colliding with actual DOM subscription entries in the global map of
  119. * DOM subscriptions. Also facilitates proper cleanup on page unload.
  120. *
  121. * @class SynthRegistry
  122. * @constructor
  123. * @param el {HTMLElement} the DOM element
  124. * @param yuid {String} the yuid stamp for the element
  125. * @param key {String} the generated id token used to identify an event type +
  126. * element in the global DOM subscription map.
  127. * @private
  128. */
  129. function SynthRegistry(el, yuid, key) {
  130. this.handles = [];
  131. this.el = el;
  132. this.key = key;
  133. this.domkey = yuid;
  134. }
  135.  
  136. SynthRegistry.prototype = {
  137. constructor: SynthRegistry,
  138.  
  139. // A few object properties to fake the CustomEvent interface for page
  140. // unload cleanup. DON'T TOUCH!
  141. type : '_synth',
  142. fn : noop,
  143. capture : false,
  144.  
  145. /**
  146. * Adds a subscription from the Notifier registry.
  147. *
  148. * @method register
  149. * @param handle {EventHandle} the subscription
  150. * @since 3.4.0
  151. */
  152. register: function (handle) {
  153. handle.evt.registry = this;
  154. this.handles.push(handle);
  155. },
  156.  
  157. /**
  158. * Removes the subscription from the Notifier registry.
  159. *
  160. * @method _unregisterSub
  161. * @param sub {Subscription} the subscription
  162. * @since 3.4.0
  163. */
  164. unregister: function (sub) {
  165. var handles = this.handles,
  166. events = DOMMap[this.domkey],
  167. i;
  168.  
  169. for (i = handles.length - 1; i >= 0; --i) {
  170. if (handles[i].sub === sub) {
  171. handles.splice(i, 1);
  172. break;
  173. }
  174. }
  175.  
  176. // Clean up left over objects when there are no more subscribers.
  177. if (!handles.length) {
  178. delete events[this.key];
  179. if (!Y.Object.size(events)) {
  180. delete DOMMap[this.domkey];
  181. }
  182. }
  183. },
  184.  
  185. /**
  186. * Used by the event system's unload cleanup process. When navigating
  187. * away from the page, the event system iterates the global map of element
  188. * subscriptions and detaches everything using detachAll(). Normally,
  189. * the map is populated with custom events, so this object needs to
  190. * at least support the detachAll method to duck type its way to
  191. * cleanliness.
  192. *
  193. * @method detachAll
  194. * @private
  195. * @since 3.4.0
  196. */
  197. detachAll : function () {
  198. var handles = this.handles,
  199. i = handles.length;
  200.  
  201. while (--i >= 0) {
  202. handles[i].detach();
  203. }
  204. }
  205. };
  206.  
  207. /**
  208. * <p>Wrapper class for the integration of new events into the YUI event
  209. * infrastructure. Don't instantiate this object directly, use
  210. * <code>Y.Event.define(type, config)</code>. See that method for details.</p>
  211. *
  212. * <p>Properties that MAY or SHOULD be specified in the configuration are noted
  213. * below and in the description of <code>Y.Event.define</code>.</p>
  214. *
  215. * @class SyntheticEvent
  216. * @constructor
  217. * @param cfg {Object} Implementation pieces and configuration
  218. * @since 3.1.0
  219. * @in event-synthetic
  220. */
  221. function SyntheticEvent() {
  222. this._init.apply(this, arguments);
  223. }
  224.  
  225. Y.mix(SyntheticEvent, {
  226. Notifier: Notifier,
  227. SynthRegistry: SynthRegistry,
  228.  
  229. /**
  230. * Returns the array of subscription handles for a node for the given event
  231. * type. Passing true as the third argument will create a registry entry
  232. * in the event system's DOM map to host the array if one doesn't yet exist.
  233. *
  234. * @method getRegistry
  235. * @param node {Node} the node
  236. * @param type {String} the event
  237. * @param create {Boolean} create a registration entry to host a new array
  238. * if one doesn't exist.
  239. * @return {Array}
  240. * @static
  241. * @protected
  242. * @since 3.2.0
  243. */
  244. getRegistry: function (node, type, create) {
  245. var el = node._node,
  246. yuid = Y.stamp(el),
  247. key = 'event:' + yuid + type + '_synth',
  248. events = DOMMap[yuid];
  249.  
  250. if (create) {
  251. if (!events) {
  252. events = DOMMap[yuid] = {};
  253. }
  254. if (!events[key]) {
  255. events[key] = new SynthRegistry(el, yuid, key);
  256. }
  257. }
  258.  
  259. return (events && events[key]) || null;
  260. },
  261.  
  262. /**
  263. * Alternate <code>_delete()</code> method for the CustomEvent object
  264. * created to manage SyntheticEvent subscriptions.
  265. *
  266. * @method _deleteSub
  267. * @param sub {Subscription} the subscription to clean up
  268. * @private
  269. * @since 3.2.0
  270. */
  271. _deleteSub: function (sub) {
  272. if (sub && sub.fn) {
  273. var synth = this.eventDef,
  274. method = (sub.filter) ? 'detachDelegate' : 'detach';
  275.  
  276. this._subscribers = [];
  277.  
  278. if (CustomEvent.keepDeprecatedSubs) {
  279. this.subscribers = {};
  280. }
  281.  
  282. synth[method](sub.node, sub, this.notifier, sub.filter);
  283. this.registry.unregister(sub);
  284.  
  285. delete sub.fn;
  286. delete sub.node;
  287. delete sub.context;
  288. }
  289. },
  290.  
  291. prototype: {
  292. constructor: SyntheticEvent,
  293.  
  294. /**
  295. * Construction logic for the event.
  296. *
  297. * @method _init
  298. * @protected
  299. */
  300. _init: function () {
  301. var config = this.publishConfig || (this.publishConfig = {});
  302.  
  303. // The notification mechanism handles facade creation
  304. this.emitFacade = ('emitFacade' in config) ?
  305. config.emitFacade :
  306. true;
  307. config.emitFacade = false;
  308. },
  309.  
  310. /**
  311. * <p>Implementers MAY provide this method definition.</p>
  312. *
  313. * <p>Implement this function if the event supports a different
  314. * subscription signature. This function is used by both
  315. * <code>on()</code> and <code>delegate()</code>. The second parameter
  316. * indicates that the event is being subscribed via
  317. * <code>delegate()</code>.</p>
  318. *
  319. * <p>Implementations must remove extra arguments from the args list
  320. * before returning. The required args for <code>on()</code>
  321. * subscriptions are</p>
  322. * <pre><code>[type, callback, target, context, argN...]</code></pre>
  323. *
  324. * <p>The required args for <code>delegate()</code>
  325. * subscriptions are</p>
  326. *
  327. * <pre><code>[type, callback, target, filter, context, argN...]</code></pre>
  328. *
  329. * <p>The return value from this function will be stored on the
  330. * subscription in the '_extra' property for reference elsewhere.</p>
  331. *
  332. * @method processArgs
  333. * @param args {Array} parmeters passed to Y.on(..) or Y.delegate(..)
  334. * @param delegate {Boolean} true if the subscription is from Y.delegate
  335. * @return {any}
  336. */
  337. processArgs: noop,
  338.  
  339. /**
  340. * <p>Implementers MAY override this property.</p>
  341. *
  342. * <p>Whether to prevent multiple subscriptions to this event that are
  343. * classified as being the same. By default, this means the subscribed
  344. * callback is the same function. See the <code>subMatch</code>
  345. * method. Setting this to true will impact performance for high volume
  346. * events.</p>
  347. *
  348. * @property preventDups
  349. * @type {Boolean}
  350. * @default false
  351. */
  352. //preventDups : false,
  353.  
  354. /**
  355. * <p>Implementers SHOULD provide this method definition.</p>
  356. *
  357. * Implementation logic for subscriptions done via <code>node.on(type,
  358. * fn)</code> or <code>Y.on(type, fn, target)</code>. This
  359. * function should set up the monitor(s) that will eventually fire the
  360. * event. Typically this involves subscribing to at least one DOM
  361. * event. It is recommended to store detach handles from any DOM
  362. * subscriptions to make for easy cleanup in the <code>detach</code>
  363. * method. Typically these handles are added to the <code>sub</code>
  364. * object. Also for SyntheticEvents that leverage a single DOM
  365. * subscription under the hood, it is recommended to pass the DOM event
  366. * object to <code>notifier.fire(e)</code>. (The event name on the
  367. * object will be updated).
  368. *
  369. * @method on
  370. * @param node {Node} the node the subscription is being applied to
  371. * @param sub {Subscription} the object to track this subscription
  372. * @param notifier {SyntheticEvent.Notifier} call notifier.fire(..) to
  373. * trigger the execution of the subscribers
  374. */
  375. on: noop,
  376.  
  377. /**
  378. * <p>Implementers SHOULD provide this method definition.</p>
  379. *
  380. * <p>Implementation logic for detaching subscriptions done via
  381. * <code>node.on(type, fn)</code>. This function should clean up any
  382. * subscriptions made in the <code>on()</code> phase.</p>
  383. *
  384. * @method detach
  385. * @param node {Node} the node the subscription was applied to
  386. * @param sub {Subscription} the object tracking this subscription
  387. * @param notifier {SyntheticEvent.Notifier} the Notifier used to
  388. * trigger the execution of the subscribers
  389. */
  390. detach: noop,
  391.  
  392. /**
  393. * <p>Implementers SHOULD provide this method definition.</p>
  394. *
  395. * <p>Implementation logic for subscriptions done via
  396. * <code>node.delegate(type, fn, filter)</code> or
  397. * <code>Y.delegate(type, fn, container, filter)</code>. Like with
  398. * <code>on()</code> above, this function should monitor the environment
  399. * for the event being fired, and trigger subscription execution by
  400. * calling <code>notifier.fire(e)</code>.</p>
  401. *
  402. * <p>This function receives a fourth argument, which is the filter
  403. * used to identify which Node's are of interest to the subscription.
  404. * The filter will be either a boolean function that accepts a target
  405. * Node for each hierarchy level as the event bubbles, or a selector
  406. * string. To translate selector strings into filter functions, use
  407. * <code>Y.delegate.compileFilter(filter)</code>.</p>
  408. *
  409. * @method delegate
  410. * @param node {Node} the node the subscription is being applied to
  411. * @param sub {Subscription} the object to track this subscription
  412. * @param notifier {SyntheticEvent.Notifier} call notifier.fire(..) to
  413. * trigger the execution of the subscribers
  414. * @param filter {String|Function} Selector string or function that
  415. * accepts an event object and returns null, a Node, or an
  416. * array of Nodes matching the criteria for processing.
  417. * @since 3.2.0
  418. */
  419. delegate : noop,
  420.  
  421. /**
  422. * <p>Implementers SHOULD provide this method definition.</p>
  423. *
  424. * <p>Implementation logic for detaching subscriptions done via
  425. * <code>node.delegate(type, fn, filter)</code> or
  426. * <code>Y.delegate(type, fn, container, filter)</code>. This function
  427. * should clean up any subscriptions made in the
  428. * <code>delegate()</code> phase.</p>
  429. *
  430. * @method detachDelegate
  431. * @param node {Node} the node the subscription was applied to
  432. * @param sub {Subscription} the object tracking this subscription
  433. * @param notifier {SyntheticEvent.Notifier} the Notifier used to
  434. * trigger the execution of the subscribers
  435. * @param filter {String|Function} Selector string or function that
  436. * accepts an event object and returns null, a Node, or an
  437. * array of Nodes matching the criteria for processing.
  438. * @since 3.2.0
  439. */
  440. detachDelegate : noop,
  441.  
  442. /**
  443. * Sets up the boilerplate for detaching the event and facilitating the
  444. * execution of subscriber callbacks.
  445. *
  446. * @method _on
  447. * @param args {Array} array of arguments passed to
  448. * <code>Y.on(...)</code> or <code>Y.delegate(...)</code>
  449. * @param delegate {Boolean} true if called from
  450. * <code>Y.delegate(...)</code>
  451. * @return {EventHandle} the detach handle for this subscription
  452. * @private
  453. * since 3.2.0
  454. */
  455. _on: function (args, delegate) {
  456. var handles = [],
  457. originalArgs = args.slice(),
  458. extra = this.processArgs(args, delegate),
  459. selector = args[2],
  460. method = delegate ? 'delegate' : 'on',
  461. nodes, handle;
  462.  
  463. // Can't just use Y.all because it doesn't support window (yet?)
  464. nodes = (isString(selector)) ?
  465. query(selector) :
  466. toArray(selector || Y.one(Y.config.win));
  467.  
  468. if (!nodes.length && isString(selector)) {
  469. handle = Y.on('available', function () {
  470. Y.mix(handle, Y[method].apply(Y, originalArgs), true);
  471. }, selector);
  472.  
  473. return handle;
  474. }
  475.  
  476. Y.Array.each(nodes, function (node) {
  477. var subArgs = args.slice(),
  478. filter;
  479.  
  480. node = Y.one(node);
  481.  
  482. if (node) {
  483. if (delegate) {
  484. filter = subArgs.splice(3, 1)[0];
  485. }
  486.  
  487. // (type, fn, el, thisObj, ...) => (fn, thisObj, ...)
  488. subArgs.splice(0, 4, subArgs[1], subArgs[3]);
  489.  
  490. if (!this.preventDups ||
  491. !this.getSubs(node, args, null, true))
  492. {
  493. handles.push(this._subscribe(node, method, subArgs, extra, filter));
  494. }
  495. }
  496. }, this);
  497.  
  498. return (handles.length === 1) ?
  499. handles[0] :
  500. new Y.EventHandle(handles);
  501. },
  502.  
  503. /**
  504. * Creates a new Notifier object for use by this event's
  505. * <code>on(...)</code> or <code>delegate(...)</code> implementation
  506. * and register the custom event proxy in the DOM system for cleanup.
  507. *
  508. * @method _subscribe
  509. * @param node {Node} the Node hosting the event
  510. * @param method {String} "on" or "delegate"
  511. * @param args {Array} the subscription arguments passed to either
  512. * <code>Y.on(...)</code> or <code>Y.delegate(...)</code>
  513. * after running through <code>processArgs(args)</code> to
  514. * normalize the argument signature
  515. * @param extra {any} Extra data parsed from
  516. * <code>processArgs(args)</code>
  517. * @param filter {String|Function} the selector string or function
  518. * filter passed to <code>Y.delegate(...)</code> (not
  519. * present when called from <code>Y.on(...)</code>)
  520. * @return {EventHandle}
  521. * @private
  522. * @since 3.2.0
  523. */
  524. _subscribe: function (node, method, args, extra, filter) {
  525. var dispatcher = new Y.CustomEvent(this.type, this.publishConfig),
  526. handle = dispatcher.on.apply(dispatcher, args),
  527. notifier = new Notifier(handle, this.emitFacade),
  528. registry = SyntheticEvent.getRegistry(node, this.type, true),
  529. sub = handle.sub;
  530.  
  531. sub.node = node;
  532. sub.filter = filter;
  533. if (extra) {
  534. this.applyArgExtras(extra, sub);
  535. }
  536.  
  537. Y.mix(dispatcher, {
  538. eventDef : this,
  539. notifier : notifier,
  540. host : node, // I forget what this is for
  541. currentTarget: node, // for generating facades
  542. target : node, // for generating facades
  543. el : node._node, // For category detach
  544.  
  545. _delete : SyntheticEvent._deleteSub
  546. }, true);
  547.  
  548. handle.notifier = notifier;
  549.  
  550. registry.register(handle);
  551.  
  552. // Call the implementation's "on" or "delegate" method
  553. this[method](node, sub, notifier, filter);
  554.  
  555. return handle;
  556. },
  557.  
  558. /**
  559. * <p>Implementers MAY provide this method definition.</p>
  560. *
  561. * <p>Implement this function if you want extra data extracted during
  562. * processArgs to be propagated to subscriptions on a per-node basis.
  563. * That is to say, if you call <code>Y.on('xyz', fn, xtra, 'div')</code>
  564. * the data returned from processArgs will be shared
  565. * across the subscription objects for all the divs. If you want each
  566. * subscription to receive unique information, do that processing
  567. * here.</p>
  568. *
  569. * <p>The default implementation adds the data extracted by processArgs
  570. * to the subscription object as <code>sub._extra</code>.</p>
  571. *
  572. * @method applyArgExtras
  573. * @param extra {any} Any extra data extracted from processArgs
  574. * @param sub {Subscription} the individual subscription
  575. */
  576. applyArgExtras: function (extra, sub) {
  577. sub._extra = extra;
  578. },
  579.  
  580. /**
  581. * Removes the subscription(s) from the internal subscription dispatch
  582. * mechanism. See <code>SyntheticEvent._deleteSub</code>.
  583. *
  584. * @method _detach
  585. * @param args {Array} The arguments passed to
  586. * <code>node.detach(...)</code>
  587. * @private
  588. * @since 3.2.0
  589. */
  590. _detach: function (args) {
  591. // Can't use Y.all because it doesn't support window (yet?)
  592. // TODO: Does Y.all support window now?
  593. var target = args[2],
  594. els = (isString(target)) ?
  595. query(target) : toArray(target),
  596. node, i, len, handles, j;
  597.  
  598. // (type, fn, el, context, filter?) => (type, fn, context, filter?)
  599. args.splice(2, 1);
  600.  
  601. for (i = 0, len = els.length; i < len; ++i) {
  602. node = Y.one(els[i]);
  603.  
  604. if (node) {
  605. handles = this.getSubs(node, args);
  606.  
  607. if (handles) {
  608. for (j = handles.length - 1; j >= 0; --j) {
  609. handles[j].detach();
  610. }
  611. }
  612. }
  613. }
  614. },
  615.  
  616. /**
  617. * Returns the detach handles of subscriptions on a node that satisfy a
  618. * search/filter function. By default, the filter used is the
  619. * <code>subMatch</code> method.
  620. *
  621. * @method getSubs
  622. * @param node {Node} the node hosting the event
  623. * @param args {Array} the array of original subscription args passed
  624. * to <code>Y.on(...)</code> (before
  625. * <code>processArgs</code>
  626. * @param filter {Function} function used to identify a subscription
  627. * for inclusion in the returned array
  628. * @param first {Boolean} stop after the first match (used to check for
  629. * duplicate subscriptions)
  630. * @return {EventHandle[]} detach handles for the matching subscriptions
  631. */
  632. getSubs: function (node, args, filter, first) {
  633. var registry = SyntheticEvent.getRegistry(node, this.type),
  634. handles = [],
  635. allHandles, i, len, handle;
  636.  
  637. if (registry) {
  638. allHandles = registry.handles;
  639.  
  640. if (!filter) {
  641. filter = this.subMatch;
  642. }
  643.  
  644. for (i = 0, len = allHandles.length; i < len; ++i) {
  645. handle = allHandles[i];
  646. if (filter.call(this, handle.sub, args)) {
  647. if (first) {
  648. return handle;
  649. } else {
  650. handles.push(allHandles[i]);
  651. }
  652. }
  653. }
  654. }
  655.  
  656. return handles.length && handles;
  657. },
  658.  
  659. /**
  660. * <p>Implementers MAY override this to define what constitutes a
  661. * &quot;same&quot; subscription. Override implementations should
  662. * consider the lack of a comparator as a match, so calling
  663. * <code>getSubs()</code> with no arguments will return all subs.</p>
  664. *
  665. * <p>Compares a set of subscription arguments against a Subscription
  666. * object to determine if they match. The default implementation
  667. * compares the callback function against the second argument passed to
  668. * <code>Y.on(...)</code> or <code>node.detach(...)</code> etc.</p>
  669. *
  670. * @method subMatch
  671. * @param sub {Subscription} the existing subscription
  672. * @param args {Array} the calling arguments passed to
  673. * <code>Y.on(...)</code> etc.
  674. * @return {Boolean} true if the sub can be described by the args
  675. * present
  676. * @since 3.2.0
  677. */
  678. subMatch: function (sub, args) {
  679. // Default detach cares only about the callback matching
  680. return !args[1] || sub.fn === args[1];
  681. }
  682. }
  683. }, true);
  684.  
  685. Y.SyntheticEvent = SyntheticEvent;
  686.  
  687. /**
  688. * <p>Defines a new event in the DOM event system. Implementers are
  689. * responsible for monitoring for a scenario whereby the event is fired. A
  690. * notifier object is provided to the functions identified below. When the
  691. * criteria defining the event are met, call notifier.fire( [args] ); to
  692. * execute event subscribers.</p>
  693. *
  694. * <p>The first parameter is the name of the event. The second parameter is a
  695. * configuration object which define the behavior of the event system when the
  696. * new event is subscribed to or detached from. The methods that should be
  697. * defined in this configuration object are <code>on</code>,
  698. * <code>detach</code>, <code>delegate</code>, and <code>detachDelegate</code>.
  699. * You are free to define any other methods or properties needed to define your
  700. * event. Be aware, however, that since the object is used to subclass
  701. * SyntheticEvent, you should avoid method names used by SyntheticEvent unless
  702. * your intention is to override the default behavior.</p>
  703. *
  704. * <p>This is a list of properties and methods that you can or should specify
  705. * in the configuration object:</p>
  706. *
  707. * <dl>
  708. * <dt><code>on</code></dt>
  709. * <dd><code>function (node, subscription, notifier)</code> The
  710. * implementation logic for subscription. Any special setup you need to
  711. * do to create the environment for the event being fired--E.g. native
  712. * DOM event subscriptions. Store subscription related objects and
  713. * state on the <code>subscription</code> object. When the
  714. * criteria have been met to fire the synthetic event, call
  715. * <code>notifier.fire(e)</code>. See Notifier's <code>fire()</code>
  716. * method for details about what to pass as parameters.</dd>
  717. *
  718. * <dt><code>detach</code></dt>
  719. * <dd><code>function (node, subscription, notifier)</code> The
  720. * implementation logic for cleaning up a detached subscription. E.g.
  721. * detach any DOM subscriptions added in <code>on</code>.</dd>
  722. *
  723. * <dt><code>delegate</code></dt>
  724. * <dd><code>function (node, subscription, notifier, filter)</code> The
  725. * implementation logic for subscription via <code>Y.delegate</code> or
  726. * <code>node.delegate</code>. The filter is typically either a selector
  727. * string or a function. You can use
  728. * <code>Y.delegate.compileFilter(selectorString)</code> to create a
  729. * filter function from a selector string if needed. The filter function
  730. * expects an event object as input and should output either null, a
  731. * matching Node, or an array of matching Nodes. Otherwise, this acts
  732. * like <code>on</code> DOM event subscriptions. Store subscription
  733. * related objects and information on the <code>subscription</code>
  734. * object. When the criteria have been met to fire the synthetic event,
  735. * call <code>notifier.fire(e)</code> as noted above.</dd>
  736. *
  737. * <dt><code>detachDelegate</code></dt>
  738. * <dd><code>function (node, subscription, notifier)</code> The
  739. * implementation logic for cleaning up a detached delegate subscription.
  740. * E.g. detach any DOM delegate subscriptions added in
  741. * <code>delegate</code>.</dd>
  742. *
  743. * <dt><code>publishConfig</code></dt>
  744. * <dd>(Object) The configuration object that will be used to instantiate
  745. * the underlying CustomEvent. See Notifier's <code>fire</code> method
  746. * for details.</dd>
  747. *
  748. * <dt><code>processArgs</code></dt
  749. * <dd>
  750. * <p><code>function (argArray, fromDelegate)</code> Optional method
  751. * to extract any additional arguments from the subscription
  752. * signature. Using this allows <code>on</code> or
  753. * <code>delegate</code> signatures like
  754. * <code>node.on(&quot;hover&quot;, overCallback,
  755. * outCallback)</code>.</p>
  756. * <p>When processing an atypical argument signature, make sure the
  757. * args array is returned to the normal signature before returning
  758. * from the function. For example, in the &quot;hover&quot; example
  759. * above, the <code>outCallback</code> needs to be <code>splice</code>d
  760. * out of the array. The expected signature of the args array for
  761. * <code>on()</code> subscriptions is:</p>
  762. * <pre>
  763. * <code>[type, callback, target, contextOverride, argN...]</code>
  764. * </pre>
  765. * <p>And for <code>delegate()</code>:</p>
  766. * <pre>
  767. * <code>[type, callback, target, filter, contextOverride, argN...]</code>
  768. * </pre>
  769. * <p>where <code>target</code> is the node the event is being
  770. * subscribed for. You can see these signatures documented for
  771. * <code>Y.on()</code> and <code>Y.delegate()</code> respectively.</p>
  772. * <p>Whatever gets returned from the function will be stored on the
  773. * <code>subscription</code> object under
  774. * <code>subscription._extra</code>.</p></dd>
  775. * <dt><code>subMatch</code></dt>
  776. * <dd>
  777. * <p><code>function (sub, args)</code> Compares a set of
  778. * subscription arguments against a Subscription object to determine
  779. * if they match. The default implementation compares the callback
  780. * function against the second argument passed to
  781. * <code>Y.on(...)</code> or <code>node.detach(...)</code> etc.</p>
  782. * </dd>
  783. * </dl>
  784. *
  785. * @method define
  786. * @param type {String} the name of the event
  787. * @param config {Object} the prototype definition for the new event (see above)
  788. * @param force {Boolean} override an existing event (use with caution)
  789. * @return {SyntheticEvent} the subclass implementation instance created to
  790. * handle event subscriptions of this type
  791. * @static
  792. * @for Event
  793. * @since 3.1.0
  794. * @in event-synthetic
  795. */
  796. Y.Event.define = function (type, config, force) {
  797. var eventDef, Impl, synth;
  798.  
  799. if (type && type.type) {
  800. eventDef = type;
  801. force = config;
  802. } else if (config) {
  803. eventDef = Y.merge({ type: type }, config);
  804. }
  805.  
  806. if (eventDef) {
  807. if (force || !Y.Node.DOM_EVENTS[eventDef.type]) {
  808. Impl = function () {
  809. SyntheticEvent.apply(this, arguments);
  810. };
  811. Y.extend(Impl, SyntheticEvent, eventDef);
  812. synth = new Impl();
  813.  
  814. type = synth.type;
  815.  
  816. Y.Node.DOM_EVENTS[type] = Y.Env.evt.plugins[type] = {
  817. eventDef: synth,
  818.  
  819. on: function () {
  820. return synth._on(toArray(arguments));
  821. },
  822.  
  823. delegate: function () {
  824. return synth._on(toArray(arguments), true);
  825. },
  826.  
  827. detach: function () {
  828. return synth._detach(toArray(arguments));
  829. }
  830. };
  831.  
  832. }
  833. } else if (isString(type) || isArray(type)) {
  834. Y.Array.each(toArray(type), function (t) {
  835. Y.Node.DOM_EVENTS[t] = 1;
  836. });
  837. }
  838.  
  839. return synth;
  840. };
  841.