API Docs for: 3.18.1
Show:

File: event-custom/js/event-custom.js

  1.  
  2. /**
  3. * Custom event engine, DOM event listener abstraction layer, synthetic DOM
  4. * events.
  5. * @module event-custom
  6. * @submodule event-custom-base
  7. */
  8.  
  9.  
  10. // var onsubscribeType = "_event:onsub",
  11. var YArray = Y.Array,
  12.  
  13. AFTER = 'after',
  14. CONFIGS = [
  15. 'broadcast',
  16. 'monitored',
  17. 'bubbles',
  18. 'context',
  19. 'contextFn',
  20. 'currentTarget',
  21. 'defaultFn',
  22. 'defaultTargetOnly',
  23. 'details',
  24. 'emitFacade',
  25. 'fireOnce',
  26. 'async',
  27. 'host',
  28. 'preventable',
  29. 'preventedFn',
  30. 'queuable',
  31. 'silent',
  32. 'stoppedFn',
  33. 'target',
  34. 'type'
  35. ],
  36.  
  37. CONFIGS_HASH = YArray.hash(CONFIGS),
  38.  
  39. nativeSlice = Array.prototype.slice,
  40.  
  41. YUI3_SIGNATURE = 9,
  42. YUI_LOG = 'yui:log',
  43.  
  44. mixConfigs = function(r, s, ov) {
  45. var p;
  46.  
  47. for (p in s) {
  48. if (CONFIGS_HASH[p] && (ov || !(p in r))) {
  49. r[p] = s[p];
  50. }
  51. }
  52.  
  53. return r;
  54. };
  55.  
  56. /**
  57. * The CustomEvent class lets you define events for your application
  58. * that can be subscribed to by one or more independent component.
  59. *
  60. * @param {String} type The type of event, which is passed to the callback
  61. * when the event fires.
  62. * @param {object} defaults configuration object.
  63. * @class CustomEvent
  64. * @constructor
  65. */
  66.  
  67. /**
  68. * The type of event, returned to subscribers when the event fires
  69. * @property type
  70. * @type string
  71. */
  72.  
  73. /**
  74. * By default all custom events are logged in the debug build, set silent
  75. * to true to disable debug outpu for this event.
  76. * @property silent
  77. * @type boolean
  78. */
  79.  
  80. Y.CustomEvent = function(type, defaults) {
  81.  
  82. this._kds = Y.CustomEvent.keepDeprecatedSubs;
  83.  
  84. this.id = Y.guid();
  85.  
  86. this.type = type;
  87. this.silent = this.logSystem = (type === YUI_LOG);
  88.  
  89. if (this._kds) {
  90. /**
  91. * The subscribers to this event
  92. * @property subscribers
  93. * @type Subscriber {}
  94. * @deprecated
  95. */
  96.  
  97. /**
  98. * 'After' subscribers
  99. * @property afters
  100. * @type Subscriber {}
  101. * @deprecated
  102. */
  103. this.subscribers = {};
  104. this.afters = {};
  105. }
  106.  
  107. if (defaults) {
  108. mixConfigs(this, defaults, true);
  109. }
  110. };
  111.  
  112. /**
  113. * Static flag to enable population of the <a href="#property_subscribers">`subscribers`</a>
  114. * and <a href="#property_subscribers">`afters`</a> properties held on a `CustomEvent` instance.
  115. *
  116. * These properties were changed to private properties (`_subscribers` and `_afters`), and
  117. * converted from objects to arrays for performance reasons.
  118. *
  119. * Setting this property to true will populate the deprecated `subscribers` and `afters`
  120. * properties for people who may be using them (which is expected to be rare). There will
  121. * be a performance hit, compared to the new array based implementation.
  122. *
  123. * If you are using these deprecated properties for a use case which the public API
  124. * does not support, please file an enhancement request, and we can provide an alternate
  125. * public implementation which doesn't have the performance cost required to maintiain the
  126. * properties as objects.
  127. *
  128. * @property keepDeprecatedSubs
  129. * @static
  130. * @for CustomEvent
  131. * @type boolean
  132. * @default false
  133. * @deprecated
  134. */
  135. Y.CustomEvent.keepDeprecatedSubs = false;
  136.  
  137. Y.CustomEvent.mixConfigs = mixConfigs;
  138.  
  139. Y.CustomEvent.prototype = {
  140.  
  141. constructor: Y.CustomEvent,
  142.  
  143. /**
  144. * Monitor when an event is attached or detached.
  145. *
  146. * @property monitored
  147. * @type boolean
  148. */
  149.  
  150. /**
  151. * If 0, this event does not broadcast. If 1, the YUI instance is notified
  152. * every time this event fires. If 2, the YUI instance and the YUI global
  153. * (if event is enabled on the global) are notified every time this event
  154. * fires.
  155. * @property broadcast
  156. * @type int
  157. */
  158.  
  159. /**
  160. * Specifies whether this event should be queued when the host is actively
  161. * processing an event. This will effect exectution order of the callbacks
  162. * for the various events.
  163. * @property queuable
  164. * @type boolean
  165. * @default false
  166. */
  167.  
  168. /**
  169. * This event has fired if true
  170. *
  171. * @property fired
  172. * @type boolean
  173. * @default false;
  174. */
  175.  
  176. /**
  177. * An array containing the arguments the custom event
  178. * was last fired with.
  179. * @property firedWith
  180. * @type Array
  181. */
  182.  
  183. /**
  184. * This event should only fire one time if true, and if
  185. * it has fired, any new subscribers should be notified
  186. * immediately.
  187. *
  188. * @property fireOnce
  189. * @type boolean
  190. * @default false;
  191. */
  192.  
  193. /**
  194. * fireOnce listeners will fire syncronously unless async
  195. * is set to true
  196. * @property async
  197. * @type boolean
  198. * @default false
  199. */
  200.  
  201. /**
  202. * Flag for stopPropagation that is modified during fire()
  203. * 1 means to stop propagation to bubble targets. 2 means
  204. * to also stop additional subscribers on this target.
  205. * @property stopped
  206. * @type int
  207. */
  208.  
  209. /**
  210. * Flag for preventDefault that is modified during fire().
  211. * if it is not 0, the default behavior for this event
  212. * @property prevented
  213. * @type int
  214. */
  215.  
  216. /**
  217. * Specifies the host for this custom event. This is used
  218. * to enable event bubbling
  219. * @property host
  220. * @type EventTarget
  221. */
  222.  
  223. /**
  224. * The default function to execute after event listeners
  225. * have fire, but only if the default action was not
  226. * prevented.
  227. * @property defaultFn
  228. * @type Function
  229. */
  230.  
  231. /**
  232. * Flag for the default function to execute only if the
  233. * firing event is the current target. This happens only
  234. * when using custom event delegation and setting the
  235. * flag to `true` mimics the behavior of event delegation
  236. * in the DOM.
  237. *
  238. * @property defaultTargetOnly
  239. * @type Boolean
  240. * @default false
  241. */
  242.  
  243. /**
  244. * The function to execute if a subscriber calls
  245. * stopPropagation or stopImmediatePropagation
  246. * @property stoppedFn
  247. * @type Function
  248. */
  249.  
  250. /**
  251. * The function to execute if a subscriber calls
  252. * preventDefault
  253. * @property preventedFn
  254. * @type Function
  255. */
  256.  
  257. /**
  258. * The subscribers to this event
  259. * @property _subscribers
  260. * @type Subscriber []
  261. * @private
  262. */
  263.  
  264. /**
  265. * 'After' subscribers
  266. * @property _afters
  267. * @type Subscriber []
  268. * @private
  269. */
  270.  
  271. /**
  272. * If set to true, the custom event will deliver an EventFacade object
  273. * that is similar to a DOM event object.
  274. * @property emitFacade
  275. * @type boolean
  276. * @default false
  277. */
  278.  
  279. /**
  280. * Supports multiple options for listener signatures in order to
  281. * port YUI 2 apps.
  282. * @property signature
  283. * @type int
  284. * @default 9
  285. */
  286. signature : YUI3_SIGNATURE,
  287.  
  288. /**
  289. * The context the the event will fire from by default. Defaults to the YUI
  290. * instance.
  291. * @property context
  292. * @type object
  293. */
  294. context : Y,
  295.  
  296. /**
  297. * Specifies whether or not this event's default function
  298. * can be cancelled by a subscriber by executing preventDefault()
  299. * on the event facade
  300. * @property preventable
  301. * @type boolean
  302. * @default true
  303. */
  304. preventable : true,
  305.  
  306. /**
  307. * Specifies whether or not a subscriber can stop the event propagation
  308. * via stopPropagation(), stopImmediatePropagation(), or halt()
  309. *
  310. * Events can only bubble if emitFacade is true.
  311. *
  312. * @property bubbles
  313. * @type boolean
  314. * @default true
  315. */
  316. bubbles : true,
  317.  
  318. /**
  319. * Returns the number of subscribers for this event as the sum of the on()
  320. * subscribers and after() subscribers.
  321. *
  322. * @method hasSubs
  323. * @return Number
  324. */
  325. hasSubs: function(when) {
  326. var s = 0,
  327. a = 0,
  328. subs = this._subscribers,
  329. afters = this._afters,
  330. sib = this.sibling;
  331.  
  332. if (subs) {
  333. s = subs.length;
  334. }
  335.  
  336. if (afters) {
  337. a = afters.length;
  338. }
  339.  
  340. if (sib) {
  341. subs = sib._subscribers;
  342. afters = sib._afters;
  343.  
  344. if (subs) {
  345. s += subs.length;
  346. }
  347.  
  348. if (afters) {
  349. a += afters.length;
  350. }
  351. }
  352.  
  353. if (when) {
  354. return (when === 'after') ? a : s;
  355. }
  356.  
  357. return (s + a);
  358. },
  359.  
  360. /**
  361. * Monitor the event state for the subscribed event. The first parameter
  362. * is what should be monitored, the rest are the normal parameters when
  363. * subscribing to an event.
  364. * @method monitor
  365. * @param what {string} what to monitor ('detach', 'attach', 'publish').
  366. * @return {EventHandle} return value from the monitor event subscription.
  367. */
  368. monitor: function(what) {
  369. this.monitored = true;
  370. var type = this.id + '|' + this.type + '_' + what,
  371. args = nativeSlice.call(arguments, 0);
  372. args[0] = type;
  373. return this.host.on.apply(this.host, args);
  374. },
  375.  
  376. /**
  377. * Get all of the subscribers to this event and any sibling event
  378. * @method getSubs
  379. * @return {Array} first item is the on subscribers, second the after.
  380. */
  381. getSubs: function() {
  382.  
  383. var sibling = this.sibling,
  384. subs = this._subscribers,
  385. afters = this._afters,
  386. siblingSubs,
  387. siblingAfters;
  388.  
  389. if (sibling) {
  390. siblingSubs = sibling._subscribers;
  391. siblingAfters = sibling._afters;
  392. }
  393.  
  394. if (siblingSubs) {
  395. if (subs) {
  396. subs = subs.concat(siblingSubs);
  397. } else {
  398. subs = siblingSubs.concat();
  399. }
  400. } else {
  401. if (subs) {
  402. subs = subs.concat();
  403. } else {
  404. subs = [];
  405. }
  406. }
  407.  
  408. if (siblingAfters) {
  409. if (afters) {
  410. afters = afters.concat(siblingAfters);
  411. } else {
  412. afters = siblingAfters.concat();
  413. }
  414. } else {
  415. if (afters) {
  416. afters = afters.concat();
  417. } else {
  418. afters = [];
  419. }
  420. }
  421.  
  422. return [subs, afters];
  423. },
  424.  
  425. /**
  426. * Apply configuration properties. Only applies the CONFIG whitelist
  427. * @method applyConfig
  428. * @param o hash of properties to apply.
  429. * @param force {boolean} if true, properties that exist on the event
  430. * will be overwritten.
  431. */
  432. applyConfig: function(o, force) {
  433. mixConfigs(this, o, force);
  434. },
  435.  
  436. /**
  437. * Create the Subscription for subscribing function, context, and bound
  438. * arguments. If this is a fireOnce event, the subscriber is immediately
  439. * notified.
  440. *
  441. * @method _on
  442. * @param fn {Function} Subscription callback
  443. * @param [context] {Object} Override `this` in the callback
  444. * @param [args] {Array} bound arguments that will be passed to the callback after the arguments generated by fire()
  445. * @param [when] {String} "after" to slot into after subscribers
  446. * @return {EventHandle}
  447. * @protected
  448. */
  449. _on: function(fn, context, args, when) {
  450.  
  451. if (!fn) { this.log('Invalid callback for CE: ' + this.type); }
  452.  
  453. var s = new Y.Subscriber(fn, context, args, when),
  454. firedWith;
  455.  
  456. if (this.fireOnce && this.fired) {
  457.  
  458. firedWith = this.firedWith;
  459.  
  460. // It's a little ugly for this to know about facades,
  461. // but given the current breakup, not much choice without
  462. // moving a whole lot of stuff around.
  463. if (this.emitFacade && this._addFacadeToArgs) {
  464. this._addFacadeToArgs(firedWith);
  465. }
  466.  
  467. if (this.async) {
  468. setTimeout(Y.bind(this._notify, this, s, firedWith), 0);
  469. } else {
  470. this._notify(s, firedWith);
  471. }
  472. }
  473.  
  474. if (when === AFTER) {
  475. if (!this._afters) {
  476. this._afters = [];
  477. }
  478. this._afters.push(s);
  479. } else {
  480. if (!this._subscribers) {
  481. this._subscribers = [];
  482. }
  483. this._subscribers.push(s);
  484. }
  485.  
  486. if (this._kds) {
  487. if (when === AFTER) {
  488. this.afters[s.id] = s;
  489. } else {
  490. this.subscribers[s.id] = s;
  491. }
  492. }
  493.  
  494. return new Y.EventHandle(this, s);
  495. },
  496.  
  497. /**
  498. * Listen for this event
  499. * @method subscribe
  500. * @param {Function} fn The function to execute.
  501. * @return {EventHandle} Unsubscribe handle.
  502. * @deprecated use on.
  503. */
  504. subscribe: function(fn, context) {
  505. Y.log('ce.subscribe deprecated, use "on"', 'warn', 'deprecated');
  506. var a = (arguments.length > 2) ? nativeSlice.call(arguments, 2) : null;
  507. return this._on(fn, context, a, true);
  508. },
  509.  
  510. /**
  511. * Listen for this event
  512. * @method on
  513. * @param {Function} fn The function to execute.
  514. * @param {object} context optional execution context.
  515. * @param {mixed} arg* 0..n additional arguments to supply to the subscriber
  516. * when the event fires.
  517. * @return {EventHandle} An object with a detach method to detch the handler(s).
  518. */
  519. on: function(fn, context) {
  520. var a = (arguments.length > 2) ? nativeSlice.call(arguments, 2) : null;
  521.  
  522. if (this.monitored && this.host) {
  523. this.host._monitor('attach', this, {
  524. args: arguments
  525. });
  526. }
  527. return this._on(fn, context, a, true);
  528. },
  529.  
  530. /**
  531. * Listen for this event after the normal subscribers have been notified and
  532. * the default behavior has been applied. If a normal subscriber prevents the
  533. * default behavior, it also prevents after listeners from firing.
  534. * @method after
  535. * @param {Function} fn The function to execute.
  536. * @param {object} context optional execution context.
  537. * @param {mixed} arg* 0..n additional arguments to supply to the subscriber
  538. * when the event fires.
  539. * @return {EventHandle} handle Unsubscribe handle.
  540. */
  541. after: function(fn, context) {
  542. var a = (arguments.length > 2) ? nativeSlice.call(arguments, 2) : null;
  543. return this._on(fn, context, a, AFTER);
  544. },
  545.  
  546. /**
  547. * Detach listeners.
  548. * @method detach
  549. * @param {Function} fn The subscribed function to remove, if not supplied
  550. * all will be removed.
  551. * @param {Object} context The context object passed to subscribe.
  552. * @return {Number} returns the number of subscribers unsubscribed.
  553. */
  554. detach: function(fn, context) {
  555. // unsubscribe handle
  556. if (fn && fn.detach) {
  557. return fn.detach();
  558. }
  559.  
  560. var i, s,
  561. found = 0,
  562. subs = this._subscribers,
  563. afters = this._afters;
  564.  
  565. if (subs) {
  566. for (i = subs.length; i >= 0; i--) {
  567. s = subs[i];
  568. if (s && (!fn || fn === s.fn)) {
  569. this._delete(s, subs, i);
  570. found++;
  571. }
  572. }
  573. }
  574.  
  575. if (afters) {
  576. for (i = afters.length; i >= 0; i--) {
  577. s = afters[i];
  578. if (s && (!fn || fn === s.fn)) {
  579. this._delete(s, afters, i);
  580. found++;
  581. }
  582. }
  583. }
  584.  
  585. return found;
  586. },
  587.  
  588. /**
  589. * Detach listeners.
  590. * @method unsubscribe
  591. * @param {Function} fn The subscribed function to remove, if not supplied
  592. * all will be removed.
  593. * @param {Object} context The context object passed to subscribe.
  594. * @return {int|undefined} returns the number of subscribers unsubscribed.
  595. * @deprecated use detach.
  596. */
  597. unsubscribe: function() {
  598. return this.detach.apply(this, arguments);
  599. },
  600.  
  601. /**
  602. * Notify a single subscriber
  603. * @method _notify
  604. * @param {Subscriber} s the subscriber.
  605. * @param {Array} args the arguments array to apply to the listener.
  606. * @protected
  607. */
  608. _notify: function(s, args, ef) {
  609.  
  610. this.log(this.type + '->' + 'sub: ' + s.id);
  611.  
  612. var ret;
  613.  
  614. ret = s.notify(args, this);
  615.  
  616. if (false === ret || this.stopped > 1) {
  617. this.log(this.type + ' cancelled by subscriber');
  618. return false;
  619. }
  620.  
  621. return true;
  622. },
  623.  
  624. /**
  625. * Logger abstraction to centralize the application of the silent flag
  626. * @method log
  627. * @param {string} msg message to log.
  628. * @param {string} cat log category.
  629. */
  630. log: function(msg, cat) {
  631. if (!this.silent) { Y.log(this.id + ': ' + msg, cat || 'info', 'event'); }
  632. },
  633.  
  634. /**
  635. * Notifies the subscribers. The callback functions will be executed
  636. * from the context specified when the event was created, and with the
  637. * following parameters:
  638. * <ul>
  639. * <li>The type of event</li>
  640. * <li>All of the arguments fire() was executed with as an array</li>
  641. * <li>The custom object (if any) that was passed into the subscribe()
  642. * method</li>
  643. * </ul>
  644. * @method fire
  645. * @param {Object*} arguments an arbitrary set of parameters to pass to
  646. * the handler.
  647. * @return {boolean} false if one of the subscribers returned false,
  648. * true otherwise.
  649. *
  650. */
  651. fire: function() {
  652.  
  653. // push is the fastest way to go from arguments to arrays
  654. // for most browsers currently
  655. // http://jsperf.com/push-vs-concat-vs-slice/2
  656.  
  657. var args = [];
  658. args.push.apply(args, arguments);
  659.  
  660. return this._fire(args);
  661. },
  662.  
  663. /**
  664. * Private internal implementation for `fire`, which is can be used directly by
  665. * `EventTarget` and other event module classes which have already converted from
  666. * an `arguments` list to an array, to avoid the repeated overhead.
  667. *
  668. * @method _fire
  669. * @private
  670. * @param {Array} args The array of arguments passed to be passed to handlers.
  671. * @return {boolean} false if one of the subscribers returned false, true otherwise.
  672. */
  673. _fire: function(args) {
  674.  
  675. if (this.fireOnce && this.fired) {
  676. this.log('fireOnce event: ' + this.type + ' already fired');
  677. return true;
  678. } else {
  679.  
  680. // this doesn't happen if the event isn't published
  681. // this.host._monitor('fire', this.type, args);
  682.  
  683. this.fired = true;
  684.  
  685. if (this.fireOnce) {
  686. this.firedWith = args;
  687. }
  688.  
  689. if (this.emitFacade) {
  690. return this.fireComplex(args);
  691. } else {
  692. return this.fireSimple(args);
  693. }
  694. }
  695. },
  696.  
  697. /**
  698. * Set up for notifying subscribers of non-emitFacade events.
  699. *
  700. * @method fireSimple
  701. * @param args {Array} Arguments passed to fire()
  702. * @return Boolean false if a subscriber returned false
  703. * @protected
  704. */
  705. fireSimple: function(args) {
  706. this.stopped = 0;
  707. this.prevented = 0;
  708. if (this.hasSubs()) {
  709. var subs = this.getSubs();
  710. this._procSubs(subs[0], args);
  711. this._procSubs(subs[1], args);
  712. }
  713. if (this.broadcast) {
  714. this._broadcast(args);
  715. }
  716. return this.stopped ? false : true;
  717. },
  718.  
  719. // Requires the event-custom-complex module for full funcitonality.
  720. fireComplex: function(args) {
  721. this.log('Missing event-custom-complex needed to emit a facade for: ' + this.type);
  722. args[0] = args[0] || {};
  723. return this.fireSimple(args);
  724. },
  725.  
  726. /**
  727. * Notifies a list of subscribers.
  728. *
  729. * @method _procSubs
  730. * @param subs {Array} List of subscribers
  731. * @param args {Array} Arguments passed to fire()
  732. * @param ef {}
  733. * @return Boolean false if a subscriber returns false or stops the event
  734. * propagation via e.stopPropagation(),
  735. * e.stopImmediatePropagation(), or e.halt()
  736. * @private
  737. */
  738. _procSubs: function(subs, args, ef) {
  739. var s, i, l;
  740.  
  741. for (i = 0, l = subs.length; i < l; i++) {
  742. s = subs[i];
  743. if (s && s.fn) {
  744. if (false === this._notify(s, args, ef)) {
  745. this.stopped = 2;
  746. }
  747. if (this.stopped === 2) {
  748. return false;
  749. }
  750. }
  751. }
  752.  
  753. return true;
  754. },
  755.  
  756. /**
  757. * Notifies the YUI instance if the event is configured with broadcast = 1,
  758. * and both the YUI instance and Y.Global if configured with broadcast = 2.
  759. *
  760. * @method _broadcast
  761. * @param args {Array} Arguments sent to fire()
  762. * @private
  763. */
  764. _broadcast: function(args) {
  765. if (!this.stopped && this.broadcast) {
  766.  
  767. var a = args.concat();
  768. a.unshift(this.type);
  769.  
  770. if (this.host !== Y) {
  771. Y.fire.apply(Y, a);
  772. }
  773.  
  774. if (this.broadcast === 2) {
  775. Y.Global.fire.apply(Y.Global, a);
  776. }
  777. }
  778. },
  779.  
  780. /**
  781. * Removes all listeners
  782. * @method unsubscribeAll
  783. * @return {Number} The number of listeners unsubscribed.
  784. * @deprecated use detachAll.
  785. */
  786. unsubscribeAll: function() {
  787. return this.detachAll.apply(this, arguments);
  788. },
  789.  
  790. /**
  791. * Removes all listeners
  792. * @method detachAll
  793. * @return {Number} The number of listeners unsubscribed.
  794. */
  795. detachAll: function() {
  796. return this.detach();
  797. },
  798.  
  799. /**
  800. * Deletes the subscriber from the internal store of on() and after()
  801. * subscribers.
  802. *
  803. * @method _delete
  804. * @param s subscriber object.
  805. * @param subs (optional) on or after subscriber array
  806. * @param index (optional) The index found.
  807. * @private
  808. */
  809. _delete: function(s, subs, i) {
  810. var when = s._when;
  811.  
  812. if (!subs) {
  813. subs = (when === AFTER) ? this._afters : this._subscribers;
  814. }
  815.  
  816. if (subs) {
  817. i = YArray.indexOf(subs, s, 0);
  818.  
  819. if (s && subs[i] === s) {
  820. subs.splice(i, 1);
  821. }
  822. }
  823.  
  824. if (this._kds) {
  825. if (when === AFTER) {
  826. delete this.afters[s.id];
  827. } else {
  828. delete this.subscribers[s.id];
  829. }
  830. }
  831.  
  832. if (this.monitored && this.host) {
  833. this.host._monitor('detach', this, {
  834. ce: this,
  835. sub: s
  836. });
  837. }
  838.  
  839. if (s) {
  840. s.deleted = true;
  841. }
  842. }
  843. };
  844.