From aa481939228200d69b2c7f03e4ec8f291a59fd84 Mon Sep 17 00:00:00 2001 From: cliffhall Date: Sun, 3 Mar 2024 17:00:22 -0500 Subject: [PATCH] Initial port from the original ActionScript --- .gitignore | 36 + LICENSE | 12 + README.md | 28 + VERSION | 13 + lib/puremvc-2.0.0.js | 1640 ++++++++++++++++++++++++++++++++++++++ package.json | 11 + src/AsyncCommand.js | 42 + src/AsyncMacroCommand.js | 140 ++++ src/index.js | 9 + 9 files changed, 1931 insertions(+) create mode 100644 .gitignore create mode 100755 LICENSE create mode 100644 README.md create mode 100755 VERSION create mode 100644 lib/puremvc-2.0.0.js create mode 100644 package.json create mode 100644 src/AsyncCommand.js create mode 100644 src/AsyncMacroCommand.js create mode 100644 src/index.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..15b2f45 --- /dev/null +++ b/.gitignore @@ -0,0 +1,36 @@ +# dependencies +/node_modules +/.pnp +.pnp.js + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem +.vscode +.idea + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# local env files +.env +.env*.local + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts diff --git a/LICENSE b/LICENSE new file mode 100755 index 0000000..1c2faf9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,12 @@ +* PureMVC Utility for JS - AsyncCommand - Ported by Cliff Hall (no relation) +* Original: PureMVC Utility for AS3 - AsyncCommand - Copyright © 2008 Duncan Hall +* PureMVC - Copyright © 2007-2024 Futurescale, Inc. +* All rights reserved. + +* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + * Neither the name of Futurescale, Inc., PureMVC.org, nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..9144ba2 --- /dev/null +++ b/README.md @@ -0,0 +1,28 @@ +## [PureMVC](http://puremvc.github.com/) [JavaScript](https://github.com/PureMVC/puremvc-js-multicore-framework/wiki) Utility: Async Command +The Async Command utility offers a solution to the problem of executing a series of commands each of which may need to complete one or more asynchronous operations before the next command is executed. + +* This is a direct port of the original [AS3 AsyncCommand utility](https://github.com/PureMVC/puremvc-as3-util-asynccommand) +* [Historical Discussion](http://forums.puremvc.org/index.php?topic=831.0) + +## Status +Production - [Version 1.0](https://github.com/PureMVC/puremvc-js-util-asynccommand/blob/master/VERSION) + +## Demos Using This Utility +* [Sequential](https://github.com/PureMVC/puremvc-as3-demo-flash-sequential/wiki) + +## Platforms / Technologies +* [ActionScript 3](http://en.wikipedia.org/wiki/ActionScript) + + +## License +* PureMVC Utility for AS3 - AsyncCommand - Copyright © 2008 Duncan Hall +* PureMVC - Copyright © 2007-2012 Futurescale, Inc. +* All rights reserved. + +* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + * Neither the name of Futurescale, Inc., PureMVC.org, nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/VERSION b/VERSION new file mode 100755 index 0000000..9bb43eb --- /dev/null +++ b/VERSION @@ -0,0 +1,13 @@ +PureMVC Async Command Utility for Javascript (Ported) +-------------------------------------------------------------------------- +Release Date: 3/3/2024 + Platform: JavaScript (Native) + Version: 1 + Revision: 0 + Minor: 0 + Authors: Copyright © 2008 Duncan Hall + : Ported in 2024 by Cliff Hall +------------------------------------------------------------------------- +1.0.0 Initial port + + diff --git a/lib/puremvc-2.0.0.js b/lib/puremvc-2.0.0.js new file mode 100644 index 0000000..7947f12 --- /dev/null +++ b/lib/puremvc-2.0.0.js @@ -0,0 +1,1640 @@ +/* + * Observer.js + * PureMVC JavaScript Multicore + * + * Copyright(c) 2023 Saad Shams + * Your reuse is governed by the BSD License +*/ + +/** + * A base `Observer` implementation. + * + *

An `Observer` is an object that encapsulates information + * about an interested object with a method that should + * be called when a particular `Notification` is broadcast.

+ * + *

In PureMVC, the `Observer` class assumes these responsibilities:

+ * + *
    + *
  • Encapsulate the notification (callback) method of the interested object.
  • + *
  • Encapsulate the notification context (this) of the interested object.
  • + *
  • Provide methods for setting the notification method and context.
  • + *
  • Provide a method for notifying the interested object.
  • + *
