Repositorio del curso CCOM4030 el semestre B91 del proyecto Artesanías con el Instituto de Cultura

fromEvent.ts 10KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245
  1. import { Observable } from '../Observable';
  2. import { isArray } from '../util/isArray';
  3. import { isFunction } from '../util/isFunction';
  4. import { Subscriber } from '../Subscriber';
  5. import { map } from '../operators/map';
  6. const toString: Function = (() => Object.prototype.toString)();
  7. export interface NodeStyleEventEmitter {
  8. addListener: (eventName: string | symbol, handler: NodeEventHandler) => this;
  9. removeListener: (eventName: string | symbol, handler: NodeEventHandler) => this;
  10. }
  11. export type NodeEventHandler = (...args: any[]) => void;
  12. // For APIs that implement `addListener` and `removeListener` methods that may
  13. // not use the same arguments or return EventEmitter values
  14. // such as React Native
  15. export interface NodeCompatibleEventEmitter {
  16. addListener: (eventName: string, handler: NodeEventHandler) => void | {};
  17. removeListener: (eventName: string, handler: NodeEventHandler) => void | {};
  18. }
  19. export interface JQueryStyleEventEmitter {
  20. on: (eventName: string, handler: Function) => void;
  21. off: (eventName: string, handler: Function) => void;
  22. }
  23. export interface HasEventTargetAddRemove<E> {
  24. addEventListener(type: string, listener: ((evt: E) => void) | null, options?: boolean | AddEventListenerOptions): void;
  25. removeEventListener(type: string, listener?: ((evt: E) => void) | null, options?: EventListenerOptions | boolean): void;
  26. }
  27. export type EventTargetLike<T> = HasEventTargetAddRemove<T> | NodeStyleEventEmitter | NodeCompatibleEventEmitter | JQueryStyleEventEmitter;
  28. export type FromEventTarget<T> = EventTargetLike<T> | ArrayLike<EventTargetLike<T>>;
  29. export interface EventListenerOptions {
  30. capture?: boolean;
  31. passive?: boolean;
  32. once?: boolean;
  33. }
  34. export interface AddEventListenerOptions extends EventListenerOptions {
  35. once?: boolean;
  36. passive?: boolean;
  37. }
  38. /* tslint:disable:max-line-length */
  39. export function fromEvent<T>(target: FromEventTarget<T>, eventName: string): Observable<T>;
  40. /** @deprecated resultSelector no longer supported, pipe to map instead */
  41. export function fromEvent<T>(target: FromEventTarget<T>, eventName: string, resultSelector: (...args: any[]) => T): Observable<T>;
  42. export function fromEvent<T>(target: FromEventTarget<T>, eventName: string, options: EventListenerOptions): Observable<T>;
  43. /** @deprecated resultSelector no longer supported, pipe to map instead */
  44. export function fromEvent<T>(target: FromEventTarget<T>, eventName: string, options: EventListenerOptions, resultSelector: (...args: any[]) => T): Observable<T>;
  45. /* tslint:enable:max-line-length */
  46. /**
  47. * Creates an Observable that emits events of a specific type coming from the
  48. * given event target.
  49. *
  50. * <span class="informal">Creates an Observable from DOM events, or Node.js
  51. * EventEmitter events or others.</span>
  52. *
  53. * ![](fromEvent.png)
  54. *
  55. * `fromEvent` accepts as a first argument event target, which is an object with methods
  56. * for registering event handler functions. As a second argument it takes string that indicates
  57. * type of event we want to listen for. `fromEvent` supports selected types of event targets,
  58. * which are described in detail below. If your event target does not match any of the ones listed,
  59. * you should use {@link fromEventPattern}, which can be used on arbitrary APIs.
  60. * When it comes to APIs supported by `fromEvent`, their methods for adding and removing event
  61. * handler functions have different names, but they all accept a string describing event type
  62. * and function itself, which will be called whenever said event happens.
  63. *
  64. * Every time resulting Observable is subscribed, event handler function will be registered
  65. * to event target on given event type. When that event fires, value
  66. * passed as a first argument to registered function will be emitted by output Observable.
  67. * When Observable is unsubscribed, function will be unregistered from event target.
  68. *
  69. * Note that if event target calls registered function with more than one argument, second
  70. * and following arguments will not appear in resulting stream. In order to get access to them,
  71. * you can pass to `fromEvent` optional project function, which will be called with all arguments
  72. * passed to event handler. Output Observable will then emit value returned by project function,
  73. * instead of the usual value.
  74. *
  75. * Remember that event targets listed below are checked via duck typing. It means that
  76. * no matter what kind of object you have and no matter what environment you work in,
  77. * you can safely use `fromEvent` on that object if it exposes described methods (provided
  78. * of course they behave as was described above). So for example if Node.js library exposes
  79. * event target which has the same method names as DOM EventTarget, `fromEvent` is still
  80. * a good choice.
  81. *
  82. * If the API you use is more callback then event handler oriented (subscribed
  83. * callback function fires only once and thus there is no need to manually
  84. * unregister it), you should use {@link bindCallback} or {@link bindNodeCallback}
  85. * instead.
  86. *
  87. * `fromEvent` supports following types of event targets:
  88. *
  89. * **DOM EventTarget**
  90. *
  91. * This is an object with `addEventListener` and `removeEventListener` methods.
  92. *
  93. * In the browser, `addEventListener` accepts - apart from event type string and event
  94. * handler function arguments - optional third parameter, which is either an object or boolean,
  95. * both used for additional configuration how and when passed function will be called. When
  96. * `fromEvent` is used with event target of that type, you can provide this values
  97. * as third parameter as well.
  98. *
  99. * **Node.js EventEmitter**
  100. *
  101. * An object with `addListener` and `removeListener` methods.
  102. *
  103. * **JQuery-style event target**
  104. *
  105. * An object with `on` and `off` methods
  106. *
  107. * **DOM NodeList**
  108. *
  109. * List of DOM Nodes, returned for example by `document.querySelectorAll` or `Node.childNodes`.
  110. *
  111. * Although this collection is not event target in itself, `fromEvent` will iterate over all Nodes
  112. * it contains and install event handler function in every of them. When returned Observable
  113. * is unsubscribed, function will be removed from all Nodes.
  114. *
  115. * **DOM HtmlCollection**
  116. *
  117. * Just as in case of NodeList it is a collection of DOM nodes. Here as well event handler function is
  118. * installed and removed in each of elements.
  119. *
  120. *
  121. * ## Examples
  122. * ### Emits clicks happening on the DOM document
  123. * ```ts
  124. * import { fromEvent } from 'rxjs';
  125. *
  126. * const clicks = fromEvent(document, 'click');
  127. * clicks.subscribe(x => console.log(x));
  128. *
  129. * // Results in:
  130. * // MouseEvent object logged to console every time a click
  131. * // occurs on the document.
  132. * ```
  133. *
  134. * ### Use addEventListener with capture option
  135. * ```ts
  136. * import { fromEvent } from 'rxjs';
  137. *
  138. * const clicksInDocument = fromEvent(document, 'click', true); // note optional configuration parameter
  139. * // which will be passed to addEventListener
  140. * const clicksInDiv = fromEvent(someDivInDocument, 'click');
  141. *
  142. * clicksInDocument.subscribe(() => console.log('document'));
  143. * clicksInDiv.subscribe(() => console.log('div'));
  144. *
  145. * // By default events bubble UP in DOM tree, so normally
  146. * // when we would click on div in document
  147. * // "div" would be logged first and then "document".
  148. * // Since we specified optional `capture` option, document
  149. * // will catch event when it goes DOWN DOM tree, so console
  150. * // will log "document" and then "div".
  151. * ```
  152. *
  153. * @see {@link bindCallback}
  154. * @see {@link bindNodeCallback}
  155. * @see {@link fromEventPattern}
  156. *
  157. * @param {FromEventTarget<T>} target The DOM EventTarget, Node.js
  158. * EventEmitter, JQuery-like event target, NodeList or HTMLCollection to attach the event handler to.
  159. * @param {string} eventName The event name of interest, being emitted by the
  160. * `target`.
  161. * @param {EventListenerOptions} [options] Options to pass through to addEventListener
  162. * @return {Observable<T>}
  163. * @name fromEvent
  164. */
  165. export function fromEvent<T>(
  166. target: FromEventTarget<T>,
  167. eventName: string,
  168. options?: EventListenerOptions | ((...args: any[]) => T),
  169. resultSelector?: ((...args: any[]) => T)
  170. ): Observable<T> {
  171. if (isFunction(options)) {
  172. // DEPRECATED PATH
  173. resultSelector = options;
  174. options = undefined;
  175. }
  176. if (resultSelector) {
  177. // DEPRECATED PATH
  178. return fromEvent<T>(target, eventName, <EventListenerOptions | undefined>options).pipe(
  179. map(args => isArray(args) ? resultSelector(...args) : resultSelector(args))
  180. );
  181. }
  182. return new Observable<T>(subscriber => {
  183. function handler(e: T) {
  184. if (arguments.length > 1) {
  185. subscriber.next(Array.prototype.slice.call(arguments));
  186. } else {
  187. subscriber.next(e);
  188. }
  189. }
  190. setupSubscription(target, eventName, handler, subscriber, options as EventListenerOptions);
  191. });
  192. }
  193. function setupSubscription<T>(sourceObj: FromEventTarget<T>, eventName: string,
  194. handler: (...args: any[]) => void, subscriber: Subscriber<T>,
  195. options?: EventListenerOptions) {
  196. let unsubscribe: () => void;
  197. if (isEventTarget(sourceObj)) {
  198. const source = sourceObj;
  199. sourceObj.addEventListener(eventName, handler, options);
  200. unsubscribe = () => source.removeEventListener(eventName, handler, options);
  201. } else if (isJQueryStyleEventEmitter(sourceObj)) {
  202. const source = sourceObj;
  203. sourceObj.on(eventName, handler);
  204. unsubscribe = () => source.off(eventName, handler);
  205. } else if (isNodeStyleEventEmitter(sourceObj)) {
  206. const source = sourceObj;
  207. sourceObj.addListener(eventName, handler as NodeEventHandler);
  208. unsubscribe = () => source.removeListener(eventName, handler as NodeEventHandler);
  209. } else if (sourceObj && (sourceObj as any).length) {
  210. for (let i = 0, len = (sourceObj as any).length; i < len; i++) {
  211. setupSubscription(sourceObj[i], eventName, handler, subscriber, options);
  212. }
  213. } else {
  214. throw new TypeError('Invalid event target');
  215. }
  216. subscriber.add(unsubscribe);
  217. }
  218. function isNodeStyleEventEmitter(sourceObj: any): sourceObj is NodeStyleEventEmitter {
  219. return sourceObj && typeof sourceObj.addListener === 'function' && typeof sourceObj.removeListener === 'function';
  220. }
  221. function isJQueryStyleEventEmitter(sourceObj: any): sourceObj is JQueryStyleEventEmitter {
  222. return sourceObj && typeof sourceObj.on === 'function' && typeof sourceObj.off === 'function';
  223. }
  224. function isEventTarget(sourceObj: any): sourceObj is HasEventTargetAddRemove<any> {
  225. return sourceObj && typeof sourceObj.addEventListener === 'function' && typeof sourceObj.removeEventListener === 'function';
  226. }