123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202 |
- (function (window) {
- var StateMachine = {
-
- VERSION: "2.2.0",
-
- Result: {
- SUCCEEDED: 1,
- NOTRANSITION: 2,
- CANCELLED: 3,
- PENDING: 4
- },
- Error: {
- INVALID_TRANSITION: 100,
- PENDING_TRANSITION: 200,
- INVALID_CALLBACK: 300
- },
- WILDCARD: '*',
- ASYNC: 'async',
-
- create: function(cfg, target) {
- var initial = (typeof cfg.initial == 'string') ? { state: cfg.initial } : cfg.initial;
- var terminal = cfg.terminal || cfg['final'];
- 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]);
- map[e.name] = map[e.name] || {};
- for (var n = 0 ; n < from.length ; n++)
- map[e.name][from[n]] = e.to || from[n];
- };
- 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 (state instanceof Array) ? (state.indexOf(this.current) >= 0) : (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, e) { throw e || msg; };
- fsm.isFinished = function() { return this.is(terminal); };
- 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", e);
- }
- }
- },
- beforeAnyEvent: function(fsm, name, from, to, args) { return StateMachine.doCallback(fsm, fsm['onbeforeevent'], name, from, to, args); },
- afterAnyEvent: function(fsm, name, from, to, args) { return StateMachine.doCallback(fsm, fsm['onafterevent'] || fsm['onevent'], name, from, to, args); },
- leaveAnyState: function(fsm, name, from, to, args) { return StateMachine.doCallback(fsm, fsm['onleavestate'], name, from, to, args); },
- enterAnyState: function(fsm, name, from, to, args) { return StateMachine.doCallback(fsm, fsm['onenterstate'] || fsm['onstate'], name, from, to, args); },
- changeState: function(fsm, name, from, to, args) { return StateMachine.doCallback(fsm, fsm['onchangestate'], name, from, to, args); },
- beforeThisEvent: function(fsm, name, from, to, args) { return StateMachine.doCallback(fsm, fsm['onbefore' + name], name, from, to, args); },
- afterThisEvent: function(fsm, name, from, to, args) { return StateMachine.doCallback(fsm, fsm['onafter' + name] || fsm['on' + name], name, from, to, args); },
- leaveThisState: function(fsm, name, from, to, args) { return StateMachine.doCallback(fsm, fsm['onleave' + from], name, from, to, args); },
- enterThisState: function(fsm, name, from, to, args) { return StateMachine.doCallback(fsm, fsm['onenter' + to] || fsm['on' + to], name, from, to, args); },
- beforeEvent: function(fsm, name, from, to, args) {
- if ((false === StateMachine.beforeThisEvent(fsm, name, from, to, args)) ||
- (false === StateMachine.beforeAnyEvent( fsm, name, from, to, args)))
- return false;
- },
- afterEvent: function(fsm, name, from, to, args) {
- StateMachine.afterThisEvent(fsm, name, from, to, args);
- StateMachine.afterAnyEvent( fsm, name, from, to, args);
- },
- leaveState: function(fsm, name, from, to, args) {
- var specific = StateMachine.leaveThisState(fsm, name, from, to, args),
- general = StateMachine.leaveAnyState( fsm, name, from, to, args);
- if ((false === specific) || (false === general))
- return false;
- else if ((StateMachine.ASYNC === specific) || (StateMachine.ASYNC === general))
- return StateMachine.ASYNC;
- },
- enterState: function(fsm, name, from, to, args) {
- StateMachine.enterThisState(fsm, name, from, to, args);
- StateMachine.enterAnyState( fsm, 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);
- 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.Result.CANCELLED;
- if (from === to) {
- StateMachine.afterEvent(this, name, from, to, args);
- return StateMachine.Result.NOTRANSITION;
- }
-
- var fsm = this;
- this.transition = function() {
- fsm.transition = null;
- fsm.current = to;
- StateMachine.enterState( fsm, name, from, to, args);
- StateMachine.changeState(fsm, name, from, to, args);
- StateMachine.afterEvent( fsm, name, from, to, args);
- return StateMachine.Result.SUCCEEDED;
- };
- this.transition.cancel = function() {
- fsm.transition = null;
- StateMachine.afterEvent(fsm, name, from, to, args);
- }
- var leave = StateMachine.leaveState(this, name, from, to, args);
- if (false === leave) {
- this.transition = null;
- return StateMachine.Result.CANCELLED;
- }
- else if (StateMachine.ASYNC === leave) {
- return StateMachine.Result.PENDING;
- }
- else {
- if (this.transition)
- return this.transition();
- }
- };
- }
- };
-
- if ("function" === typeof define) {
- define(function(require) { return StateMachine; });
- }
- else {
- window.StateMachine = StateMachine;
- }
- }(this));
|