-
Notifications
You must be signed in to change notification settings - Fork 3k
/
FromEventObservable.ts
165 lines (148 loc) · 6.62 KB
/
FromEventObservable.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
import { Observable } from '../Observable';
import { tryCatch } from '../util/tryCatch';
import { isFunction } from '../util/isFunction';
import { errorObject } from '../util/errorObject';
import { Subscription } from '../Subscription';
import { Subscriber } from '../Subscriber';
const toString: Function = Object.prototype.toString;
export type NodeStyleEventEmitter = {
addListener: (eventName: string, handler: Function) => void;
removeListener: (eventName: string, handler: Function) => void;
};
function isNodeStyleEventEmitter(sourceObj: any): sourceObj is NodeStyleEventEmitter {
return !!sourceObj && typeof sourceObj.addListener === 'function' && typeof sourceObj.removeListener === 'function';
}
export type JQueryStyleEventEmitter = {
on: (eventName: string, handler: Function) => void;
off: (eventName: string, handler: Function) => void;
};
function isJQueryStyleEventEmitter(sourceObj: any): sourceObj is JQueryStyleEventEmitter {
return !!sourceObj && typeof sourceObj.on === 'function' && typeof sourceObj.off === 'function';
}
function isNodeList(sourceObj: any): sourceObj is NodeList {
return !!sourceObj && toString.call(sourceObj) === '[object NodeList]';
}
function isHTMLCollection(sourceObj: any): sourceObj is HTMLCollection {
return !!sourceObj && toString.call(sourceObj) === '[object HTMLCollection]';
}
function isEventTarget(sourceObj: any): sourceObj is EventTarget {
return !!sourceObj && typeof sourceObj.addEventListener === 'function' && typeof sourceObj.removeEventListener === 'function';
}
export type EventTargetLike = EventTarget | NodeStyleEventEmitter | JQueryStyleEventEmitter | NodeList | HTMLCollection;
export type EventListenerOptions = {
capture?: boolean;
passive?: boolean;
once?: boolean;
} | boolean;
export type SelectorMethodSignature<T> = (...args: Array<any>) => T;
/**
* We need this JSDoc comment for affecting ESDoc.
* @extends {Ignored}
* @hide true
*/
export class FromEventObservable<T> extends Observable<T> {
/* tslint:disable:max-line-length */
static create<T>(target: EventTargetLike, eventName: string): Observable<T>;
static create<T>(target: EventTargetLike, eventName: string, selector: SelectorMethodSignature<T>): Observable<T>;
static create<T>(target: EventTargetLike, eventName: string, options: EventListenerOptions): Observable<T>;
static create<T>(target: EventTargetLike, eventName: string, options: EventListenerOptions, selector: SelectorMethodSignature<T>): Observable<T>;
/* tslint:enable:max-line-length */
/**
* Creates an Observable that emits events of a specific type coming from the
* given event target.
*
* <span class="informal">Creates an Observable from DOM events, or Node
* EventEmitter events or others.</span>
*
* <img src="./img/fromEvent.png" width="100%">
*
* Creates an Observable by attaching an event listener to an "event target",
* which may be an object with `addEventListener` and `removeEventListener`,
* a Node.js EventEmitter, a jQuery style EventEmitter, a NodeList from the
* DOM, or an HTMLCollection from the DOM. The event handler is attached when
* the output Observable is subscribed, and removed when the Subscription is
* unsubscribed.
*
* @example <caption>Emits clicks happening on the DOM document</caption>
* var clicks = Rx.Observable.fromEvent(document, 'click');
* clicks.subscribe(x => console.log(x));
*
* // Results in:
* // MouseEvent object logged to console everytime a click
* // occurs on the document.
*
* @see {@link from}
* @see {@link fromEventPattern}
*
* @param {EventTargetLike} target The DOMElement, event target, Node.js
* EventEmitter, NodeList or HTMLCollection to attach the event handler to.
* @param {string} eventName The event name of interest, being emitted by the
* `target`.
* @param {EventListenerOptions} [options] Options to pass through to addEventListener
* @param {SelectorMethodSignature<T>} [selector] An optional function to
* post-process results. It takes the arguments from the event handler and
* should return a single value.
* @return {Observable<T>}
* @static true
* @name fromEvent
* @owner Observable
*/
static create<T>(target: EventTargetLike,
eventName: string,
options?: EventListenerOptions,
selector?: SelectorMethodSignature<T>): Observable<T> {
if (isFunction(options)) {
selector = <any>options;
options = undefined;
}
return new FromEventObservable(target, eventName, selector, options);
}
constructor(private sourceObj: EventTargetLike,
private eventName: string,
private selector?: SelectorMethodSignature<T>,
private options?: EventListenerOptions) {
super();
}
private static setupSubscription<T>(sourceObj: EventTargetLike,
eventName: string,
handler: Function,
subscriber: Subscriber<T>,
options?: EventListenerOptions) {
let unsubscribe: () => void;
if (isNodeList(sourceObj) || isHTMLCollection(sourceObj)) {
for (let i = 0, len = sourceObj.length; i < len; i++) {
FromEventObservable.setupSubscription(sourceObj[i], eventName, handler, subscriber, options);
}
} else if (isEventTarget(sourceObj)) {
const source = sourceObj;
sourceObj.addEventListener(eventName, <EventListener>handler, <boolean>options);
unsubscribe = () => source.removeEventListener(eventName, <EventListener>handler);
} else if (isJQueryStyleEventEmitter(sourceObj)) {
const source = sourceObj;
sourceObj.on(eventName, handler);
unsubscribe = () => source.off(eventName, handler);
} else if (isNodeStyleEventEmitter(sourceObj)) {
const source = sourceObj;
sourceObj.addListener(eventName, handler);
unsubscribe = () => source.removeListener(eventName, handler);
} else {
throw new TypeError('Invalid event target');
}
subscriber.add(new Subscription(unsubscribe));
}
protected _subscribe(subscriber: Subscriber<T>) {
const sourceObj = this.sourceObj;
const eventName = this.eventName;
const options = this.options;
const selector = this.selector;
let handler = selector ? (...args: any[]) => {
let result = tryCatch(selector)(...args);
if (result === errorObject) {
subscriber.error(errorObject.e);
} else {
subscriber.next(result);
}
} : (e: any) => subscriber.next(e);
FromEventObservable.setupSubscription(sourceObj, eventName, handler, subscriber, options);
}
}