Event.js 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
  1. import Check from "./Check.js";
  2. import defined from "./defined.js";
  3. /**
  4. * A generic utility class for managing subscribers for a particular event.
  5. * This class is usually instantiated inside of a container class and
  6. * exposed as a property for others to subscribe to.
  7. *
  8. * @alias Event
  9. * @template Listener extends (...args: any[]) => void = (...args: any[]) => void
  10. * @constructor
  11. * @example
  12. * MyObject.prototype.myListener = function(arg1, arg2) {
  13. * this.myArg1Copy = arg1;
  14. * this.myArg2Copy = arg2;
  15. * }
  16. *
  17. * const myObjectInstance = new MyObject();
  18. * const evt = new Cesium.Event();
  19. * evt.addEventListener(MyObject.prototype.myListener, myObjectInstance);
  20. * evt.raiseEvent('1', '2');
  21. * evt.removeEventListener(MyObject.prototype.myListener);
  22. */
  23. function Event() {
  24. this._listeners = [];
  25. this._scopes = [];
  26. this._toRemove = [];
  27. this._insideRaiseEvent = false;
  28. }
  29. Object.defineProperties(Event.prototype, {
  30. /**
  31. * The number of listeners currently subscribed to the event.
  32. * @memberof Event.prototype
  33. * @type {Number}
  34. * @readonly
  35. */
  36. numberOfListeners: {
  37. get: function () {
  38. return this._listeners.length - this._toRemove.length;
  39. },
  40. },
  41. });
  42. /**
  43. * Registers a callback function to be executed whenever the event is raised.
  44. * An optional scope can be provided to serve as the <code>this</code> pointer
  45. * in which the function will execute.
  46. *
  47. * @param {Listener} listener The function to be executed when the event is raised.
  48. * @param {Object} [scope] An optional object scope to serve as the <code>this</code>
  49. * pointer in which the listener function will execute.
  50. * @returns {Event.RemoveCallback} A function that will remove this event listener when invoked.
  51. *
  52. * @see Event#raiseEvent
  53. * @see Event#removeEventListener
  54. */
  55. Event.prototype.addEventListener = function (listener, scope) {
  56. //>>includeStart('debug', pragmas.debug);
  57. Check.typeOf.func("listener", listener);
  58. //>>includeEnd('debug');
  59. this._listeners.push(listener);
  60. this._scopes.push(scope);
  61. const event = this;
  62. return function () {
  63. event.removeEventListener(listener, scope);
  64. };
  65. };
  66. /**
  67. * Unregisters a previously registered callback.
  68. *
  69. * @param {Listener} listener The function to be unregistered.
  70. * @param {Object} [scope] The scope that was originally passed to addEventListener.
  71. * @returns {Boolean} <code>true</code> if the listener was removed; <code>false</code> if the listener and scope are not registered with the event.
  72. *
  73. * @see Event#addEventListener
  74. * @see Event#raiseEvent
  75. */
  76. Event.prototype.removeEventListener = function (listener, scope) {
  77. //>>includeStart('debug', pragmas.debug);
  78. Check.typeOf.func("listener", listener);
  79. //>>includeEnd('debug');
  80. const listeners = this._listeners;
  81. const scopes = this._scopes;
  82. let index = -1;
  83. for (let i = 0; i < listeners.length; i++) {
  84. if (listeners[i] === listener && scopes[i] === scope) {
  85. index = i;
  86. break;
  87. }
  88. }
  89. if (index !== -1) {
  90. if (this._insideRaiseEvent) {
  91. //In order to allow removing an event subscription from within
  92. //a callback, we don't actually remove the items here. Instead
  93. //remember the index they are at and undefined their value.
  94. this._toRemove.push(index);
  95. listeners[index] = undefined;
  96. scopes[index] = undefined;
  97. } else {
  98. listeners.splice(index, 1);
  99. scopes.splice(index, 1);
  100. }
  101. return true;
  102. }
  103. return false;
  104. };
  105. function compareNumber(a, b) {
  106. return b - a;
  107. }
  108. /**
  109. * Raises the event by calling each registered listener with all supplied arguments.
  110. *
  111. * @param {...Parameters<Listener>} arguments This method takes any number of parameters and passes them through to the listener functions.
  112. *
  113. * @see Event#addEventListener
  114. * @see Event#removeEventListener
  115. */
  116. Event.prototype.raiseEvent = function () {
  117. this._insideRaiseEvent = true;
  118. let i;
  119. const listeners = this._listeners;
  120. const scopes = this._scopes;
  121. let length = listeners.length;
  122. for (i = 0; i < length; i++) {
  123. const listener = listeners[i];
  124. if (defined(listener)) {
  125. listeners[i].apply(scopes[i], arguments);
  126. }
  127. }
  128. //Actually remove items removed in removeEventListener.
  129. const toRemove = this._toRemove;
  130. length = toRemove.length;
  131. if (length > 0) {
  132. toRemove.sort(compareNumber);
  133. for (i = 0; i < length; i++) {
  134. const index = toRemove[i];
  135. listeners.splice(index, 1);
  136. scopes.splice(index, 1);
  137. }
  138. toRemove.length = 0;
  139. }
  140. this._insideRaiseEvent = false;
  141. };
  142. /**
  143. * A function that removes a listener.
  144. * @callback Event.RemoveCallback
  145. */
  146. export default Event;