UNPKG

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