UNPKG

16.8 kBJavaScriptView Raw
1import { find, each, ArrayExt } from '@lumino/algorithm';
2
3// Copyright (c) Jupyter Development Team.
4/**
5 * A concrete implementation of `ISignal`.
6 *
7 * #### Example
8 * ```typescript
9 * import { ISignal, Signal } from '@lumino/signaling';
10 *
11 * class SomeClass {
12 *
13 * constructor(name: string) {
14 * this.name = name;
15 * }
16 *
17 * readonly name: string;
18 *
19 * get valueChanged: ISignal<this, number> {
20 * return this._valueChanged;
21 * }
22 *
23 * get value(): number {
24 * return this._value;
25 * }
26 *
27 * set value(value: number) {
28 * if (value === this._value) {
29 * return;
30 * }
31 * this._value = value;
32 * this._valueChanged.emit(value);
33 * }
34 *
35 * private _value = 0;
36 * private _valueChanged = new Signal<this, number>(this);
37 * }
38 *
39 * function logger(sender: SomeClass, value: number): void {
40 * console.log(sender.name, value);
41 * }
42 *
43 * let m1 = new SomeClass('foo');
44 * let m2 = new SomeClass('bar');
45 *
46 * m1.valueChanged.connect(logger);
47 * m2.valueChanged.connect(logger);
48 *
49 * m1.value = 42; // logs: foo 42
50 * m2.value = 17; // logs: bar 17
51 * ```
52 */
53var Signal = /** @class */ (function () {
54 /**
55 * Construct a new signal.
56 *
57 * @param sender - The sender which owns the signal.
58 */
59 function Signal(sender) {
60 this.sender = sender;
61 }
62 /**
63 * Connect a slot to the signal.
64 *
65 * @param slot - The slot to invoke when the signal is emitted.
66 *
67 * @param thisArg - The `this` context for the slot. If provided,
68 * this must be a non-primitive object.
69 *
70 * @returns `true` if the connection succeeds, `false` otherwise.
71 */
72 Signal.prototype.connect = function (slot, thisArg) {
73 return Private.connect(this, slot, thisArg);
74 };
75 /**
76 * Disconnect a slot from the signal.
77 *
78 * @param slot - The slot to disconnect from the signal.
79 *
80 * @param thisArg - The `this` context for the slot. If provided,
81 * this must be a non-primitive object.
82 *
83 * @returns `true` if the connection is removed, `false` otherwise.
84 */
85 Signal.prototype.disconnect = function (slot, thisArg) {
86 return Private.disconnect(this, slot, thisArg);
87 };
88 /**
89 * Emit the signal and invoke the connected slots.
90 *
91 * @param args - The args to pass to the connected slots.
92 *
93 * #### Notes
94 * Slots are invoked synchronously in connection order.
95 *
96 * Exceptions thrown by connected slots will be caught and logged.
97 */
98 Signal.prototype.emit = function (args) {
99 Private.emit(this, args);
100 };
101 return Signal;
102}());
103/**
104 * The namespace for the `Signal` class statics.
105 */
106(function (Signal) {
107 /**
108 * Remove all connections between a sender and receiver.
109 *
110 * @param sender - The sender object of interest.
111 *
112 * @param receiver - The receiver object of interest.
113 *
114 * #### Notes
115 * If a `thisArg` is provided when connecting a signal, that object
116 * is considered the receiver. Otherwise, the `slot` is considered
117 * the receiver.
118 */
119 function disconnectBetween(sender, receiver) {
120 Private.disconnectBetween(sender, receiver);
121 }
122 Signal.disconnectBetween = disconnectBetween;
123 /**
124 * Remove all connections where the given object is the sender.
125 *
126 * @param sender - The sender object of interest.
127 */
128 function disconnectSender(sender) {
129 Private.disconnectSender(sender);
130 }
131 Signal.disconnectSender = disconnectSender;
132 /**
133 * Remove all connections where the given object is the receiver.
134 *
135 * @param receiver - The receiver object of interest.
136 *
137 * #### Notes
138 * If a `thisArg` is provided when connecting a signal, that object
139 * is considered the receiver. Otherwise, the `slot` is considered
140 * the receiver.
141 */
142 function disconnectReceiver(receiver) {
143 Private.disconnectReceiver(receiver);
144 }
145 Signal.disconnectReceiver = disconnectReceiver;
146 /**
147 * Remove all connections where an object is the sender or receiver.
148 *
149 * @param object - The object of interest.
150 *
151 * #### Notes
152 * If a `thisArg` is provided when connecting a signal, that object
153 * is considered the receiver. Otherwise, the `slot` is considered
154 * the receiver.
155 */
156 function disconnectAll(object) {
157 Private.disconnectAll(object);
158 }
159 Signal.disconnectAll = disconnectAll;
160 /**
161 * Clear all signal data associated with the given object.
162 *
163 * @param object - The object for which the data should be cleared.
164 *
165 * #### Notes
166 * This removes all signal connections and any other signal data
167 * associated with the object.
168 */
169 function clearData(object) {
170 Private.disconnectAll(object);
171 }
172 Signal.clearData = clearData;
173 /**
174 * Get the signal exception handler.
175 *
176 * @returns The current exception handler.
177 *
178 * #### Notes
179 * The default exception handler is `console.error`.
180 */
181 function getExceptionHandler() {
182 return Private.exceptionHandler;
183 }
184 Signal.getExceptionHandler = getExceptionHandler;
185 /**
186 * Set the signal exception handler.
187 *
188 * @param handler - The function to use as the exception handler.
189 *
190 * @returns The old exception handler.
191 *
192 * #### Notes
193 * The exception handler is invoked when a slot throws an exception.
194 */
195 function setExceptionHandler(handler) {
196 var old = Private.exceptionHandler;
197 Private.exceptionHandler = handler;
198 return old;
199 }
200 Signal.setExceptionHandler = setExceptionHandler;
201})(Signal || (Signal = {}));
202/**
203 * The namespace for the module implementation details.
204 */
205var Private;
206(function (Private) {
207 /**
208 * The signal exception handler function.
209 */
210 Private.exceptionHandler = function (err) {
211 console.error(err);
212 };
213 /**
214 * Connect a slot to a signal.
215 *
216 * @param signal - The signal of interest.
217 *
218 * @param slot - The slot to invoke when the signal is emitted.
219 *
220 * @param thisArg - The `this` context for the slot. If provided,
221 * this must be a non-primitive object.
222 *
223 * @returns `true` if the connection succeeds, `false` otherwise.
224 */
225 function connect(signal, slot, thisArg) {
226 // Coerce a `null` `thisArg` to `undefined`.
227 thisArg = thisArg || undefined;
228 // Ensure the sender's array of receivers is created.
229 var receivers = receiversForSender.get(signal.sender);
230 if (!receivers) {
231 receivers = [];
232 receiversForSender.set(signal.sender, receivers);
233 }
234 // Bail if a matching connection already exists.
235 if (findConnection(receivers, signal, slot, thisArg)) {
236 return false;
237 }
238 // Choose the best object for the receiver.
239 var receiver = thisArg || slot;
240 // Ensure the receiver's array of senders is created.
241 var senders = sendersForReceiver.get(receiver);
242 if (!senders) {
243 senders = [];
244 sendersForReceiver.set(receiver, senders);
245 }
246 // Create a new connection and add it to the end of each array.
247 var connection = { signal: signal, slot: slot, thisArg: thisArg };
248 receivers.push(connection);
249 senders.push(connection);
250 // Indicate a successful connection.
251 return true;
252 }
253 Private.connect = connect;
254 /**
255 * Disconnect a slot from a signal.
256 *
257 * @param signal - The signal of interest.
258 *
259 * @param slot - The slot to disconnect from the signal.
260 *
261 * @param thisArg - The `this` context for the slot. If provided,
262 * this must be a non-primitive object.
263 *
264 * @returns `true` if the connection is removed, `false` otherwise.
265 */
266 function disconnect(signal, slot, thisArg) {
267 // Coerce a `null` `thisArg` to `undefined`.
268 thisArg = thisArg || undefined;
269 // Lookup the list of receivers, and bail if none exist.
270 var receivers = receiversForSender.get(signal.sender);
271 if (!receivers || receivers.length === 0) {
272 return false;
273 }
274 // Bail if no matching connection exits.
275 var connection = findConnection(receivers, signal, slot, thisArg);
276 if (!connection) {
277 return false;
278 }
279 // Choose the best object for the receiver.
280 var receiver = thisArg || slot;
281 // Lookup the array of senders, which is now known to exist.
282 var senders = sendersForReceiver.get(receiver);
283 // Clear the connection and schedule cleanup of the arrays.
284 connection.signal = null;
285 scheduleCleanup(receivers);
286 scheduleCleanup(senders);
287 // Indicate a successful disconnection.
288 return true;
289 }
290 Private.disconnect = disconnect;
291 /**
292 * Remove all connections between a sender and receiver.
293 *
294 * @param sender - The sender object of interest.
295 *
296 * @param receiver - The receiver object of interest.
297 */
298 function disconnectBetween(sender, receiver) {
299 // If there are no receivers, there is nothing to do.
300 var receivers = receiversForSender.get(sender);
301 if (!receivers || receivers.length === 0) {
302 return;
303 }
304 // If there are no senders, there is nothing to do.
305 var senders = sendersForReceiver.get(receiver);
306 if (!senders || senders.length === 0) {
307 return;
308 }
309 // Clear each connection between the sender and receiver.
310 each(senders, function (connection) {
311 // Skip connections which have already been cleared.
312 if (!connection.signal) {
313 return;
314 }
315 // Clear the connection if it matches the sender.
316 if (connection.signal.sender === sender) {
317 connection.signal = null;
318 }
319 });
320 // Schedule a cleanup of the senders and receivers.
321 scheduleCleanup(receivers);
322 scheduleCleanup(senders);
323 }
324 Private.disconnectBetween = disconnectBetween;
325 /**
326 * Remove all connections where the given object is the sender.
327 *
328 * @param sender - The sender object of interest.
329 */
330 function disconnectSender(sender) {
331 // If there are no receivers, there is nothing to do.
332 var receivers = receiversForSender.get(sender);
333 if (!receivers || receivers.length === 0) {
334 return;
335 }
336 // Clear each receiver connection.
337 each(receivers, function (connection) {
338 // Skip connections which have already been cleared.
339 if (!connection.signal) {
340 return;
341 }
342 // Choose the best object for the receiver.
343 var receiver = connection.thisArg || connection.slot;
344 // Clear the connection.
345 connection.signal = null;
346 // Cleanup the array of senders, which is now known to exist.
347 scheduleCleanup(sendersForReceiver.get(receiver));
348 });
349 // Schedule a cleanup of the receivers.
350 scheduleCleanup(receivers);
351 }
352 Private.disconnectSender = disconnectSender;
353 /**
354 * Remove all connections where the given object is the receiver.
355 *
356 * @param receiver - The receiver object of interest.
357 */
358 function disconnectReceiver(receiver) {
359 // If there are no senders, there is nothing to do.
360 var senders = sendersForReceiver.get(receiver);
361 if (!senders || senders.length === 0) {
362 return;
363 }
364 // Clear each sender connection.
365 each(senders, function (connection) {
366 // Skip connections which have already been cleared.
367 if (!connection.signal) {
368 return;
369 }
370 // Lookup the sender for the connection.
371 var sender = connection.signal.sender;
372 // Clear the connection.
373 connection.signal = null;
374 // Cleanup the array of receivers, which is now known to exist.
375 scheduleCleanup(receiversForSender.get(sender));
376 });
377 // Schedule a cleanup of the list of senders.
378 scheduleCleanup(senders);
379 }
380 Private.disconnectReceiver = disconnectReceiver;
381 /**
382 * Remove all connections where an object is the sender or receiver.
383 *
384 * @param object - The object of interest.
385 */
386 function disconnectAll(object) {
387 // Remove all connections where the given object is the sender.
388 disconnectSender(object);
389 // Remove all connections where the given object is the receiver.
390 disconnectReceiver(object);
391 }
392 Private.disconnectAll = disconnectAll;
393 /**
394 * Emit a signal and invoke its connected slots.
395 *
396 * @param signal - The signal of interest.
397 *
398 * @param args - The args to pass to the connected slots.
399 *
400 * #### Notes
401 * Slots are invoked synchronously in connection order.
402 *
403 * Exceptions thrown by connected slots will be caught and logged.
404 */
405 function emit(signal, args) {
406 // If there are no receivers, there is nothing to do.
407 var receivers = receiversForSender.get(signal.sender);
408 if (!receivers || receivers.length === 0) {
409 return;
410 }
411 // Invoke the slots for connections with a matching signal.
412 // Any connections added during emission are not invoked.
413 for (var i = 0, n = receivers.length; i < n; ++i) {
414 var connection = receivers[i];
415 if (connection.signal === signal) {
416 invokeSlot(connection, args);
417 }
418 }
419 }
420 Private.emit = emit;
421 /**
422 * A weak mapping of sender to array of receiver connections.
423 */
424 var receiversForSender = new WeakMap();
425 /**
426 * A weak mapping of receiver to array of sender connections.
427 */
428 var sendersForReceiver = new WeakMap();
429 /**
430 * A set of connection arrays which are pending cleanup.
431 */
432 var dirtySet = new Set();
433 /**
434 * A function to schedule an event loop callback.
435 */
436 var schedule = (function () {
437 var ok = typeof requestAnimationFrame === 'function';
438 // @ts-ignore
439 return ok ? requestAnimationFrame : setImmediate;
440 })();
441 /**
442 * Find a connection which matches the given parameters.
443 */
444 function findConnection(connections, signal, slot, thisArg) {
445 return find(connections, function (connection) {
446 return connection.signal === signal &&
447 connection.slot === slot &&
448 connection.thisArg === thisArg;
449 });
450 }
451 /**
452 * Invoke a slot with the given parameters.
453 *
454 * The connection is assumed to be valid.
455 *
456 * Exceptions in the slot will be caught and logged.
457 */
458 function invokeSlot(connection, args) {
459 var signal = connection.signal, slot = connection.slot, thisArg = connection.thisArg;
460 try {
461 slot.call(thisArg, signal.sender, args);
462 }
463 catch (err) {
464 Private.exceptionHandler(err);
465 }
466 }
467 /**
468 * Schedule a cleanup of a connection array.
469 *
470 * This will add the array to the dirty set and schedule a deferred
471 * cleanup of the array contents. On cleanup, any connection with a
472 * `null` signal will be removed from the array.
473 */
474 function scheduleCleanup(array) {
475 if (dirtySet.size === 0) {
476 schedule(cleanupDirtySet);
477 }
478 dirtySet.add(array);
479 }
480 /**
481 * Cleanup the connection lists in the dirty set.
482 *
483 * This function should only be invoked asynchronously, when the
484 * stack frame is guaranteed to not be on the path of user code.
485 */
486 function cleanupDirtySet() {
487 dirtySet.forEach(cleanupConnections);
488 dirtySet.clear();
489 }
490 /**
491 * Cleanup the dirty connections in a connections array.
492 *
493 * This will remove any connection with a `null` signal.
494 *
495 * This function should only be invoked asynchronously, when the
496 * stack frame is guaranteed to not be on the path of user code.
497 */
498 function cleanupConnections(connections) {
499 ArrayExt.removeAllWhere(connections, isDeadConnection);
500 }
501 /**
502 * Test whether a connection is dead.
503 *
504 * A dead connection has a `null` signal.
505 */
506 function isDeadConnection(connection) {
507 return connection.signal === null;
508 }
509})(Private || (Private = {}));
510
511export { Signal };
512//# sourceMappingURL=index.es6.js.map