From d26eba2d2c6d789f5e1b23cfd4549abbca6e7f70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adomas=20Ven=C4=8Dkauskas?= Date: Fri, 9 Apr 2021 10:47:39 +0300 Subject: [PATCH] Adjustments and documentation for Utilities.Internal.makeClassEventDispatcher --- .../zotero/containers/collectionTree.jsx | 2 +- .../content/zotero/containers/libraryTree.js | 4 +- .../zotero/xpcom/utilities_internal.js | 146 ++++++++++-------- 3 files changed, 84 insertions(+), 68 deletions(-) diff --git a/chrome/content/zotero/containers/collectionTree.jsx b/chrome/content/zotero/containers/collectionTree.jsx index d178879081..3798d424ad 100644 --- a/chrome/content/zotero/containers/collectionTree.jsx +++ b/chrome/content/zotero/containers/collectionTree.jsx @@ -92,7 +92,7 @@ var CollectionTree = class CollectionTree extends LibraryTree { this._typingString = ""; this._typingTimeout = null; - this.onLoad = this._createEventBinding('load', true, true); + this.onLoad = this.createEventBinding('load', true, true); } async makeVisible() { diff --git a/chrome/content/zotero/containers/libraryTree.js b/chrome/content/zotero/containers/libraryTree.js index 9cd0a1ec7e..addc244738 100644 --- a/chrome/content/zotero/containers/libraryTree.js +++ b/chrome/content/zotero/containers/libraryTree.js @@ -39,8 +39,8 @@ var LibraryTree = class LibraryTree extends React.Component { this.domEl = props.domEl; this._ownerDocument = props.domEl.ownerDocument; - this.onSelect = this._createEventBinding('select'); - this.onRefresh = this._createEventBinding('refresh'); + this.onSelect = this.createEventBinding('select'); + this.onRefresh = this.createEventBinding('refresh'); } get window() { diff --git a/chrome/content/zotero/xpcom/utilities_internal.js b/chrome/content/zotero/xpcom/utilities_internal.js index ba2880fdf7..ab9fefdaa6 100644 --- a/chrome/content/zotero/xpcom/utilities_internal.js +++ b/chrome/content/zotero/xpcom/utilities_internal.js @@ -31,72 +31,7 @@ */ Zotero.Utilities.Internal = { SNAPSHOT_SAVE_TIMEOUT: 30000, - - makeClassEventDispatcher: function (cls) { - cls.prototype._events = null; - cls.prototype.runListeners = async function (event) { - // Zotero.debug(`Running ${event} listeners on ${cls.toString()}`); - if (!this._events) this._events = {}; - if (!this._events[event]) { - this._events[event] = { - listeners: new Map(), - }; - } - this._events[event].triggered = true; - // Array.from(entries) since entries() returns an iterator and we want a snapshot of the entries - // at the time of runListeners() call to prevent triggering listeners that are added right - // runListeners() invocation - for (let [listener, once] of Array.from(this._events[event].listeners.entries())) { - await Zotero.Promise.resolve(listener.call(this)); - if (once) { - this._events[event].listeners.delete(listener); - } - } - }; - /** - * @param event {String} name of the event - * @param alwaysOnce {Boolean} whether all event listeners on this event will only be triggered once - * @param immediateAfterTrigger {Boolean} whether the event listeners should be triggered immediately - * upon being added if the event had been triggered at least once - * @returns {Object} A listener object with an addListener(listener, once) method - * @private - */ - cls.prototype._createEventBinding = function (event, alwaysOnce, immediateAfterTrigger) { - if (!this._events) this._events = {}; - this._events[event] = { - listeners: new Map(), - immediateAfterTrigger - }; - return { - addListener: (listener, once) => { - this._addListener(event, listener, alwaysOnce || once, immediateAfterTrigger) - } - } - }; - - cls.prototype._addListener = function (event, listener, once, immediateAfterTrigger) { - if (!this._events) this._events = {}; - let ev = this._events[event]; - if (!ev) { - this._events[event] = { - listeners: new Map(), - immediateAfterTrigger - }; - } - if ((immediateAfterTrigger || ev.immediateAfterTrigger) && ev.triggered) { - return listener.call(this); - } - this._events[event].listeners.set(listener, once); - }; - - cls.prototype._waitForEvent = async function (event) { - return new Zotero.Promise((resolve, reject) => { - this._addListener(event, () => resolve(), true); - }); - }; - }, - /** * Run a function on chunks of a given size of an array's elements. * @@ -2159,7 +2094,88 @@ Zotero.Utilities.Internal = { return Zotero.ItemTypes.getImageSrc(attachment.mimeType === "application/pdf" ? "attachment-pdf" : "attachment-snapshot"); }, + + /** + * Pass a class into this to add generic methods for creating event listeners + * (and running those events). + * + * ``` + * var MyClass = Zotero.Utilities.Internal.makeClassEventDispatcher(class { + * constructor: () => { + * this.onFoo = this.createEventBinding('foo'); + * } + * foo: () => this.runListeners('foo'); + * }); + * let object = new MyClass(); + * object.onFoo.addListener(() => console.log('foo ran in object of MyClass')); + * object.foo(); + * ``` + * @param cls + */ + makeClassEventDispatcher: function (cls) { + cls.prototype._events = null; + cls.prototype.runListeners = async function (event) { + // Zotero.debug(`Running ${event} listeners on ${cls.toString()}`); + if (!this._events) this._events = {}; + if (!this._events[event]) { + this._events[event] = { + listeners: new Map(), + }; + } + this._events[event].triggered = true; + // Array.from(entries) since entries() returns an iterator and we want a snapshot of the entries + // at the time of runListeners() call to prevent triggering listeners that are added right + // runListeners() invocation + for (let [listener, once] of Array.from(this._events[event].listeners.entries())) { + await Zotero.Promise.resolve(listener.call(this)); + if (once) { + this._events[event].listeners.delete(listener); + } + } + }; + /** + * @param event {String} name of the event + * @param alwaysOnce {Boolean} whether all event listeners on this event will only be triggered once + * @param immediateAfterTrigger {Boolean} whether the event listeners should be triggered immediately + * upon being added if the event had been triggered at least once + * @returns {Object} A listener object with an addListener(listener, once) method + * @private + */ + cls.prototype.createEventBinding = function (event, alwaysOnce, immediateAfterTrigger) { + if (!this._events) this._events = {}; + this._events[event] = { + listeners: new Map(), + immediateAfterTrigger + }; + return { + addListener: (listener, once) => { + this._addListener(event, listener, alwaysOnce || once, immediateAfterTrigger); + } + } + }; + + cls.prototype._addListener = function (event, listener, once, immediateAfterTrigger) { + if (!this._events) this._events = {}; + let ev = this._events[event]; + if (!ev) { + this._events[event] = { + listeners: new Map(), + immediateAfterTrigger + }; + } + if ((immediateAfterTrigger || ev.immediateAfterTrigger) && ev.triggered) { + return listener.call(this); + } + this._events[event].listeners.set(listener, once); + }; + + cls.prototype._waitForEvent = async function (event) { + return new Zotero.Promise((resolve, reject) => { + this._addListener(event, () => resolve(), true); + }); + }; + } } /**