diff options
| author | stephanchrst <stephanchrst@gmail.com> | 2022-05-10 21:51:50 +0700 |
|---|---|---|
| committer | stephanchrst <stephanchrst@gmail.com> | 2022-05-10 21:51:50 +0700 |
| commit | 3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch) | |
| tree | a44932296ef4a9b71d5f010906253d8c53727726 /addons/base_import/static/lib/javascript-state-machine/state-machine.js | |
| parent | 0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff) | |
initial commit 2
Diffstat (limited to 'addons/base_import/static/lib/javascript-state-machine/state-machine.js')
| -rw-r--r-- | addons/base_import/static/lib/javascript-state-machine/state-machine.js | 155 |
1 files changed, 155 insertions, 0 deletions
diff --git a/addons/base_import/static/lib/javascript-state-machine/state-machine.js b/addons/base_import/static/lib/javascript-state-machine/state-machine.js new file mode 100644 index 00000000..0d503ee7 --- /dev/null +++ b/addons/base_import/static/lib/javascript-state-machine/state-machine.js @@ -0,0 +1,155 @@ +(function (window) { + + StateMachine = { + + //--------------------------------------------------------------------------- + + VERSION: "2.1.0", + + //--------------------------------------------------------------------------- + + Result: { + SUCCEEDED: 1, // the event transitioned successfully from one state to another + NOTRANSITION: 2, // the event was successfull but no state transition was necessary + CANCELLED: 3, // the event was cancelled by the caller in a beforeEvent callback + ASYNC: 4, // the event is asynchronous and the caller is in control of when the transition occurs + }, + + Error: { + INVALID_TRANSITION: 100, // caller tried to fire an event that was innapropriate in the current state + PENDING_TRANSITION: 200, // caller tried to fire an event while an async transition was still pending + INVALID_CALLBACK: 300, // caller provided callback function threw an exception + }, + + WILDCARD: '*', + ASYNC: 'async', + + //--------------------------------------------------------------------------- + + create: function(cfg, target) { + + var initial = (typeof cfg.initial == 'string') ? { state: cfg.initial } : cfg.initial; // allow for a simple string, or an object with { state: 'foo', event: 'setup', defer: true|false } + var fsm = target || cfg.target || {}; + var events = cfg.events || []; + var callbacks = cfg.callbacks || {}; + var map = {}; + + var add = function(e) { + var from = (e.from instanceof Array) ? e.from : (e.from ? [e.from] : [StateMachine.WILDCARD]); // allow 'wildcard' transition if 'from' is not specified + map[e.name] = map[e.name] || {}; + for (var n = 0 ; n < from.length ; n++) + map[e.name][from[n]] = e.to || from[n]; // allow no-op transition if 'to' is not specified + }; + + if (initial) { + initial.event = initial.event || 'startup'; + add({ name: initial.event, from: 'none', to: initial.state }); + } + + for(var n = 0 ; n < events.length ; n++) + add(events[n]); + + for(var name in map) { + if (map.hasOwnProperty(name)) + fsm[name] = StateMachine.buildEvent(name, map[name]); + } + + for(var name in callbacks) { + if (callbacks.hasOwnProperty(name)) + fsm[name] = callbacks[name] + } + + fsm.current = 'none'; + fsm.is = function(state) { return this.current == state; }; + fsm.can = function(event) { return !this.transition && (map[event].hasOwnProperty(this.current) || map[event].hasOwnProperty(StateMachine.WILDCARD)); } + fsm.cannot = function(event) { return !this.can(event); }; + fsm.error = cfg.error || function(name, from, to, args, error, msg) { throw msg; }; // default behavior when something unexpected happens is to throw an exception, but caller can override this behavior if desired (see github issue #3) + + if (initial && !initial.defer) + fsm[initial.event](); + + return fsm; + + }, + + //=========================================================================== + + doCallback: function(fsm, func, name, from, to, args) { + if (func) { + try { + return func.apply(fsm, [name, from, to].concat(args)); + } + catch(e) { + return fsm.error(name, from, to, args, StateMachine.Error.INVALID_CALLBACK, "an exception occurred in a caller-provided callback function"); + } + } + }, + + beforeEvent: function(fsm, name, from, to, args) { return StateMachine.doCallback(fsm, fsm['onbefore' + name], name, from, to, args); }, + afterEvent: function(fsm, name, from, to, args) { return StateMachine.doCallback(fsm, fsm['onafter' + name] || fsm['on' + name], name, from, to, args); }, + leaveState: function(fsm, name, from, to, args) { return StateMachine.doCallback(fsm, fsm['onleave' + from], name, from, to, args); }, + enterState: function(fsm, name, from, to, args) { return StateMachine.doCallback(fsm, fsm['onenter' + to] || fsm['on' + to], name, from, to, args); }, + changeState: function(fsm, name, from, to, args) { return StateMachine.doCallback(fsm, fsm['onchangestate'], name, from, to, args); }, + + + buildEvent: function(name, map) { + return function() { + + var from = this.current; + var to = map[from] || map[StateMachine.WILDCARD] || from; + var args = Array.prototype.slice.call(arguments); // turn arguments into pure array + + if (this.transition) + return this.error(name, from, to, args, StateMachine.Error.PENDING_TRANSITION, "event " + name + " inappropriate because previous transition did not complete"); + + if (this.cannot(name)) + return this.error(name, from, to, args, StateMachine.Error.INVALID_TRANSITION, "event " + name + " inappropriate in current state " + this.current); + + if (false === StateMachine.beforeEvent(this, name, from, to, args)) + return StateMachine.CANCELLED; + + if (from === to) { + StateMachine.afterEvent(this, name, from, to, args); + return StateMachine.NOTRANSITION; + } + + // prepare a transition method for use EITHER lower down, or by caller if they want an async transition (indicated by an ASYNC return value from leaveState) + var fsm = this; + this.transition = function() { + fsm.transition = null; // this method should only ever be called once + fsm.current = to; + StateMachine.enterState( fsm, name, from, to, args); + StateMachine.changeState(fsm, name, from, to, args); + StateMachine.afterEvent( fsm, name, from, to, args); + }; + + var leave = StateMachine.leaveState(this, name, from, to, args); + if (false === leave) { + this.transition = null; + return StateMachine.CANCELLED; + } + else if ("async" === leave) { + return StateMachine.ASYNC; + } + else { + if (this.transition) + this.transition(); // in case user manually called transition() but forgot to return ASYNC + return StateMachine.SUCCEEDED; + } + + }; + } + + }; // StateMachine + + //=========================================================================== + + if ("function" === typeof define) { + define("statemachine", [], function() { return StateMachine; }); + } + else { + window.StateMachine = StateMachine; + } + +}(this)); + |
