Show:
  1. /**
  2. * Adds event facades, preventable default behavior, and bubbling.
  3. * events.
  4. * @module event-custom
  5. * @submodule event-custom-complex
  6. */
  7. var FACADE,
  8. FACADE_KEYS,
  9. YObject = Y.Object,
  10. key,
  11. EMPTY = {},
  12. CEProto = Y.CustomEvent.prototype,
  13. ETProto = Y.EventTarget.prototype,
  14. mixFacadeProps = function(facade, payload) {
  15. var p;
  16. for (p in payload) {
  17. if (!(FACADE_KEYS.hasOwnProperty(p))) {
  18. facade[p] = payload[p];
  19. }
  20. }
  21. };
  22. /**
  23. * Wraps and protects a custom event for use when emitFacade is set to true.
  24. * Requires the event-custom-complex module
  25. * @class EventFacade
  26. * @param e {Event} the custom event
  27. * @param currentTarget {HTMLElement} the element the listener was attached to
  28. */
  29. Y.EventFacade = function(e, currentTarget) {
  30. if (!e) {
  31. e = EMPTY;
  32. }
  33. this._event = e;
  34. /**
  35. * The arguments passed to fire
  36. * @property details
  37. * @type Array
  38. */
  39. this.details = e.details;
  40. /**
  41. * The event type, this can be overridden by the fire() payload
  42. * @property type
  43. * @type string
  44. */
  45. this.type = e.type;
  46. /**
  47. * The real event type
  48. * @property _type
  49. * @type string
  50. * @private
  51. */
  52. this._type = e.type;
  53. //////////////////////////////////////////////////////
  54. /**
  55. * Node reference for the targeted eventtarget
  56. * @property target
  57. * @type Node
  58. */
  59. this.target = e.target;
  60. /**
  61. * Node reference for the element that the listener was attached to.
  62. * @property currentTarget
  63. * @type Node
  64. */
  65. this.currentTarget = currentTarget;
  66. /**
  67. * Node reference to the relatedTarget
  68. * @property relatedTarget
  69. * @type Node
  70. */
  71. this.relatedTarget = e.relatedTarget;
  72. };
  73. Y.mix(Y.EventFacade.prototype, {
  74. /**
  75. * Stops the propagation to the next bubble target
  76. * @method stopPropagation
  77. */
  78. stopPropagation: function() {
  79. this._event.stopPropagation();
  80. this.stopped = 1;
  81. },
  82. /**
  83. * Stops the propagation to the next bubble target and
  84. * prevents any additional listeners from being exectued
  85. * on the current target.
  86. * @method stopImmediatePropagation
  87. */
  88. stopImmediatePropagation: function() {
  89. this._event.stopImmediatePropagation();
  90. this.stopped = 2;
  91. },
  92. /**
  93. * Prevents the event's default behavior
  94. * @method preventDefault
  95. */
  96. preventDefault: function() {
  97. this._event.preventDefault();
  98. this.prevented = 1;
  99. },
  100. /**
  101. * Stops the event propagation and prevents the default
  102. * event behavior.
  103. * @method halt
  104. * @param immediate {boolean} if true additional listeners
  105. * on the current target will not be executed
  106. */
  107. halt: function(immediate) {
  108. this._event.halt(immediate);
  109. this.prevented = 1;
  110. this.stopped = (immediate) ? 2 : 1;
  111. }
  112. });
  113. CEProto.fireComplex = function(args) {
  114. var es,
  115. ef,
  116. q,
  117. queue,
  118. ce,
  119. ret = true,
  120. events,
  121. subs,
  122. ons,
  123. afters,
  124. afterQueue,
  125. postponed,
  126. prevented,
  127. preventedFn,
  128. defaultFn,
  129. self = this,
  130. host = self.host || self,
  131. next,
  132. oldbubble,
  133. stack = self.stack,
  134. yuievt = host._yuievt,
  135. hasPotentialSubscribers;
  136. if (stack) {
  137. // queue this event if the current item in the queue bubbles
  138. if (self.queuable && self.type !== stack.next.type) {
  139. self.log('queue ' + self.type);
  140. if (!stack.queue) {
  141. stack.queue = [];
  142. }
  143. stack.queue.push([self, args]);
  144. return true;
  145. }
  146. }
  147. hasPotentialSubscribers = self.hasSubs() || yuievt.hasTargets || self.broadcast;
  148. self.target = self.target || host;
  149. self.currentTarget = host;
  150. self.details = args.concat();
  151. if (hasPotentialSubscribers) {
  152. es = stack || {
  153. id: self.id, // id of the first event in the stack
  154. next: self,
  155. silent: self.silent,
  156. stopped: 0,
  157. prevented: 0,
  158. bubbling: null,
  159. type: self.type,
  160. // defaultFnQueue: new Y.Queue(),
  161. defaultTargetOnly: self.defaultTargetOnly
  162. };
  163. subs = self.getSubs();
  164. ons = subs[0];
  165. afters = subs[1];
  166. self.stopped = (self.type !== es.type) ? 0 : es.stopped;
  167. self.prevented = (self.type !== es.type) ? 0 : es.prevented;
  168. if (self.stoppedFn) {
  169. // PERF TODO: Can we replace with callback, like preventedFn. Look into history
  170. events = new Y.EventTarget({
  171. fireOnce: true,
  172. context: host
  173. });
  174. self.events = events;
  175. events.on('stopped', self.stoppedFn);
  176. }
  177. // self.log("Firing " + self + ", " + "args: " + args);
  178. self.log("Firing " + self.type);
  179. self._facade = null; // kill facade to eliminate stale properties
  180. ef = self._createFacade(args);
  181. if (ons) {
  182. self._procSubs(ons, args, ef);
  183. }
  184. // bubble if this is hosted in an event target and propagation has not been stopped
  185. if (self.bubbles && host.bubble && !self.stopped) {
  186. oldbubble = es.bubbling;
  187. es.bubbling = self.type;
  188. if (es.type !== self.type) {
  189. es.stopped = 0;
  190. es.prevented = 0;
  191. }
  192. ret = host.bubble(self, args, null, es);
  193. self.stopped = Math.max(self.stopped, es.stopped);
  194. self.prevented = Math.max(self.prevented, es.prevented);
  195. es.bubbling = oldbubble;
  196. }
  197. prevented = self.prevented;
  198. if (prevented) {
  199. preventedFn = self.preventedFn;
  200. if (preventedFn) {
  201. preventedFn.apply(host, args);
  202. }
  203. } else {
  204. defaultFn = self.defaultFn;
  205. if (defaultFn && ((!self.defaultTargetOnly && !es.defaultTargetOnly) || host === ef.target)) {
  206. defaultFn.apply(host, args);
  207. }
  208. }
  209. // broadcast listeners are fired as discreet events on the
  210. // YUI instance and potentially the YUI global.
  211. if (self.broadcast) {
  212. self._broadcast(args);
  213. }
  214. if (afters && !self.prevented && self.stopped < 2) {
  215. // Queue the after
  216. afterQueue = es.afterQueue;
  217. if (es.id === self.id || self.type !== yuievt.bubbling) {
  218. self._procSubs(afters, args, ef);
  219. if (afterQueue) {
  220. while ((next = afterQueue.last())) {
  221. next();
  222. }
  223. }
  224. } else {
  225. postponed = afters;
  226. if (es.execDefaultCnt) {
  227. postponed = Y.merge(postponed);
  228. Y.each(postponed, function(s) {
  229. s.postponed = true;
  230. });
  231. }
  232. if (!afterQueue) {
  233. es.afterQueue = new Y.Queue();
  234. }
  235. es.afterQueue.add(function() {
  236. self._procSubs(postponed, args, ef);
  237. });
  238. }
  239. }
  240. self.target = null;
  241. if (es.id === self.id) {
  242. queue = es.queue;
  243. if (queue) {
  244. while (queue.length) {
  245. q = queue.pop();
  246. ce = q[0];
  247. // set up stack to allow the next item to be processed
  248. es.next = ce;
  249. ce._fire(q[1]);
  250. }
  251. }
  252. self.stack = null;
  253. }
  254. ret = !(self.stopped);
  255. if (self.type !== yuievt.bubbling) {
  256. es.stopped = 0;
  257. es.prevented = 0;
  258. self.stopped = 0;
  259. self.prevented = 0;
  260. }
  261. } else {
  262. defaultFn = self.defaultFn;
  263. if(defaultFn) {
  264. ef = self._createFacade(args);
  265. if ((!self.defaultTargetOnly) || (host === ef.target)) {
  266. defaultFn.apply(host, args);
  267. }
  268. }
  269. }
  270. // Kill the cached facade to free up memory.
  271. // Otherwise we have the facade from the last fire, sitting around forever.
  272. self._facade = null;
  273. return ret;
  274. };
  275. /**
  276. * @method _hasPotentialSubscribers
  277. * @for CustomEvent
  278. * @private
  279. * @return {boolean} Whether the event has potential subscribers or not
  280. */
  281. CEProto._hasPotentialSubscribers = function() {
  282. return this.hasSubs() || this.host._yuievt.hasTargets || this.broadcast;
  283. };
  284. /**
  285. * Internal utility method to create a new facade instance and
  286. * insert it into the fire argument list, accounting for any payload
  287. * merging which needs to happen.
  288. *
  289. * This used to be called `_getFacade`, but the name seemed inappropriate
  290. * when it was used without a need for the return value.
  291. *
  292. * @method _createFacade
  293. * @private
  294. * @param fireArgs {Array} The arguments passed to "fire", which need to be
  295. * shifted (and potentially merged) when the facade is added.
  296. * @return {EventFacade} The event facade created.
  297. */
  298. // TODO: Remove (private) _getFacade alias, once synthetic.js is updated.
  299. CEProto._createFacade = CEProto._getFacade = function(fireArgs) {
  300. var userArgs = this.details,
  301. firstArg = userArgs && userArgs[0],
  302. firstArgIsObj = (firstArg && (typeof firstArg === "object")),
  303. ef = this._facade;
  304. if (!ef) {
  305. ef = new Y.EventFacade(this, this.currentTarget);
  306. }
  307. if (firstArgIsObj) {
  308. // protect the event facade properties
  309. mixFacadeProps(ef, firstArg);
  310. // Allow the event type to be faked http://yuilibrary.com/projects/yui3/ticket/2528376
  311. if (firstArg.type) {
  312. ef.type = firstArg.type;
  313. }
  314. if (fireArgs) {
  315. fireArgs[0] = ef;
  316. }
  317. } else {
  318. if (fireArgs) {
  319. fireArgs.unshift(ef);
  320. }
  321. }
  322. // update the details field with the arguments
  323. ef.details = this.details;
  324. // use the original target when the event bubbled to this target
  325. ef.target = this.originalTarget || this.target;
  326. ef.currentTarget = this.currentTarget;
  327. ef.stopped = 0;
  328. ef.prevented = 0;
  329. this._facade = ef;
  330. return this._facade;
  331. };
  332. /**
  333. * Utility method to manipulate the args array passed in, to add the event facade,
  334. * if it's not already the first arg.
  335. *
  336. * @method _addFacadeToArgs
  337. * @private
  338. * @param {Array} The arguments to manipulate
  339. */
  340. CEProto._addFacadeToArgs = function(args) {
  341. var e = args[0];
  342. // Trying not to use instanceof, just to avoid potential cross Y edge case issues.
  343. if (!(e && e.halt && e.stopImmediatePropagation && e.stopPropagation && e._event)) {
  344. this._createFacade(args);
  345. }
  346. };
  347. /**
  348. * Stop propagation to bubble targets
  349. * @for CustomEvent
  350. * @method stopPropagation
  351. */
  352. CEProto.stopPropagation = function() {
  353. this.stopped = 1;
  354. if (this.stack) {
  355. this.stack.stopped = 1;
  356. }
  357. if (this.events) {
  358. this.events.fire('stopped', this);
  359. }
  360. };
  361. /**
  362. * Stops propagation to bubble targets, and prevents any remaining
  363. * subscribers on the current target from executing.
  364. * @method stopImmediatePropagation
  365. */
  366. CEProto.stopImmediatePropagation = function() {
  367. this.stopped = 2;
  368. if (this.stack) {
  369. this.stack.stopped = 2;
  370. }
  371. if (this.events) {
  372. this.events.fire('stopped', this);
  373. }
  374. };
  375. /**
  376. * Prevents the execution of this event's defaultFn
  377. * @method preventDefault
  378. */
  379. CEProto.preventDefault = function() {
  380. if (this.preventable) {
  381. this.prevented = 1;
  382. if (this.stack) {
  383. this.stack.prevented = 1;
  384. }
  385. }
  386. };
  387. /**
  388. * Stops the event propagation and prevents the default
  389. * event behavior.
  390. * @method halt
  391. * @param immediate {boolean} if true additional listeners
  392. * on the current target will not be executed
  393. */
  394. CEProto.halt = function(immediate) {
  395. if (immediate) {
  396. this.stopImmediatePropagation();
  397. } else {
  398. this.stopPropagation();
  399. }
  400. this.preventDefault();
  401. };
  402. /**
  403. * Registers another EventTarget as a bubble target. Bubble order
  404. * is determined by the order registered. Multiple targets can
  405. * be specified.
  406. *
  407. * Events can only bubble if emitFacade is true.
  408. *
  409. * Included in the event-custom-complex submodule.
  410. *
  411. * @method addTarget
  412. * @chainable
  413. * @param o {EventTarget} the target to add
  414. * @for EventTarget
  415. */
  416. ETProto.addTarget = function(o) {
  417. var etState = this._yuievt;
  418. if (!etState.targets) {
  419. etState.targets = {};
  420. }
  421. etState.targets[Y.stamp(o)] = o;
  422. etState.hasTargets = true;
  423. return this;
  424. };
  425. /**
  426. * Returns an array of bubble targets for this object.
  427. * @method getTargets
  428. * @return EventTarget[]
  429. */
  430. ETProto.getTargets = function() {
  431. var targets = this._yuievt.targets;
  432. return targets ? YObject.values(targets) : [];
  433. };
  434. /**
  435. * Removes a bubble target
  436. * @method removeTarget
  437. * @chainable
  438. * @param o {EventTarget} the target to remove
  439. * @for EventTarget
  440. */
  441. ETProto.removeTarget = function(o) {
  442. var targets = this._yuievt.targets;
  443. if (targets) {
  444. delete targets[Y.stamp(o, true)];
  445. if (YObject.size(targets) === 0) {
  446. this._yuievt.hasTargets = false;
  447. }
  448. }
  449. return this;
  450. };
  451. /**
  452. * Propagate an event. Requires the event-custom-complex module.
  453. * @method bubble
  454. * @param evt {CustomEvent} the custom event to propagate
  455. * @return {boolean} the aggregated return value from Event.Custom.fire
  456. * @for EventTarget
  457. */
  458. ETProto.bubble = function(evt, args, target, es) {
  459. var targs = this._yuievt.targets,
  460. ret = true,
  461. t,
  462. ce,
  463. i,
  464. bc,
  465. ce2,
  466. type = evt && evt.type,
  467. originalTarget = target || (evt && evt.target) || this,
  468. oldbubble;
  469. if (!evt || ((!evt.stopped) && targs)) {
  470. for (i in targs) {
  471. if (targs.hasOwnProperty(i)) {
  472. t = targs[i];
  473. ce = t._yuievt.events[type];
  474. if (t._hasSiblings) {
  475. ce2 = t.getSibling(type, ce);
  476. }
  477. if (ce2 && !ce) {
  478. ce = t.publish(type);
  479. }
  480. oldbubble = t._yuievt.bubbling;
  481. t._yuievt.bubbling = type;
  482. // if this event was not published on the bubble target,
  483. // continue propagating the event.
  484. if (!ce) {
  485. if (t._yuievt.hasTargets) {
  486. t.bubble(evt, args, originalTarget, es);
  487. }
  488. } else {
  489. if (ce2) {
  490. ce.sibling = ce2;
  491. }
  492. // set the original target to that the target payload on the facade is correct.
  493. ce.target = originalTarget;
  494. ce.originalTarget = originalTarget;
  495. ce.currentTarget = t;
  496. bc = ce.broadcast;
  497. ce.broadcast = false;
  498. // default publish may not have emitFacade true -- that
  499. // shouldn't be what the implementer meant to do
  500. ce.emitFacade = true;
  501. ce.stack = es;
  502. // TODO: See what's getting in the way of changing this to use
  503. // the more performant ce._fire(args || evt.details || []).
  504. // Something in Widget Parent/Child tests is not happy if we
  505. // change it - maybe evt.details related?
  506. ret = ret && ce.fire.apply(ce, args || evt.details || []);
  507. ce.broadcast = bc;
  508. ce.originalTarget = null;
  509. // stopPropagation() was called
  510. if (ce.stopped) {
  511. break;
  512. }
  513. }
  514. t._yuievt.bubbling = oldbubble;
  515. }
  516. }
  517. }
  518. return ret;
  519. };
  520. /**
  521. * @method _hasPotentialSubscribers
  522. * @for EventTarget
  523. * @private
  524. * @param {String} fullType The fully prefixed type name
  525. * @return {boolean} Whether the event has potential subscribers or not
  526. */
  527. ETProto._hasPotentialSubscribers = function(fullType) {
  528. var etState = this._yuievt,
  529. e = etState.events[fullType];
  530. if (e) {
  531. return e.hasSubs() || etState.hasTargets || e.broadcast;
  532. } else {
  533. return false;
  534. }
  535. };
  536. FACADE = new Y.EventFacade();
  537. FACADE_KEYS = {};
  538. // Flatten whitelist
  539. for (key in FACADE) {
  540. FACADE_KEYS[key] = true;
  541. }