events.js 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  1. define(function(require, exports, module) {
  2. "use strict";
  3. var Util = require('./util');
  4. // Returns a function that will be executed at most one time, no matter how
  5. // often you call it. Useful for lazy initialization.
  6. var _once = function(func) {
  7. var ran = false,
  8. memo;
  9. return function() {
  10. if (ran) return memo;
  11. ran = true;
  12. memo = func.apply(this, arguments);
  13. func = null;
  14. return memo;
  15. };
  16. };
  17. /**
  18. * @discription events
  19. * @mixin
  20. */
  21. var Events = {
  22. // Bind an event to a `callback` function. Passing `"all"` will bind
  23. // the callback to all events fired.
  24. on: function(name, callback, context) {
  25. if (!eventsApi(this, 'on', name, [callback, context]) || !callback) return this;
  26. this._events || (this._events = {});
  27. var events = this._events[name] || (this._events[name] = []);
  28. events.push({
  29. callback: callback,
  30. context: context,
  31. ctx: context || this
  32. });
  33. return this;
  34. },
  35. // Bind an event to only be triggered a single time. After the first time
  36. // the callback is invoked, it will be removed.
  37. once: function(name, callback, context) {
  38. if (!eventsApi(this, 'once', name, [callback, context]) || !callback) return this;
  39. var self = this;
  40. var once = _once(function() {
  41. self.off(name, once);
  42. callback.apply(this, arguments);
  43. });
  44. once._callback = callback;
  45. return this.on(name, once, context);
  46. },
  47. // Remove one or many callbacks. If `context` is null, removes all
  48. // callbacks with that function. If `callback` is null, removes all
  49. // callbacks for the event. If `name` is null, removes all bound
  50. // callbacks for all events.
  51. off: function(name, callback, context) {
  52. if (!this._events || !eventsApi(this, 'off', name, [callback, context])) return this;
  53. // Remove all callbacks for all events.
  54. if (!name && !callback && !context) {
  55. this._events = void 0;
  56. return this;
  57. }
  58. var names = name ? [name] : Object.keys(this._events);
  59. for (var i = 0, length = names.length; i < length; i++) {
  60. name = names[i];
  61. // Bail out if there are no events stored.
  62. var events = this._events[name];
  63. if (!events) continue;
  64. // Remove all callbacks for this event.
  65. if (!callback && !context) {
  66. delete this._events[name];
  67. continue;
  68. }
  69. // Find any remaining events.
  70. var remaining = [];
  71. for (var j = 0, k = events.length; j < k; j++) {
  72. var event = events[j];
  73. if (
  74. callback && callback !== event.callback &&
  75. callback !== event.callback._callback ||
  76. context && context !== event.context
  77. ) {
  78. remaining.push(event);
  79. }
  80. }
  81. // Replace events if there are any remaining. Otherwise, clean up.
  82. if (remaining.length) {
  83. this._events[name] = remaining;
  84. } else {
  85. delete this._events[name];
  86. }
  87. }
  88. return this;
  89. },
  90. // Trigger one or many events, firing all bound callbacks. Callbacks are
  91. // passed the same arguments as `trigger` is, apart from the event name
  92. // (unless you're listening on `"all"`, which will cause your callback to
  93. // receive the true name of the event as the first argument).
  94. trigger: function(name) {
  95. if (!this._events) return this;
  96. var args = Array.prototype.slice.call(arguments, 1);
  97. if (!eventsApi(this, 'trigger', name, args)) return this;
  98. var events = this._events[name];
  99. var allEvents = this._events.all;
  100. if (events) triggerEvents(events, args);
  101. if (allEvents) triggerEvents(allEvents, arguments);
  102. return this;
  103. },
  104. // Inversion-of-control versions of `on` and `once`. Tell *this* object to
  105. // listen to an event in another object ... keeping track of what it's
  106. // listening to.
  107. listenTo: function(obj, name, callback) {
  108. var listeningTo = this._listeningTo || (this._listeningTo = {});
  109. var id = obj._listenId || (obj._listenId = Util.guid('l'));
  110. listeningTo[id] = obj;
  111. if (!callback && typeof name === 'object') callback = this;
  112. obj.on(name, callback, this);
  113. return this;
  114. },
  115. listenToOnce: function(obj, name, callback) {
  116. if (typeof name === 'object') {
  117. for (var event in name) this.listenToOnce(obj, event, name[event]);
  118. return this;
  119. }
  120. var cb = _once(function() {
  121. this.stopListening(obj, name, cb);
  122. callback.apply(this, arguments);
  123. });
  124. cb._callback = callback;
  125. return this.listenTo(obj, name, cb);
  126. },
  127. // Tell this object to stop listening to either specific events ... or
  128. // to every object it's currently listening to.
  129. stopListening: function(obj, name, callback) {
  130. var listeningTo = this._listeningTo;
  131. if (!listeningTo) return this;
  132. var remove = !name && !callback;
  133. if (!callback && typeof name === 'object') callback = this;
  134. if (obj)(listeningTo = {})[obj._listenId] = obj;
  135. for (var id in listeningTo) {
  136. obj = listeningTo[id];
  137. obj.off(name, callback, this);
  138. if (remove || Util.isEmpty(obj._events)) delete this._listeningTo[id];
  139. }
  140. return this;
  141. }
  142. };
  143. // Regular expression used to split event strings.
  144. var eventSplitter = /\s+/;
  145. // Implement fancy features of the Events API such as multiple event
  146. // names `"change blur"` and jQuery-style event maps `{change: action}`
  147. // in terms of the existing API.
  148. var eventsApi = function(obj, action, name, rest) {
  149. if (!name) return true;
  150. // Handle event maps.
  151. if (typeof name === 'object') {
  152. for (var key in name) {
  153. obj[action].apply(obj, [key, name[key]].concat(rest));
  154. }
  155. return false;
  156. }
  157. // Handle space separated event names.
  158. if (eventSplitter.test(name)) {
  159. var names = name.split(eventSplitter);
  160. for (var i = 0, length = names.length; i < length; i++) {
  161. obj[action].apply(obj, [names[i]].concat(rest));
  162. }
  163. return false;
  164. }
  165. return true;
  166. };
  167. // A difficult-to-believe, but optimized internal dispatch function for
  168. // triggering events. Tries to keep the usual cases speedy (most internal
  169. var triggerEvents = function(events, args) {
  170. var ev, i = -1,
  171. l = events.length,
  172. a1 = args[0],
  173. a2 = args[1],
  174. a3 = args[2];
  175. switch (args.length) {
  176. case 0:
  177. while (++i < l)(ev = events[i]).callback.call(ev.ctx);
  178. return;
  179. case 1:
  180. while (++i < l)(ev = events[i]).callback.call(ev.ctx, a1);
  181. return;
  182. case 2:
  183. while (++i < l)(ev = events[i]).callback.call(ev.ctx, a1, a2);
  184. return;
  185. case 3:
  186. while (++i < l)(ev = events[i]).callback.call(ev.ctx, a1, a2, a3);
  187. return;
  188. default:
  189. while (++i < l)(ev = events[i]).callback.apply(ev.ctx, args);
  190. return;
  191. }
  192. };
  193. // Aliases for backwards compatibility.
  194. Events.bind = Events.on;
  195. Events.unbind = Events.off;
  196. if (typeof module == 'object' && module.exports) {
  197. module.exports = Events;
  198. }
  199. /** ignored by jsdoc **/
  200. else {
  201. return Events;
  202. }
  203. });