Evented.js.html 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="utf-8">
  5. <title>JSDoc: Source: Evented.js</title>
  6. <script src="scripts/prettify/prettify.js"> </script>
  7. <script src="scripts/prettify/lang-css.js"> </script>
  8. <!--[if lt IE 9]>
  9. <script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
  10. <![endif]-->
  11. <link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
  12. <link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
  13. </head>
  14. <body>
  15. <div id="main">
  16. <h1 class="page-title">Source: Evented.js</h1>
  17. <section>
  18. <article>
  19. <pre class="prettyprint source linenums"><code>// Licensed Materials - Property of IBM
  20. //
  21. // IBM Watson Analytics
  22. //
  23. // (C) Copyright IBM Corp. 2015
  24. //
  25. // US Government Users Restricted Rights - Use, duplication or
  26. // disclosure restricted by GSA ADP Schedule Contract with IBM Corp.
  27. module.exports = ( function(
  28. Error,
  29. ObjectPolyfill,
  30. decl,
  31. Destroyable,
  32. EventArgs,
  33. WeakMap
  34. )
  35. {
  36. "use strict";
  37. /*global console*/
  38. var DEFAULT_ARGS = decl.freeze( new EventArgs() );
  39. // listenersRef is used by Evented to register and unregister itself.
  40. var g_listenersRef = new WeakMap();
  41. // Using a WeakMap prevents circular references and hides the listeners from the object.
  42. var g_eventHandles = new WeakMap();
  43. var ensureCallable = ObjectPolyfill.ensureCallable;
  44. /**
  45. * Create a new EventedHandle.
  46. * @class module:barejs.Evented~EventedHandle
  47. * @classdesc EventedHandle is the handle returned by {@link module:barejs.Evented#on Evented::on} and {@link module:barejs.Evented#once Evented::once}.
  48. */
  49. function EventedHandle( _listeners, _eventName, _listener )
  50. {
  51. Destroyable.call( this );
  52. g_listenersRef.set( this, _listeners );
  53. decl.defineProperties( this,
  54. {
  55. eventName: { enumerable: true, value: _eventName },
  56. listener: { configurable: true, enumerable: true, value: _listener },
  57. _attached: { writable: true, value: false }
  58. } );
  59. this.attach();
  60. }
  61. decl.declareClass( EventedHandle, Destroyable,
  62. /** @lends module:barejs.Evented~EventedHandle# */
  63. {
  64. eventName: null,
  65. listener: null,
  66. _attached: null,
  67. /**
  68. * Destroy the handle.
  69. */
  70. destroy: function destroy()
  71. {
  72. this.remove();
  73. delete this.listener;
  74. g_listenersRef["delete"]( this );
  75. Destroyable.prototype.destroy.call( this );
  76. },
  77. /**
  78. * Remove the handle. A removed handle can be re-attached using {@link module:barejs.Evented~EventedHandle#attach attach}.
  79. * @returns {boolean} True if the handle got detached.
  80. */
  81. remove: function remove()
  82. {
  83. var listeners;
  84. if ( this._attached &amp;&amp; this.listener &amp;&amp; ( listeners = g_listenersRef.get( this ) ) )
  85. {
  86. // Look up event listeners backwards, since destroy iterates backwards
  87. var idx = listeners.lastIndexOf( this );
  88. // If the last listener is removed, use pop, since it doesn't return an Array.
  89. if ( idx >= ( listeners.length - 1 ) )
  90. listeners.pop();
  91. /*istanbul ignore else: this is purely a sanity check, else path should never occur*/
  92. else if ( idx >= 0 )
  93. listeners.splice( idx, 1 );
  94. this._attached = false;
  95. return true;
  96. }
  97. return false;
  98. },
  99. /**
  100. * Attach the handle. A handle is attached by default, this method should only be used after the handle has been detached
  101. * using {@link module:barejs.Evented~EventedHandle#remove remove}.
  102. * @returns {boolean} True if the handle is attached.
  103. */
  104. attach: function attach()
  105. {
  106. var listeners;
  107. if ( !this._attached &amp;&amp; this.listener &amp;&amp; ( listeners = g_listenersRef.get( this ) ) )
  108. {
  109. listeners.push( this );
  110. this._attached = true;
  111. }
  112. return this._attached;
  113. },
  114. /**
  115. * Check if the handle is attached.
  116. * @returns {boolean} True if the handle is attached.
  117. */
  118. isAttached: function isAttached()
  119. {
  120. return this._attached;
  121. }
  122. } );
  123. /**
  124. * The evented constructor will invoke the Destroyable constructor to ensure the object is initialized correctly.
  125. * @class module:barejs.Evented
  126. * @extends module:barejs.Destroyable
  127. * @classdesc Evented is a base class that adds Eventing to JavaScript Objects.
  128. * Extends Destroyable to automatically remove listeners if the object is destroyed.
  129. * If handles given to the listener are {@link module:barejs.Destroyable#own own}ed (or manually destroyed at the appropiate time),
  130. * the event link between Evented and its listener will be removed as soon as either party is destroyed.
  131. */
  132. function Evented()
  133. {
  134. Destroyable.call( this );
  135. }
  136. decl.preventCast( Evented );
  137. return decl.declareClass( Evented, Destroyable,
  138. /** @lends module:barejs.Evented# */
  139. {
  140. /**
  141. * Destroy the Evented object. This will clean up the object, removing any links to listeners.
  142. */
  143. destroy: function destroy()
  144. {
  145. var handles = g_eventHandles.get( this ) || null;
  146. if ( handles )
  147. {
  148. g_eventHandles["delete"]( this );
  149. // Note: handles remove themselves from the array, so iterate backwards.
  150. // Do NOT use Destroyable.destroyAll for this!
  151. for ( var i = handles.length - 1; i >= 0; --i )
  152. handles[i].destroy();
  153. }
  154. Destroyable.prototype.destroy.call( this );
  155. },
  156. /**
  157. * Listen to an event. To stop listening, call destroy the returned handle. Listeners should {@link module:barejs.Destroyable#own own}
  158. * the handle returned by this method, so it is automatically disconnected when the listener is destroyed.
  159. * @param {string} _eventName The event to listen to.
  160. * @param {module:barejs.Evented~EventListener} _listener The callback to call if the event occurs.
  161. * @returns {module:barejs.Evented~EventedHandle}
  162. */
  163. on: function on( _eventName, _listener )
  164. {
  165. var handlers;
  166. // Note: inline check to see _listener is callable is performed first.
  167. if ( Destroyable.isDestroyed( ensureCallable( _listener, this )) )
  168. throw new Error( "The target object has been destroyed, cannot attach an event listener to it" );
  169. if ( !( ( "on" + _eventName ) in this ) )
  170. throw new Error( "The target object does not have a(n) " + _eventName + " event" );
  171. if ( !( handlers = g_eventHandles.get( this ) ) )
  172. g_eventHandles.set( this, handlers = [] );
  173. // No need to own the handle; handles are automatically destroyed
  174. return new EventedHandle( handlers, _eventName, _listener );
  175. },
  176. /**
  177. * Listen for an event, automatically detaching after one invocation of the listener.
  178. * @param {string} _eventName The event to listen to.
  179. * @param {module:barejs.Evented~EventListener} _listener The callback to call (once) if the event occurs.
  180. * @returns {module:barejs.Evented~EventedHandle}
  181. */
  182. once: function once( _eventName, _listener )
  183. {
  184. // Inline validation of _listener
  185. var handle = ensureCallable( _listener, null );
  186. return ( handle = this.on( _eventName, function()
  187. {
  188. /*istanbul ignore else: this is purely a sanity check, else path should never occur*/
  189. if ( handle )
  190. {
  191. handle.destroy();
  192. handle = null;
  193. }
  194. // Forward call
  195. return _listener.apply( this, arguments );
  196. } ) );
  197. },
  198. /**
  199. * Emit the event with _eventName.
  200. * @param {string} _eventName The name of the event to emit.
  201. * @param {module:barejs.EventArgs} _eventArgs the event args to emit.
  202. * @returns {module:barejs.EventArgs} _eventArgs (or null if not specified).
  203. */
  204. emit: function emit( _eventName, _eventArgs )
  205. {
  206. if ( Destroyable.isDestroyed( this ) )
  207. throw new Error( "The target object has been destroyed, cannot emit an event from it" );
  208. var args = _eventArgs || DEFAULT_ARGS,
  209. evtDef = "on" + _eventName,
  210. handles;
  211. if ( !( evtDef in this ) )
  212. throw new Error( "The " + _eventName + " event being emitted is not defined. Is this class missing an " + evtDef + " event definition?" );
  213. if ( !__RELEASE__ &amp;&amp; !( args instanceof EventArgs ) &amp;&amp; ( typeof console !== "undefined" ) )
  214. console.warn( ( this.constructor.name || "Evented" ) + " is emitting the \"" + _eventName + "\" event with arguments that are not EventArgs" );
  215. // First, call the local method (event definition)
  216. this[evtDef]( args, this );
  217. // If there are no handles, there are no listeners, so bail out
  218. if ( ( handles = g_eventHandles.get( this ) ) &amp;&amp; ( handles.length > 0 ) )
  219. {
  220. // Get the listeners for this event; using filter gives us a temporary array which is protected
  221. // from the handles array being modified as a side effect of handler execution.
  222. var listeners = handles.filter( function( _handle )
  223. {
  224. return _handle.eventName === _eventName;
  225. } );
  226. // Invoke the event handlers
  227. for ( var idx = 0, len = listeners.length, handle; idx &lt; len; idx++ )
  228. {
  229. // It is possible a handle got removed as a side effect of a previous handler; if so, ignore it.
  230. if ( ( handle = listeners[idx] )._attached &amp;&amp; handle.listener )
  231. handle.listener( args, this );
  232. }
  233. }
  234. return _eventArgs || null;
  235. },
  236. /**
  237. * Check if there is at least one listener for _eventName. It is highly recommended to just
  238. * emit an event instead of checking if there are listeners. This method is provided for edge
  239. * cases where building the event metadata is an expensive process, which should be avoided if
  240. * there are no listeners.
  241. * @param {string} _eventName The name of the event to check.
  242. * @returns {Boolean} True if there is at least one listener, false otherwise.
  243. */
  244. hasListener: function( _eventName )
  245. {
  246. var handles = g_eventHandles.get( this );
  247. return ( !!handles ) &amp;&amp; handles.some( function( _handle )
  248. {
  249. return _handle.eventName === _eventName;
  250. } );
  251. }
  252. } );
  253. /**
  254. * Event listeners are called with two arguments; the EventArgs and the sender.
  255. * @callback module:barejs.Evented~EventListener
  256. * @param {module:barejs.EventArgs} _eventArgs The EventArgs to the event.
  257. * @param {module:barejs.Evented} _sender The Evented object that emitted the event.
  258. */
  259. // End of module
  260. }(
  261. Error,
  262. require( "./polyfill/Object" ),
  263. require( "./decl" ),
  264. require( "./Destroyable" ),
  265. require( "./EventArgs" ),
  266. require( "./WeakMap" )
  267. ) );
  268. </code></pre>
  269. </article>
  270. </section>
  271. </div>
  272. <nav>
  273. <h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-barejs.html">barejs</a></li><li><a href="module-barejs_polyfill.html">barejs/polyfill</a></li><li><a href="module-barejs_polyfill_Intl.html">barejs/polyfill/Intl</a></li></ul><h3>Classes</h3><ul><li><a href="module-barejs.decl.html">decl</a></li><li><a href="module-barejs.decl-Enum.html">Enum</a></li><li><a href="module-barejs.decl-Interface.html">Interface</a></li><li><a href="module-barejs.decl-SpecialType.html">SpecialType</a></li><li><a href="module-barejs.Destroyable.html">Destroyable</a></li><li><a href="module-barejs.EventArgs.html">EventArgs</a></li><li><a href="module-barejs.Evented.html">Evented</a></li><li><a href="module-barejs.Evented-EventedHandle.html">EventedHandle</a></li><li><a href="module-barejs.Exception.html">Exception</a></li><li><a href="module-barejs_polyfill.Array.html">Array</a></li><li><a href="module-barejs_polyfill.Date.html">Date</a></li><li><a href="module-barejs_polyfill.EntryStore.html">EntryStore</a></li><li><a href="module-barejs_polyfill.EntryStore.Iterator.html">Iterator</a></li><li><a href="module-barejs_polyfill.Function.html">Function</a></li><li><a href="module-barejs_polyfill.Map.html">Map</a></li><li><a href="module-barejs_polyfill.Map-MapIterator.html">MapIterator</a></li><li><a href="module-barejs_polyfill.Math.html">Math</a></li><li><a href="module-barejs_polyfill.Number.html">Number</a></li><li><a href="module-barejs_polyfill.Object.html">Object</a></li><li><a href="module-barejs_polyfill.Promise.html">Promise</a></li><li><a href="module-barejs_polyfill.Set.html">Set</a></li><li><a href="module-barejs_polyfill.Set-SetIterator.html">SetIterator</a></li><li><a href="module-barejs_polyfill.String.html">String</a></li><li><a href="module-barejs_polyfill.Symbol.html">Symbol</a></li><li><a href="module-barejs_polyfill.WeakMap.html">WeakMap</a></li><li><a href="module-barejs_polyfill.WeakSet.html">WeakSet</a></li><li><a href="module-barejs_polyfill_Intl.DateTimeFormat.html">DateTimeFormat</a></li><li><a href="module-barejs_polyfill_Intl.DateTimeFormat-DateTimeFormatOptions.html">DateTimeFormatOptions</a></li><li><a href="module-barejs_polyfill_Intl.NumberFormat.html">NumberFormat</a></li><li><a href="module-barejs_polyfill_Intl.NumberFormat-NumberFormatOptions.html">NumberFormatOptions</a></li><li><a href="module-barejs_polyfill_Intl-Format.html">Format</a></li></ul>
  274. </nav>
  275. <br class="clear">
  276. <footer>
  277. Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a> on Wed Oct 03 2018 15:59:33 GMT+0200 (W. Europe Daylight Time)
  278. </footer>
  279. <script> prettyPrint(); </script>
  280. <script src="scripts/linenumber.js"> </script>
  281. </body>
  282. </html>