events.js 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216
  1. /**
  2. * Durandal 2.1.0 Copyright (c) 2012 Blue Spire Consulting, Inc. All Rights Reserved.
  3. * Available via the MIT license.
  4. * see: http://durandaljs.com or https://github.com/BlueSpire/Durandal for details.
  5. */
  6. /**
  7. * Durandal events originate from backbone.js but also combine some ideas from signals.js as well as some additional improvements.
  8. * Events can be installed into any object and are installed into the `app` module by default for convenient app-wide eventing.
  9. * @module events
  10. * @requires system
  11. */
  12. define(['durandal/system'], function (system) {
  13. var eventSplitter = /\s+/;
  14. var Events = function() { };
  15. /**
  16. * Represents an event subscription.
  17. * @class Subscription
  18. */
  19. var Subscription = function(owner, events) {
  20. this.owner = owner;
  21. this.events = events;
  22. };
  23. /**
  24. * Attaches a callback to the event subscription.
  25. * @method then
  26. * @param {function} callback The callback function to invoke when the event is triggered.
  27. * @param {object} [context] An object to use as `this` when invoking the `callback`.
  28. * @chainable
  29. */
  30. Subscription.prototype.then = function (callback, context) {
  31. this.callback = callback || this.callback;
  32. this.context = context || this.context;
  33. if (!this.callback) {
  34. return this;
  35. }
  36. this.owner.on(this.events, this.callback, this.context);
  37. return this;
  38. };
  39. /**
  40. * Attaches a callback to the event subscription.
  41. * @method on
  42. * @param {function} [callback] The callback function to invoke when the event is triggered. If `callback` is not provided, the previous callback will be re-activated.
  43. * @param {object} [context] An object to use as `this` when invoking the `callback`.
  44. * @chainable
  45. */
  46. Subscription.prototype.on = Subscription.prototype.then;
  47. /**
  48. * Cancels the subscription.
  49. * @method off
  50. * @chainable
  51. */
  52. Subscription.prototype.off = function () {
  53. this.owner.off(this.events, this.callback, this.context);
  54. return this;
  55. };
  56. /**
  57. * Creates an object with eventing capabilities.
  58. * @class Events
  59. */
  60. /**
  61. * Creates a subscription or registers a callback for the specified event.
  62. * @method on
  63. * @param {string} events One or more events, separated by white space.
  64. * @param {function} [callback] The callback function to invoke when the event is triggered. If `callback` is not provided, a subscription instance is returned.
  65. * @param {object} [context] An object to use as `this` when invoking the `callback`.
  66. * @return {Subscription|Events} A subscription is returned if no callback is supplied, otherwise the events object is returned for chaining.
  67. */
  68. Events.prototype.on = function(events, callback, context) {
  69. var calls, event, list;
  70. if (!callback) {
  71. return new Subscription(this, events);
  72. } else {
  73. calls = this.callbacks || (this.callbacks = {});
  74. events = events.split(eventSplitter);
  75. while (event = events.shift()) {
  76. list = calls[event] || (calls[event] = []);
  77. list.push(callback, context);
  78. }
  79. return this;
  80. }
  81. };
  82. /**
  83. * Removes the callbacks for the specified events.
  84. * @method off
  85. * @param {string} [events] One or more events, separated by white space to turn off. If no events are specified, then the callbacks will be removed.
  86. * @param {function} [callback] The callback function to remove. If `callback` is not provided, all callbacks for the specified events will be removed.
  87. * @param {object} [context] The object that was used as `this`. Callbacks with this context will be removed.
  88. * @chainable
  89. */
  90. Events.prototype.off = function(events, callback, context) {
  91. var event, calls, list, i;
  92. // No events
  93. if (!(calls = this.callbacks)) {
  94. return this;
  95. }
  96. //removing all
  97. if (!(events || callback || context)) {
  98. delete this.callbacks;
  99. return this;
  100. }
  101. events = events ? events.split(eventSplitter) : system.keys(calls);
  102. // Loop through the callback list, splicing where appropriate.
  103. while (event = events.shift()) {
  104. if (!(list = calls[event]) || !(callback || context)) {
  105. delete calls[event];
  106. continue;
  107. }
  108. for (i = list.length - 2; i >= 0; i -= 2) {
  109. if (!(callback && list[i] !== callback || context && list[i + 1] !== context)) {
  110. list.splice(i, 2);
  111. }
  112. }
  113. }
  114. return this;
  115. };
  116. /**
  117. * Triggers the specified events.
  118. * @method trigger
  119. * @param {string} [events] One or more events, separated by white space to trigger.
  120. * @chainable
  121. */
  122. Events.prototype.trigger = function(events) {
  123. var event, calls, list, i, length, args, all, rest;
  124. if (!(calls = this.callbacks)) {
  125. return this;
  126. }
  127. rest = [];
  128. events = events.split(eventSplitter);
  129. for (i = 1, length = arguments.length; i < length; i++) {
  130. rest[i - 1] = arguments[i];
  131. }
  132. // For each event, walk through the list of callbacks twice, first to
  133. // trigger the event, then to trigger any `"all"` callbacks.
  134. while (event = events.shift()) {
  135. // Copy callback lists to prevent modification.
  136. if (all = calls.all) {
  137. all = all.slice();
  138. }
  139. if (list = calls[event]) {
  140. list = list.slice();
  141. }
  142. // Execute event callbacks.
  143. if (list) {
  144. for (i = 0, length = list.length; i < length; i += 2) {
  145. list[i].apply(list[i + 1] || this, rest);
  146. }
  147. }
  148. // Execute "all" callbacks.
  149. if (all) {
  150. args = [event].concat(rest);
  151. for (i = 0, length = all.length; i < length; i += 2) {
  152. all[i].apply(all[i + 1] || this, args);
  153. }
  154. }
  155. }
  156. return this;
  157. };
  158. /**
  159. * Creates a function that will trigger the specified events when called. Simplifies proxying jQuery (or other) events through to the events object.
  160. * @method proxy
  161. * @param {string} events One or more events, separated by white space to trigger by invoking the returned function.
  162. * @return {function} Calling the function will invoke the previously specified events on the events object.
  163. */
  164. Events.prototype.proxy = function(events) {
  165. var that = this;
  166. return (function(arg) {
  167. that.trigger(events, arg);
  168. });
  169. };
  170. /**
  171. * Creates an object with eventing capabilities.
  172. * @class EventsModule
  173. * @static
  174. */
  175. /**
  176. * Adds eventing capabilities to the specified object.
  177. * @method includeIn
  178. * @param {object} targetObject The object to add eventing capabilities to.
  179. */
  180. Events.includeIn = function(targetObject) {
  181. targetObject.on = Events.prototype.on;
  182. targetObject.off = Events.prototype.off;
  183. targetObject.trigger = Events.prototype.trigger;
  184. targetObject.proxy = Events.prototype.proxy;
  185. };
  186. return Events;
  187. });