+ * + * @class Observer + */ +class Observer { + + /** + * Constructor. + * + *

The notification method on the interested object should take + * one parameter of type `Notification`

+ * + * @param {function(Notification):void} notifyMethod + * @param {Object} notifyContext + */ + constructor(notifyMethod, notifyContext) { + this._notifyMethod = notifyMethod; + this._notifyContext = notifyContext; + } + + /** + * Notify the interested object. + * + * @param {Notification} notification + */ + notifyObserver(notification) { + this._notifyMethod.call(this._notifyContext, notification); + } + + /** + * Compare an object to the notification context. + * + * @param {Object} notifyContext + * @returns {boolean} + */ + compareNotifyContext(notifyContext) { + return this._notifyContext === notifyContext; + } + + /** + * Get the notification method. + * + * @returns {function(Notification):void} + */ + get notifyMethod() { + return this._notifyMethod + } + + /** + * Set the notification method. + * + *

The notification method should take one parameter of type `Notification`.

+ * + * @param {function(Notification): void} notifyMethod - The function to be called when a notification is received. + */ + set notifyMethod(notifyMethod) { + this._notifyMethod = notifyMethod; + } + + /** + * Get the notifyContext + * + * @returns {Object} + */ + get notifyContext() { + return this._notifyContext; + } + + /** + * Set the notification context. + * + * @param {Object} notifyContext + */ + set notifyContext(notifyContext) { + this._notifyContext = notifyContext; + } + +} + +/* + * View.js + * PureMVC JavaScript Multicore + * + * Copyright(c) 2023 Saad Shams + * Your reuse is governed by the BSD License +*/ + + +/** + * A Multiton `View` implementation. + * + *

In PureMVC, the `View` class assumes these responsibilities:

+ * + *
    + *
  • Maintain a cache of `Mediator` instances.
  • + *
  • Provide methods for registering, retrieving, and removing `Mediators`.
  • + *
  • Notifying `Mediators` when they are registered or removed.
  • + *
  • Managing the observer lists for each `Notification` in the application.
  • + *
  • Providing a method for attaching `Observers` to a `Notification`'s observer list.
  • + *
  • Providing a method for broadcasting a `Notification`.
  • + *
  • Notifying the `Observers` of a given `Notification` when it broadcast.
  • + *
+ * + * @see Mediator Mediator + * @see Observer Observer + * @see Notification Notification + * + * @class View + */ +class View { + + /** + * Constructor. + * + *

This `View` implementation is a Multiton, + * so you should not call the constructor + * directly, but instead call the static Multiton + * Factory method `View.getInstance( multitonKey )` + * + * @constructor + * @param {string} key + * + * @throws {Error} Error if instance for this Multiton key has already been constructed + */ + constructor(key) { + if (View.instanceMap.get(key) != null) throw new Error(View.MULTITON_MSG); + /** @protected + * @type {string} */ + this.multitonKey = key; + View.instanceMap.set(this.multitonKey, this); + /** @protected + * @type {Map} */ + this.mediatorMap = new Map(); + /** @protected + * @type {Map.>} */ + this.observerMap = new Map(); + this.initializeView(); + } + + /** + *

Initialize the Multiton View instance.

+ * + *

Called automatically by the constructor, this + * is your opportunity to initialize the Multiton + * instance in your subclass without overriding the + * constructor.

+ */ + initializeView() { + + } + + /** + * View Multiton factory method. + * + * @static + * @param {string} key + * @param {function(string):View} factory + * @returns {View} the Multiton instance of `View` + */ + static getInstance(key, factory) { + if (View.instanceMap == null) + /** @static + * @type {Map} */ + View.instanceMap = new Map(); + if (View.instanceMap.get(key) == null) View.instanceMap.set(key, factory(key)); + return View.instanceMap.get(key); + } + + /** + *

Register an `Observer` to be notified + * of `Notifications` with a given name.

+ * + * @param {string} notificationName the name of the `Notifications` to notify this `Observer` of + * @param {Observer} observer the `Observer` to register + */ + registerObserver(notificationName, observer) { + if (this.observerMap.get(notificationName) != null) { + let observers = this.observerMap.get(notificationName); + observers.push(observer); + } else { + this.observerMap.set(notificationName, new Array(observer)); + } + } + + /** + *

Notify the `Observers` for a particular `Notification`.

+ * + *

All previously attached `Observers` for this `Notification`'s + * list are notified and are passed a reference to the `Notification` in + * the order in which they were registered.

+ * + * @param {Notification} notification the `Notification` to notify `Observers` of. + */ + notifyObservers(notification) { + if (this.observerMap.has(notification.name)) { + // Copy observers from reference array to working array, + // since the reference array may change during the notification loop + let observers = this.observerMap.get(notification.name).slice(); + + // Notify Observers from the working array + for(let i = 0; i < observers.length; i++) { + observers[i].notifyObserver(notification); + } + } + } + + /** + *

Remove the observer for a given notifyContext from an observer list for a given Notification name.

+ * + * @param {string} notificationName which observer list to remove from + * @param {Object} notifyContext remove the observer with this object as its notifyContext + */ + removeObserver(notificationName, notifyContext) { + // the observer list for the notification under inspection + let observers = this.observerMap.get(notificationName); + + // find the observer for the notifyContext + for (let i = 0; i < observers.length; i++) { + if (observers[i].compareNotifyContext(notifyContext) === true) { + // there can only be one Observer for a given notifyContext + // in any given Observer list, so remove it and break + observers.splice(i, 1); + break; + } + } + + // Also, when a Notification's Observer list length falls to + // zero, delete the notification key from the observer map + if (observers.length === 0) { + this.observerMap.delete(notificationName); + } + } + + /** + * Register a `Mediator` instance with the `View`. + * + *

Registers the `Mediator` so that it can be retrieved by name, + * and further interrogates the `Mediator` for its + * `Notification` interests.

+ * + *

If the `Mediator` returns any `Notification` + * names to be notified about, an `Observer` is created encapsulating + * the `Mediator` instance's `handleNotification` method + * and registering it as an `Observer` for all `Notifications` the + * `Mediator` is interested in.

+ * + * @param {Mediator} mediator a reference to the `Mediator` instance + */ + registerMediator(mediator) { + // do not allow re-registration (you must to removeMediator fist) + if (this.mediatorMap.has(mediator.mediatorName) !== false) return; + + mediator.initializeNotifier(this.multitonKey); + + // Register the Mediator for retrieval by name + this.mediatorMap.set(mediator.mediatorName, mediator); + + // Get Notification interests, if any. + let interests = mediator.listNotificationInterests(); + + // Register Mediator as an observer for each notification of interests + if (interests.length > 0) { + // Create Observer referencing this mediator's handleNotification method + let observer = new Observer(mediator.handleNotification.bind(mediator), mediator); // check bind + + // Register Mediator as Observer for its list of Notification interests + for (let i = 0; i < interests.length; i++) { + this.registerObserver(interests[i], observer); + } + } + + // alert the mediator that it has been registered + mediator.onRegister(); + } + + /** + * Retrieve a `Mediator` from the `View`. + * + * @param {string} mediatorName the name of the `Mediator` instance to retrieve. + * @returns {Mediator} the `Mediator` instance previously registered with the given `mediatorName`. + */ + retrieveMediator(mediatorName) { + return this.mediatorMap.get(mediatorName) || null; + } + + /** + * Remove a `Mediator` from the `View`. + * + * @param {string} mediatorName name of the `Mediator` instance to be removed. + * @returns {Mediator} the `Mediator` that was removed from the `View` + */ + removeMediator(mediatorName) { + // Retrieve the named mediator + let mediator = this.mediatorMap.get(mediatorName); + + if (mediator) { + // for every notification this mediator is interested in... + let interests = mediator.listNotificationInterests(); + for (let i = 0; i < interests.length; i++) { + // remove the observer linking the mediator + // to the notification interest + this.removeObserver(interests[i], mediator); + } + + // remove the mediator from the map + this.mediatorMap.delete(mediatorName); + + // alert the mediator that it has been removed + mediator.onRemove(); + } + + return mediator; + } + + /** + * Check if a Mediator is registered or not + * + * @param {string} mediatorName + * @returns {boolean} whether a Mediator is registered with the given `mediatorName`. + */ + hasMediator(mediatorName) { + return this.mediatorMap.has(mediatorName); + } + + /** + * Remove a View instance + * + * @static + * @param key multitonKey of View instance to remove + */ + static removeView(key) { + this.instanceMap.delete(key); + } + + /** + * Message Constants + * + * @static + * @type {string} + */ + static get MULTITON_MSG() { return "View instance for this Multiton key already constructed!" }; + +} + +/* + * Controller.js + * PureMVC JavaScript Multicore + * + * Copyright(c) 2023 Saad Shams + * Your reuse is governed by the BSD License +*/ + + +/** + * A Multiton `Controller` implementation. + * + *

In PureMVC, the `Controller` class follows the + * 'Command and Controller' strategy, and assumes these + * responsibilities:

+ * + *
    + *
  • Remembering which `Command`s + * are intended to handle which `Notifications`.
  • + *
  • Registering itself as an `Observer` with + * the `View` for each `Notification` + * that it has a `Command` mapping for.
  • + *
  • Creating a new instance of the proper `Command` + * to handle a given `Notification` when notified by the `View`.
  • + *
  • Calling the `Command`'s `execute` + * method, passing in the `Notification`.
  • + *
+ * + *

Your application must register `Commands` with the + * Controller.

+ * + *

The simplest way is to subclass `Facade`, + * and use its `initializeController` method to add your + * registrations.

+ * + * @see View View + * @see Observer Observer + * @see Notification Notification + * @see SimpleCommand SimpleCommand + * @see MacroCommand MacroCommand + * + * @class Controller + */ +class Controller { + + /** + * Constructor. + * + *

This `Controller` implementation is a Multiton, + * so you should not call the constructor + * directly, but instead call the static Factory method, + * passing the unique key for this instance + * `Controller.getInstance( multitonKey )`

+ * + * @throws {Error} Error if instance for this Multiton key has already been constructed + * + * @constructor + * @param {string} key + */ + constructor(key) { + if (Controller.instanceMap[key] != null) throw new Error(Controller.MULTITON_MSG); + /** @protected + * @type {string} */ + this.multitonKey = key; + Controller.instanceMap.set(this.multitonKey, this); + /** @protected + * @type {Map} */ + this.commandMap = new Map(); + this.initializeController(); + } + + /** + * Initialize the Multiton `Controller` instance. + * + *

Called automatically by the constructor.

+ * + *

Note that if you are using a subclass of `View` + * in your application, you should also subclass `Controller` + * and override the `initializeController` method in the + * following way:

+ * + *
`
+     *		// ensure that the Controller is talking to my View implementation
+     *		initializeController( )
+     *		{
+     *			this.view = MyView.getInstance(this.multitonKey, (key) => new MyView(key));
+     *		}
+     * `
+ * + */ + initializeController() { + /** @protected + * @type {View} **/ + this.view = View.getInstance(this.multitonKey, (key) => new View(key)); + } + + /** + * `Controller` Multiton Factory method. + * + * @static + * @param {string} key + * @param {function(string):Controller} factory + * @returns {Controller} the Multiton instance of `Controller` + */ + static getInstance(key, factory) { + if (Controller.instanceMap == null) + /** @static + @type {Map} */ + Controller.instanceMap = new Map(); + if (Controller.instanceMap.get(key) == null) Controller.instanceMap.set(key, factory(key)); + return Controller.instanceMap.get(key); + } + + /** + *

If a `Command` has previously been registered + * to handle the given `Notification`, then it is executed.

+ * + * @param {Notification} notification a `Notification` + */ + executeCommand(notification) { + let factory = this.commandMap.get(notification.name); + if (factory == null) return; + + let commandInstance = factory(); + commandInstance.initializeNotifier(this.multitonKey); + commandInstance.execute(notification); + } + + /** + *

Register a particular `Command` class as the handler + * for a particular `Notification`.

+ * + *

If an `Command` has already been registered to + * handle `Notification`s with this name, it is no longer + * used, the new `Command` is used instead.

+ * + *

The Observer for the new Command is only created if this the + * first time a Command has been registered for this Notification name.

+ * + * @param notificationName the name of the `Notification` + * @param {function():SimpleCommand} factory + */ + registerCommand(notificationName, factory) { + if (this.commandMap.get(notificationName) == null) { + this.view.registerObserver(notificationName, new Observer(this.executeCommand, this)); + } + this.commandMap.set(notificationName, factory); + } + + /** + * Check if a Command is registered for a given Notification + * + * @param {string} notificationName + * @return {boolean} whether a Command is currently registered for the given `notificationName`. + */ + hasCommand(notificationName) { + return this.commandMap.has(notificationName); + } + + /** + * Remove a previously registered `Command` to `Notification` mapping. + * + * @param {string} notificationName the name of the `Notification` to remove the `Command` mapping for + */ + removeCommand(notificationName) { + // if the Command is registered... + if(this.hasCommand(notificationName)) { + // remove the observer + this.view.removeObserver(notificationName, this); + + // remove the command + this.commandMap.delete(notificationName); + } + } + + /** + * Remove a Controller instance + * + * @static + * @param {string} key of Controller instance to remove + */ + static removeController(key) { + Controller.instanceMap.delete(key); + } + + /** + * Message Constants + * + * @static + * @type {string} + */ + static get MULTITON_MSG() { return "Controller instance for this Multiton key already constructed!" }; +} + +/* + * Model.js + * PureMVC JavaScript Multicore + * + * Copyright(c) 2023 Saad Shams + * Your reuse is governed by the BSD License +*/ + +/** + * A Multiton `Model` implementation. + * + *

In PureMVC, the `Model` class provides + * access to model objects (Proxies) by named lookup. + * + *

The `Model` assumes these responsibilities:

+ * + *
    + *
  • Maintain a cache of `Proxy` instances.
  • + *
  • Provide methods for registering, retrieving, and removing + * `Proxy` instances.
  • + *
+ * + *

Your application must register `Proxy` instances + * with the `Model`. Typically, you use an + * `Command` to create and register `Proxy` + * instances once the `Facade` has initialized the Core + * actors.

+ * + * @see Proxy Proxy + * + * @class Model + */ + +class Model { + + /** + * Constructor. + * + *

This `Model` implementation is a Multiton, + * so you should not call the constructor + * directly, but instead call the static Multiton + * Factory method `Model.getInstance( multitonKey )` + * + * @constructor + * @param {string} key + * + * @throws {Error} Error if instance for this Multiton key instance has already been constructed + */ + constructor(key) { + if (Model.instanceMap.get(key) != null) throw new Error(Model.MULTITON_MSG); + /** @protected + * @type {string} */ + this.multitonKey = key; + Model.instanceMap.set(this.multitonKey, this); + /** @protected + * @type {Map} */ + this.proxyMap = new Map(); + this.initializeModel(); + } + + /** + * Initialize the `Model` instance. + * + *

Called automatically by the constructor, this + * is your opportunity to initialize the Multiton + * instance in your subclass without overriding the + * constructor.

+ * + */ + initializeModel() { + + } + + /** + * `Model` Multiton Factory method. + * + * @static + * @param {string} key + * @param {function(string):Model} factory + * @returns {Model} the instance for this Multiton key + */ + static getInstance(key, factory) { + if (Model.instanceMap == null) + /** @static + @type {Map} */ + Model.instanceMap = new Map(); + if (Model.instanceMap.get(key) == null) Model.instanceMap.set(key, factory(key)); + return Model.instanceMap.get(key); + } + + /** + * Register a `Proxy` with the `Model`. + * + * @param {Proxy} proxy a `Proxy` to be held by the `Model`. + */ + registerProxy(proxy) { + proxy.initializeNotifier(this.multitonKey); + this.proxyMap.set(proxy.proxyName, proxy); + proxy.onRegister(); + } + + /** + * Retrieve a `Proxy` from the `Model`. + * + * @param {string} proxyName + * @returns {Proxy} the `Proxy` instance previously registered with the given `proxyName`. + */ + retrieveProxy(proxyName) { + return this.proxyMap.get(proxyName) || null; + } + + /** + * Check if a Proxy is registered + * + * @param {string} proxyName + * @returns {boolean} whether a Proxy is currently registered with the given `proxyName`. + */ + hasProxy(proxyName) { + return this.proxyMap.has(proxyName); + } + + /** + * Remove a `Proxy` from the `Model`. + * + * @param {string} proxyName name of the `Proxy` instance to be removed. + * @returns {Proxy} the `Proxy` that was removed from the `Model` + */ + removeProxy(proxyName) { + let proxy = this.proxyMap.get(proxyName); + if (proxy != null) { + this.proxyMap.delete(proxyName); + proxy.onRemove(); + } + return proxy; + } + + /** + * Remove a Model instance + * + * @static + * @param key + */ + static removeModel(key) { + Model.instanceMap.delete(key); + } + + /** + * @static + * @type {string} + */ + static get MULTITON_MSG() { return "Model instance for this Multiton key already constructed!" }; +} + +/* + * Notification.js + * PureMVC JavaScript Multicore + * + * Copyright(c) 2023 Saad Shams + * Your reuse is governed by the BSD License +*/ + +/** + * + * A base `Notification` implementation. + * + *

PureMVC does not rely upon underlying event models such + * as the one provided with Flash, and ActionScript 3 does + * not have an inherent event model.

+ * + *

The Observer Pattern as implemented within PureMVC exists + * to support event-driven communication between the + * application and the actors of the MVC triad.

+ * + *

Notifications are not meant to be a replacement for Events + * in Flex/Flash/Apollo. Generally, `Mediator` implementors + * place event listeners on their view components, which they + * then handle in the usual way. This may lead to the broadcast of `Notification`s to + * trigger `Command`s or to communicate with other `Mediators`. `Proxy` and `Command` + * instances communicate with each other and `Mediator`s + * by broadcasting `Notification`s.

+ * + *

A key difference between Flash `Event`s and PureMVC + * `Notification`s is that `Event`s follow the + * 'Chain of Responsibility' pattern, 'bubbling' up the display hierarchy + * until some parent component handles the `Event`, while + * PureMVC `Notification`s follow a 'Publish/Subscribe' + * pattern. PureMVC classes need not be related to each other in a + * parent/child relationship in order to communicate with one another + * using `Notification`s.

+ * + * @class Notification + */ +class Notification { + + /** + * Constructor. + * + * @constructor + * @param {string} name - The name of the notification. + * @param {Object|null} [body=null] - The body of the notification, defaults to `null`. + * @param {string} [type=""] - The type of the notification, defaults to an empty string. + */ + constructor(name, body = null, type = "") { + this._name = name; + this._body = body; + this._type = type; + } + + /** + * Get the name of the `Notification` instance. + * + * @returns {string} + */ + get name() { + return this._name; + } + + /** + * Get the body of the `Notification` instance. + * + * @returns {Object} + */ + get body() { + return this._body; + } + + /** + * Set the body of the `Notification` instance. + * + * @param {Object|null} body + */ + set body(body) { + this._body = body; + } + + /** + * Get the type of the `Notification` instance. + * + * @returns {string} + */ + get type() { + return this._type; + } + + /** + * Set the type of the `Notification` instance. + * + * @param {string} type + */ + set type(type) { + this._type = type; + } + + /** + * Get the string representation of the `Notification` instance. + * + * @returns {string} + */ + toString() { + let str= "Notification Name: " + this.name; + str+= "\nBody:" + ((this.body == null ) ? "null" : this.body.toString()); + str+= "\nType:" + ((this.type == null ) ? "null" : this.type); + return str; + } + +} + +/* + * Facade.js + * PureMVC JavaScript Multicore + * + * Copyright(c) 2023 Saad Shams + * Your reuse is governed by the BSD License +*/ + + +/** + * A base Multiton `Facade` implementation. + * + * @see Model Model + * @see View View + * @see Controller Controller + * + * @class Facade + */ +class Facade { + + /** + * Constructor. + * + *

This `Facade` implementation is a Multiton, + * so you should not call the constructor + * directly, but instead call the static Factory method, + * passing the unique key for this instance + * `Facade.getInstance( multitonKey )`

+ * + * @constructor + * @param {string} key + * @throws {Error} Error if instance for this Multiton key has already been constructed + */ + constructor(key) { + if (Facade.instanceMap[key] != null) throw new Error(Facade.MULTITON_MSG); + this.initializeNotifier(key); + Facade.instanceMap.set(this.multitonKey, this); + this.initializeFacade(); + } + + /** + * Initialize the Multiton `Facade` instance. + * + *

Called automatically by the constructor. Override in your + * subclass to do any subclass specific initializations. Be + * sure to call `super.initializeFacade()`, though.

+ */ + initializeFacade() { + this.initializeModel(); + this.initializeController(); + this.initializeView(); + } + + /** + * Facade Multiton Factory method + * + * @static + * @param {string} key + * @param {function(string):Facade} factory + * @returns {Facade} the Multiton instance of the Facade + */ + static getInstance(key, factory) { + if (Facade.instanceMap == null) + /** @static + * @type {Map} */ + Facade.instanceMap = new Map(); + if (Facade.instanceMap.get(key) == null) Facade.instanceMap.set(key, factory(key)); + return Facade.instanceMap.get(key); + } + + /** + * Initialize the `Model`. + * + *

Called by the `initializeFacade` method. + * Override this method in your subclass of `Facade` + * if one or both of the following are true:

+ * + *
    + *
  • You wish to initialize a different `Model`.
  • + *
  • You have `Proxy`s to register with the Model that do not + * retrieve a reference to the Facade at construction time.`
  • + *
+ * + * If you don't want to initialize a different `Model`, + * call `super.initializeModel()` at the beginning of your + * method, then register `Proxy`s. + * + *

Note: This method is rarely overridden; in practice you are more + * likely to use a `Command` to create and register `Proxy`s + * with the `Model`, since `Proxy`s with mutable data will likely + * need to send `Notification`s and thus will likely want to fetch a reference to + * the `Facade` during their construction.

+ */ + initializeModel() { + if (this.model != null) return; + this.model = Model.getInstance(this.multitonKey, key => new Model(key)); + } + + /** + * Initialize the `Controller`. + * + *

Called by the `initializeFacade` method. + * Override this method in your subclass of `Facade` + * if one or both of the following are true:

+ * + *
    + *
  • You wish to initialize a different `Controller`.
  • + *
  • You have `Commands` to register with the `Controller` at startup.`.
  • + *
+ * + *

If you don't want to initialize a different `Controller`, + * call `super.initializeController()` at the beginning of your + * method, then register `Command`s.

+ */ + initializeController() { + if (this.controller != null) return; + this.controller = Controller.getInstance(this.multitonKey, key => new Controller(key)); + } + + /** + * Initialize the `View`. + * + *

Called by the `initializeFacade` method. + * Override this method in your subclass of `Facade` + * if one or both of the following are true:

+ * + *
    + *
  • You wish to initialize a different `View`.
  • + *
  • You have `Observers` to register with the `View`
  • + *
+ * + *

If you don't want to initialize a different `View`, + * call `super.initializeView()` at the beginning of your + * method, then register `Mediator` instances.

+ * + *

Note: This method is rarely overridden; in practice you are more + * likely to use a `Command` to create and register `Mediator`s + * with the `View`, since `Mediator` instances will need to send + * `Notification`s and thus will likely want to fetch a reference + * to the `Facade` during their construction.

+ */ + initializeView() { + if (this.view != null) return; + this.view = View.getInstance(this.multitonKey, key => new View(key)); + } + + /** + * Register a `Command` with the `Controller` by Notification name. + * + * @param {string} notificationName the name of the `Notification` to associate the `Command` with + * @param {function():SimpleCommand} factory a reference to the factory of the `Command` + */ + registerCommand(notificationName, factory) { + this.controller.registerCommand(notificationName, factory); + } + + /** + * Check if a Command is registered for a given Notification + * + * @param {string} notificationName + * @returns {boolean} whether a Command is currently registered for the given `notificationName`. + */ + hasCommand(notificationName) { + return this.controller.hasCommand(notificationName); + } + + /** + * Remove a previously registered `Command` to `Notification` mapping from the Controller. + * + * @param {string} notificationName the name of the `Notification` to remove the `Command` mapping for + */ + removeCommand(notificationName) { + this.controller.removeCommand(notificationName); + } + + /** + * Register a `Proxy` with the `Model` by name. + * + * @param {Proxy} proxy the `Proxy` instance to be registered with the `Model`. + */ + registerProxy(proxy) { + this.model.registerProxy(proxy); + } + + /** + * Remove a `Proxy` from the `Model` by name. + * + * @param {string} proxyName the `Proxy` to remove from the `Model`. + * @returns {Proxy} the `Proxy` that was removed from the `Model` + */ + removeProxy(proxyName) { + return this.model.removeProxy(proxyName); + } + + /** + * Check if a Proxy is registered + * + * @param {string} proxyName + * @returns {boolean} whether a Proxy is currently registered with the given `proxyName`. + */ + hasProxy(proxyName) { + return this.model.hasProxy(proxyName); + } + + /** + * Retrieve a `Proxy` from the `Model` by name. + * + * @param {string} proxyName the name of the proxy to be retrieved. + * @returns {Proxy} the `Proxy` instance previously registered with the given `proxyName`. + */ + retrieveProxy(proxyName) { + return this.model.retrieveProxy(proxyName); + } + + /** + * Register a `Mediator` with the `View`. + * + * @param {Mediator} mediator a reference to the `Mediator` + */ + registerMediator(mediator) { + this.view.registerMediator(mediator); + } + + /** + * Remove a `Mediator` from the `View`. + * + * @param {string} mediatorName name of the `Mediator` to be removed. + * @returns {Mediator} the `Mediator` that was removed from the `View` + */ + removeMediator(mediatorName) { + return this.view.removeMediator(mediatorName); + } + + /** + * Check if a Mediator is registered or not + * + * @param {string} mediatorName + * @returns {Mediator} whether a Mediator is registered with the given `mediatorName`. + */ + hasMediator(mediatorName) { + return this.view.hasMediator(mediatorName); + } + + /** + * Retrieve a `Mediator` from the `View`. + * + * @param {string} mediatorName + * @returns {Mediator} the `Mediator` previously registered with the given `mediatorName`. + */ + retrieveMediator(mediatorName) { + return this.view.retrieveMediator(mediatorName); + } + + /** + * Create and send an `Notification`. + * + *

Keeps us from having to construct new notification + * instances in our implementation code.

+ * + * @param {string} notificationName the name of the notification to send + * @param {Object} [body] body the body of the notification (optional) + * @param {string} [type] type the type of the notification (optional) + */ + sendNotification(notificationName, body = null, type = "") { + this.notifyObservers(new Notification(notificationName, body, type)); + } + + /** + * Notify `Observer`s. + * + *

This method is left public mostly for backward + * compatibility, and to allow you to send custom + * notification classes using the facade.

+ * + *

Usually you should just call sendNotification + * and pass the parameters, never having to + * construct the notification yourself.

+ * + * @param {Notification} notification the `Notification` to have the `View` notify `Observers` of. + */ + notifyObservers(notification) { + this.view.notifyObservers(notification); + } + + /** + * Set the Multiton key for this facade instance. + * + *

Not called directly, but instead from the + * constructor when getInstance is invoked. + * It is necessary to be public in order to + * implement Notifier.

+ */ + initializeNotifier(key) { + this.multitonKey = key; + } + + /** + * Check if a Core is registered or not + * + * @static + * @param {string} key the multiton key for the Core in question + * @returns {boolean} whether a Core is registered with the given `key`. + */ + static hasCore(key) { + return this.instanceMap.has(key); + } + + /** + * Remove a Core. + * + *

Remove the Model, View, Controller and Facade + * instances for the given key.

+ * + * @static + * @param {string} key multitonKey of the Core to remove + */ + static removeCore(key) { + if (Facade.instanceMap.get(key) == null) return; + Model.removeModel(key); + View.removeView(key); + Controller.removeController(key); + this.instanceMap.delete(key); + } + + /** + * Message Constants + * + * @static + * @returns {string} + */ + static get MULTITON_MSG() {return "Facade instance for this Multiton key already constructed!"}; +} + +/* + * Notifier.js + * PureMVC JavaScript Multicore + * + * Copyright(c) 2023 Saad Shams + * Your reuse is governed by the BSD License +*/ + + +/** + * A Base `Notifier` implementation. + * + *

`MacroCommand, Command, Mediator` and `Proxy` + * all have a need to send `Notifications`.

+ * + *

The `Notifier` interface provides a common method called + * `sendNotification` that relieves implementation code of + * the necessity to actually construct `Notifications`.

+ * + *

The `Notifier` class, which all of the above mentioned classes + * extend, provides an initialized reference to the `Facade` + * Multiton, which is required for the convenience method + * for sending `Notifications`, but also eases implementation as these + * classes have frequent `Facade` interactions and usually require + * access to the facade anyway.

+ * + *

NOTE: In the MultiCore version of the framework, there is one caveat to + * notifiers, they cannot send notifications or reach the facade until they + * have a valid multitonKey.

+ * + * The multitonKey is set: + * * on a Command when it is executed by the Controller + * * on a Mediator is registered with the View + * * on a Proxy is registered with the Model. + * + * @see Proxy Proxy + * @see Facade Facade + * @see Mediator Mediator + * @see MacroCommand MacroCommand + * @see SimpleCommand SimpleCommand + * + * @class Notifier + */ +class Notifier { + + constructor() {} + + /** + * Create and send an `Notification`. + * + *

Keeps us from having to construct new Notification + * instances in our implementation code.

+ * + * @param {string} notificationName + * @param {Object} [body] body + * @param {string} [type] type + */ + sendNotification (notificationName, body = null, type = "") { + if (this.facade != null) { + this.facade.sendNotification(notificationName, body, type); + } + } + + /** + * Initialize this Notifier instance. + * + *

This is how a Notifier gets its multitonKey. + * Calls to sendNotification or to access the + * facade will fail until after this method + * has been called.

+ * + *

Mediators, Commands or Proxies may override + * this method in order to send notifications + * or access the Multiton Facade instance as + * soon as possible. They CANNOT access the facade + * in their constructors, since this method will not + * yet have been called.

+ * + * @param {string} key the multitonKey for this Notifier to use + */ + initializeNotifier(key) { + this.multitonKey = key; + } + + /** + * Return the Multiton Facade instance + * + * @typedef {Facade} Facade + * + * @throws {Error} + */ + get facade() { + if (this.multitonKey == null) throw new Error(Notifier.MULTITON_MSG); + return Facade.getInstance(this.multitonKey, key => new Facade(key)); + } + + /** + * Message Constants + * + * @static + * @returns {string} + */ + static get MULTITON_MSG() { return "multitonKey for this Notifier not yet initialized!" } +} + +/* + * SimpleCommand.js + * PureMVC JavaScript Multicore + * + * Copyright(c) 2023 Saad Shams + * Your reuse is governed by the BSD License +*/ + + +/** + * A base `Command` implementation. + * + *

Your subclass should override the `execute` + * method where your business logic will handle the `Notification`.

+ * + * @see Controller Controller + * @see Notification Notification + * @see MacroCommand MacroCommand + * + * @class SimpleCommand + */ +class SimpleCommand extends Notifier { + + constructor() { + super(); + } + + /** + * Fulfill the use-case initiated by the given `Notification`. + * + *

In the Command Pattern, an application use-case typically + * begins with some user action, which results in a `Notification` being broadcast, which + * is handled by business logic in the `execute` method of an + * `Command`.

+ * + * @param {Notification} notification + */ + execute(notification) { + + } + +} + +/* + * MacroCommand.js + * PureMVC JavaScript Multicore + * + * Copyright(c) 2023 Saad Shams + * Your reuse is governed by the BSD License +*/ + + +/** + * A base `Command` implementation that executes other `Command`s. + * + *

A `MacroCommand` maintains a list of + * `Command` Class references called SubCommands.

+ * + *

When `execute` is called, the `MacroCommand` + * instantiates and calls `execute` on each of its SubCommands turn. + * Each SubCommand will be passed a reference to the original + * `Notification` that was passed to the `MacroCommand`'s + * `execute` method.

+ * + *

Unlike `SimpleCommand`, your subclass + * should not override `execute`, but instead, should + * override the `initializeMacroCommand` method, + * calling `addSubCommand` once for each SubCommand + * to be executed.

+ * + * @see Controller Controller + * @see Notification Notification + * @see SimpleCommand SimpleCommand + * + * @class MacroCommand + */ +class MacroCommand extends SimpleCommand { + + /** + * Constructor. + * + *

You should not need to define a constructor, + * instead, override the `initializeMacroCommand` + * method.

+ * + *

If your subclass does define a constructor, be + * sure to call `super()`.

+ * + * @constructor + */ + constructor() { + super(); + /** @protected + * @type {Array.} */ + this.subCommands = []; + this.initializeMacroCommand(); + } + + /** + * Initialize the `MacroCommand`. + * + *

In your subclass, override this method to + * initialize the `MacroCommand`'s SubCommand + * list with `Command` class references like + * this:

+ * + *
`
+     *		// Initialize MyMacroCommand
+     *		initializeMacroCommand() {
+     *			this.addSubCommand(() => new app.FirstCommand());
+     *			this.addSubCommand(() => new app.SecondCommand());
+     *			this.addSubCommand(() => new app.ThirdCommand());
+     *		}
+     * `
+ * + *

Note that SubCommands may be any `Command` implementor, + * `MacroCommand`s or `SimpleCommands` are both acceptable. + */ + initializeMacroCommand() { + + } + + /** + * Add a SubCommand. + * + *

The SubCommands will be called in First In/First Out (FIFO) + * order.

+ * + * @param {function():SimpleCommand} factory + */ + addSubCommand(factory) { + this.subCommands.push(factory); + } + + /** + * Execute this `MacroCommand`'s SubCommands. + * + *

The SubCommands will be called in First In/First Out (FIFO) + * order.

+ * + * @param {Notification} notification + */ + execute(notification) { + while(this.subCommands.length > 0) { + let factory = this.subCommands.shift(); + let commandInstance = factory(); + commandInstance.initializeNotifier(this.multitonKey); + commandInstance.execute(notification); + } + } + +} + +/* + * Mediator.js + * PureMVC JavaScript Multicore + * + * Copyright(c) 2023 Saad Shams + * Your reuse is governed by the BSD License +*/ + + +/** + * A base `Mediator` implementation. + * + * @see View View + * + * @class Mediator + */ +class Mediator extends Notifier { + + /** + * Constructor. + * + * @constructor + * @param {string} mediatorName + * @param {Object} [viewComponent] viewComponent + */ + constructor(mediatorName, viewComponent = null) { + super(); + this._mediatorName = mediatorName || Mediator.NAME; + this._viewComponent = viewComponent; + } + + /** + * Called by the View when the Mediator is registered + */ + onRegister() { + + } + + /** + * Called by the View when the Mediator is removed + */ + onRemove() { + + } + + /** + * List the `Notification` names this + * `Mediator` is interested in being notified of. + * + * @returns {string[]} + */ + listNotificationInterests() { + return []; + } + + /** + * Handle `Notification`s. + * + *

+ * Typically this will be handled in a switch statement, + * with one 'case' entry per `Notification` + * the `Mediator` is interested in. + * + * @param {Notification} notification + */ + handleNotification(notification) { + + } + + /** + * the mediator name + * + * @returns {string} + */ + get mediatorName() { + return this._mediatorName; + } + + /** + * Get the `Mediator`'s view component. + * + *

+ * Additionally, an implicit getter will usually + * be defined in the subclass that casts the view + * object to a type, like this:

+ * + * @returns {Object} + */ + get viewComponent() { + return this._viewComponent; + } + + /** + * Set the `Mediator`'s view component. + * + * @param {Object} viewComponent + */ + set viewComponent(viewComponent) { + this._viewComponent = viewComponent; + } + + /** + * The name of the `Mediator`. + * + *

Typically, a `Mediator` will be written to serve + * one specific control or group controls and so, + * will not have a need to be dynamically named.

+ * + * @static + * @returns {string} + */ + static get NAME() { return "Mediator" } +} + +/* + * Proxy.js + * PureMVC JavaScript Multicore + * + * Copyright(c) 2023 Saad Shams + * Your reuse is governed by the BSD License +*/ + + +/** + * A base `Proxy` implementation. + * + *

In PureMVC, `Proxy` classes are used to manage parts of the + * application's data model.

+ * + *

A `Proxy` might simply manage a reference to a local data object, + * in which case interacting with it might involve setting and + * getting of its data in synchronous fashion.

+ * + *

`Proxy` classes are also used to encapsulate the application's + * interaction with remote services to save or retrieve data, in which case, + * we adopt an asynchronous idiom; setting data (or calling a method) on the + * `Proxy` and listening for a `Notification` to be sent + * when the `Proxy` has retrieved the data from the service.

+ * + * @see Model Model + * + * @class Proxy + */ +class Proxy extends Notifier { + /** + * Constructor + * + * @constructor + * @param {string} proxyName + * @param {Object} [data] + */ + constructor(proxyName, data = null) { + super(); + /** @protected + * @type {string} */ + this._proxyName = proxyName || Proxy.NAME; + /** @protected + * @type {Object} */ + this._data = data; + } + + /** + * Called by the Model when the Proxy is registered + */ + onRegister() {} + + /** + * Called by the Model when the Proxy is removed + */ + onRemove() {} + + /** + * Get the proxy name + * + * @returns {string} + */ + get proxyName() { + return this._proxyName; + } + + /** + * Get the data object + * + * @returns {Object} + */ + get data () { + return this._data; + } + + /** + * Set the data object + * + * @param {Object} data + */ + set data(data) { + this._data = data; + } + + /** + * + * @static + * @returns {string} + */ + static get NAME() { return "Proxy" } +} + +/* + * index.js + * PureMVC JavaScript Multicore + * + * Copyright(c) 2023 Saad Shams + * Your reuse is governed by the BSD License +*/ + + +const puremvc = { + Controller, Model, View, + SimpleCommand, MacroCommand, Facade, + Mediator, Notification, Notifier, Observer, Proxy +}; + +export { puremvc }; diff --git a/package.json b/package.json new file mode 100644 index 0000000..14c0b4b --- /dev/null +++ b/package.json @@ -0,0 +1,11 @@ +{ + "name": "puremvc-js-util-async-command", + "version": "1.0.0", + "description": "Campaign rules evaluation and funds management system", + "main": "src/index.js", + "type": "module", + "repository": { + "type": "git", + "url": "https://github.com/PureMVC/puremvc-js-util-async-command.git" + } +} diff --git a/src/AsyncCommand.js b/src/AsyncCommand.js new file mode 100644 index 0000000..95a9ede --- /dev/null +++ b/src/AsyncCommand.js @@ -0,0 +1,42 @@ +/* + PureMVC MultiCore Utility for JS - AsyncCommand + Copyright(c) 2008 Duncan Hall + 2024 Cliff Hall + Your reuse is governed by the Creative Commons Attribution 3.0 License +*/ +import {puremvc} from '../lib/puremvc-2.0.0.js'; + +/** + * A base IAsyncCommand implementation. + * + * Your subclass should override the `execute` + * method where your business logic will handle the Notification.

+ * + * @see AsyncMacroCommand + */ +export class AsyncCommand extends puremvc.SimpleCommand { + + /** + * Registers the callback for a parent AsyncMacroCommand. + * + * @param callback The AsyncMacroCommand method to call on completion + */ + setOnComplete ( callback ) + { + this.onComplete = callback; + } + + /** + * Notify the parent AsyncMacroCommand that this command is complete. + * + * Call this method from your subclass to signify that your asynchronous + * command has finished. + */ + commandComplete() + { + this.onComplete(); + } + + onComplete; // the callback to invoke on command completion + isAsync = true; // simplest workaround to lack of interfaces +} diff --git a/src/AsyncMacroCommand.js b/src/AsyncMacroCommand.js new file mode 100644 index 0000000..8d81c6e --- /dev/null +++ b/src/AsyncMacroCommand.js @@ -0,0 +1,140 @@ +/* + PureMVC MultiCore Utility for JS - AsyncCommand + Copyright(c) 2008 Duncan Hall + 2024 Cliff Hall + Your reuse is governed by the Creative Commons Attribution 3.0 License +*/ +import {puremvc} from '../lib/puremvc-2.0.0.js'; + +/** + * A base Command implementation that executes other + * Commands asynchronously. + + * An AsyncMacroCommand maintains a list of + * factories that create SubCommands. + + * When execute is called, the AsyncMacroCommand + * caches a reference to the Notification and calls + * nextCommand. + + * If there are still SubCommands to be executed, + * the nextCommand method instantiates and calls + * execute on each of its SubCommands in turn. + * + * Each SubCommand will be passed a reference to the + * original Notification that was passed to the + * AsyncMacroCommand's execute method. If the + * SubCommand to execute is an IAsyncCommand, the + * next SubCommand will not be executed until the + * previousAsyncCommand has called its commandComplete + * method. + + * Unlike AsyncCommand and SimpleCommand, your subclass + * should not override execute, but instead, should + * override the initializeAsyncMacroCommand method, + * calling addSubCommand once for each SubCommand + * to be executed. + * + * @see AsyncCommand + */ +export class AsyncMacroCommand extends puremvc.Notifier +{ + + /** + * Constructor. + + * You should not need to define a constructor, + * instead, override the initializeAsyncMacroCommand + * method. + + * If your subclass does define a constructor, be + * sure to call super(). + */ + constructor() + { + super(); + this.subCommands = []; + this.initializeAsyncMacroCommand(); + } + + /** + * Initialize the AsyncMacroCommand. + * + * In your subclass, override this method to + * initialize the AsyncMacroCommand's SubCommand + * list with factories like this: + * + * // Initialize MyMacroCommand + * override protected function initializeAsyncMacroCommand() : void + * { + * addSubCommand( () => new FirstCommand ); + * addSubCommand( () => new SecondCommand ); + * addSubCommand( () => new ThirdCommand ); + * } + * + * SubCommands should extend one of the following + * - AsyncMacroCommand + * - AsyncCommand + * - MacroCommand + * - SimpleCommand + */ + initializeAsyncMacroCommand(){} + + /** + * Add a SubCommand. + * + * The SubCommands will be called in First In/First Out (FIFO) + * order. + * + * @param factory a function that instantiates the subcommand + */ + addSubCommand( factory ) { this.subCommands.push( factory );} + + /** + * Registers the callback for a parent AsyncMacroCommand. + * + * @param callback The AsyncMacroCommand method to call on completion + */ + setOnComplete ( callback ) { this.onComplete = callback; } + + /** + * Starts execution of this AsyncMacroCommand's SubCommands. + * + * The SubCommands will be called in First In/First Out (FIFO) order. + * + * @param notification the Notification object to be passed to each SubCommand. + */ + execute( notification ) + { + this.note = notification; + this.nextCommand(); + } + + /** + * Execute this AsyncMacroCommand's next SubCommand. + * + * If the next SubCommand is asynchronous, a callback is registered for + * the command completion, else the next command is run. + */ + nextCommand() + { + if (this.subCommands.length > 0) + { + const factory = this.subCommands.shift(); + const instance = factory(); + let isAsync = ( instance?.isAsync === true ); + if (isAsync) instance.setOnComplete( this.nextCommand ); + instance.initializeNotifier( this.multitonKey ); + instance.execute( this.note ); + if (!isAsync) this.nextCommand(); + } else { + if( this.onComplete !== null ) this.onComplete(); + this.note = null; + this.onComplete = null; + } + } + + note; // Notification + subCommands; // Array of command subcommand factories + isAsync = true; // simplest workaround to lack of interfaces +} \ No newline at end of file diff --git a/src/index.js b/src/index.js new file mode 100644 index 0000000..a6a62b0 --- /dev/null +++ b/src/index.js @@ -0,0 +1,9 @@ +import {puremvc} from '../lib/puremvc-2.0.0.js'; +import {AsyncCommand} from './AsyncCommand'; +import {AsyncMacroCommand} from './AsyncMacroCommand'; + +export { + puremvc, + AsyncCommand, + AsyncMacroCommand +} \ No newline at end of file