forked from jakesgordon/javascript-state-machine
-
Notifications
You must be signed in to change notification settings - Fork 0
/
state-machine.js
125 lines (93 loc) · 3.93 KB
/
state-machine.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
StateMachine = {
//---------------------------------------------------------------------------
VERSION: "2.0.0",
//---------------------------------------------------------------------------
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];
map[e.name] = map[e.name] || {};
for (var n = 0 ; n < from.length ; n++)
map[e.name][from[n]] = e.to;
};
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 !!map[event][this.current] && !this.transition; };
fsm.cannot = function(event) { return !this.can(event); };
if (initial && !initial.defer)
fsm[initial.event]();
return fsm;
},
//===========================================================================
beforeEvent: function(name, from, to, args) {
var func = this['onbefore' + name];
if (func)
return func.apply(this, [name, from, to].concat(args));
},
afterEvent: function(name, from, to, args) {
var func = this['onafter' + name] || this['on' + name];
if (func)
return func.apply(this, [name, from, to].concat(args));
},
leaveState: function(name, from, to, args) {
var func = this['onleave' + from];
if (func)
return func.apply(this, [name, from, to].concat(args));
},
enterState: function(name, from, to, args) {
var func = this['onenter' + to] || this['on' + to];
if (func)
return func.apply(this, [name, from, to].concat(args));
},
changeState: function(name, from, to, args) {
var func = this['onchangestate'];
if (func)
return func.apply(this, [name, from, to].concat(args));
},
buildEvent: function(name, map) {
return function() {
if (this.transition)
throw "event " + name + " innapropriate because previous transition did not complete"
if (this.cannot(name))
throw "event " + name + " innapropriate in current state " + this.current;
var from = this.current;
var to = map[from];
var args = Array.prototype.slice.call(arguments); // turn arguments into pure array
if (this.current != to) {
if (false === StateMachine.beforeEvent.call(this, name, from, to, args))
return;
var self = this;
this.transition = function() { // prepare transition method for use either lower down, or by caller if they want an async transition (indicated by a false return value from leaveState)
self.transition = null; // this method should only ever be called once
self.current = to;
StateMachine.enterState.call( self, name, from, to, args);
StateMachine.changeState.call(self, name, from, to, args);
StateMachine.afterEvent.call( self, name, from, to, args);
};
if (false !== StateMachine.leaveState.call(this, name, from, to, args)) {
if (this.transition) // in case user manually called it but forgot to return false
this.transition();
}
}
};
}
//===========================================================================
};