Skip to content

Commit 5bdc6be

Browse files
committed
Wrapped in self executing function to be more easily used with loaders like require.js or curl.js (issue jakesgordon#15)
1 parent 686b593 commit 5bdc6be

File tree

3 files changed

+124
-112
lines changed

3 files changed

+124
-112
lines changed

RELEASE_NOTES.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
Version 2.1.0 (January 7th 2012)
22
--------------------------------
33

4+
* Wrapped in self executing function to be more easily used with loaders like `require.js` or `curl.js` (issue #15)
45
* Allow event to be cancelled by returning `false` from `onleavestate` handler (issue #13) - WARNING: this breaks backward compatibility for async transitions (you now need to return `StateMachine.ASYNC` instead of `false`)
56
* Added explicit return values for event methods (issue #12)
67
* Added support for wildcard events that can be fired 'from' any state (issue #11)

state-machine.js

Lines changed: 122 additions & 111 deletions
Original file line numberDiff line numberDiff line change
@@ -1,144 +1,155 @@
1-
StateMachine = {
1+
(function (window) {
22

3-
//---------------------------------------------------------------------------
3+
StateMachine = {
44

5-
VERSION: "2.1.0",
5+
//---------------------------------------------------------------------------
66

7-
//---------------------------------------------------------------------------
7+
VERSION: "2.1.0",
88

9-
Result: {
10-
SUCCEEDED: 1, // the event transitioned successfully from one state to another
11-
NOTRANSITION: 2, // the event was successfull but no state transition was necessary
12-
CANCELLED: 3, // the event was cancelled by the caller in a beforeEvent callback
13-
ASYNC: 4, // the event is asynchronous and the caller is in control of when the transition occurs
14-
},
9+
//---------------------------------------------------------------------------
1510

16-
Error: {
17-
INVALID_TRANSITION: 100, // caller tried to fire an event that was innapropriate in the current state
18-
PENDING_TRANSITION: 200, // caller tried to fire an event while an async transition was still pending
19-
INVALID_CALLBACK: 300, // caller provided callback function threw an exception
20-
},
11+
Result: {
12+
SUCCEEDED: 1, // the event transitioned successfully from one state to another
13+
NOTRANSITION: 2, // the event was successfull but no state transition was necessary
14+
CANCELLED: 3, // the event was cancelled by the caller in a beforeEvent callback
15+
ASYNC: 4, // the event is asynchronous and the caller is in control of when the transition occurs
16+
},
2117

22-
WILDCARD: '*',
23-
ASYNC: 'async',
18+
Error: {
19+
INVALID_TRANSITION: 100, // caller tried to fire an event that was innapropriate in the current state
20+
PENDING_TRANSITION: 200, // caller tried to fire an event while an async transition was still pending
21+
INVALID_CALLBACK: 300, // caller provided callback function threw an exception
22+
},
2423

25-
//---------------------------------------------------------------------------
24+
WILDCARD: '*',
25+
ASYNC: 'async',
2626

27-
create: function(cfg, target) {
27+
//---------------------------------------------------------------------------
2828

29-
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 }
30-
var fsm = target || cfg.target || {};
31-
var events = cfg.events || [];
32-
var callbacks = cfg.callbacks || {};
33-
var map = {};
29+
create: function(cfg, target) {
3430

35-
var add = function(e) {
36-
var from = (e.from instanceof Array) ? e.from : (e.from ? [e.from] : [StateMachine.WILDCARD]); // allow 'wildcard' transition if 'from' is not specified
37-
map[e.name] = map[e.name] || {};
38-
for (var n = 0 ; n < from.length ; n++)
39-
map[e.name][from[n]] = e.to || from[n]; // allow no-op transition if 'to' is not specified
40-
};
31+
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 }
32+
var fsm = target || cfg.target || {};
33+
var events = cfg.events || [];
34+
var callbacks = cfg.callbacks || {};
35+
var map = {};
4136

42-
if (initial) {
43-
initial.event = initial.event || 'startup';
44-
add({ name: initial.event, from: 'none', to: initial.state });
45-
}
46-
47-
for(var n = 0 ; n < events.length ; n++)
48-
add(events[n]);
49-
50-
for(var name in map) {
51-
if (map.hasOwnProperty(name))
52-
fsm[name] = StateMachine.buildEvent(name, map[name]);
53-
}
37+
var add = function(e) {
38+
var from = (e.from instanceof Array) ? e.from : (e.from ? [e.from] : [StateMachine.WILDCARD]); // allow 'wildcard' transition if 'from' is not specified
39+
map[e.name] = map[e.name] || {};
40+
for (var n = 0 ; n < from.length ; n++)
41+
map[e.name][from[n]] = e.to || from[n]; // allow no-op transition if 'to' is not specified
42+
};
5443

55-
for(var name in callbacks) {
56-
if (callbacks.hasOwnProperty(name))
57-
fsm[name] = callbacks[name]
58-
}
44+
if (initial) {
45+
initial.event = initial.event || 'startup';
46+
add({ name: initial.event, from: 'none', to: initial.state });
47+
}
5948

60-
fsm.current = 'none';
61-
fsm.is = function(state) { return this.current == state; };
62-
fsm.can = function(event) { return !this.transition && (map[event].hasOwnProperty(this.current) || map[event].hasOwnProperty(StateMachine.WILDCARD)); }
63-
fsm.cannot = function(event) { return !this.can(event); };
64-
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)
49+
for(var n = 0 ; n < events.length ; n++)
50+
add(events[n]);
6551

66-
if (initial && !initial.defer)
67-
fsm[initial.event]();
52+
for(var name in map) {
53+
if (map.hasOwnProperty(name))
54+
fsm[name] = StateMachine.buildEvent(name, map[name]);
55+
}
6856

69-
return fsm;
57+
for(var name in callbacks) {
58+
if (callbacks.hasOwnProperty(name))
59+
fsm[name] = callbacks[name]
60+
}
7061

71-
},
62+
fsm.current = 'none';
63+
fsm.is = function(state) { return this.current == state; };
64+
fsm.can = function(event) { return !this.transition && (map[event].hasOwnProperty(this.current) || map[event].hasOwnProperty(StateMachine.WILDCARD)); }
65+
fsm.cannot = function(event) { return !this.can(event); };
66+
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)
7267

73-
//===========================================================================
68+
if (initial && !initial.defer)
69+
fsm[initial.event]();
7470

75-
doCallback: function(fsm, func, name, from, to, args) {
76-
if (func) {
77-
try {
78-
return func.apply(fsm, [name, from, to].concat(args));
79-
}
80-
catch(e) {
81-
return fsm.error(name, from, to, args, StateMachine.Error.INVALID_CALLBACK, "an exception occurred in a caller-provided callback function");
82-
}
83-
}
84-
},
71+
return fsm;
8572

86-
beforeEvent: function(fsm, name, from, to, args) { return StateMachine.doCallback(fsm, fsm['onbefore' + name], name, from, to, args); },
87-
afterEvent: function(fsm, name, from, to, args) { return StateMachine.doCallback(fsm, fsm['onafter' + name] || fsm['on' + name], name, from, to, args); },
88-
leaveState: function(fsm, name, from, to, args) { return StateMachine.doCallback(fsm, fsm['onleave' + from], name, from, to, args); },
89-
enterState: function(fsm, name, from, to, args) { return StateMachine.doCallback(fsm, fsm['onenter' + to] || fsm['on' + to], name, from, to, args); },
90-
changeState: function(fsm, name, from, to, args) { return StateMachine.doCallback(fsm, fsm['onchangestate'], name, from, to, args); },
73+
},
9174

75+
//===========================================================================
9276

93-
buildEvent: function(name, map) {
94-
return function() {
77+
doCallback: function(fsm, func, name, from, to, args) {
78+
if (func) {
79+
try {
80+
return func.apply(fsm, [name, from, to].concat(args));
81+
}
82+
catch(e) {
83+
return fsm.error(name, from, to, args, StateMachine.Error.INVALID_CALLBACK, "an exception occurred in a caller-provided callback function");
84+
}
85+
}
86+
},
9587

96-
var from = this.current;
97-
var to = map[from] || map[StateMachine.WILDCARD] || from;
98-
var args = Array.prototype.slice.call(arguments); // turn arguments into pure array
88+
beforeEvent: function(fsm, name, from, to, args) { return StateMachine.doCallback(fsm, fsm['onbefore' + name], name, from, to, args); },
89+
afterEvent: function(fsm, name, from, to, args) { return StateMachine.doCallback(fsm, fsm['onafter' + name] || fsm['on' + name], name, from, to, args); },
90+
leaveState: function(fsm, name, from, to, args) { return StateMachine.doCallback(fsm, fsm['onleave' + from], name, from, to, args); },
91+
enterState: function(fsm, name, from, to, args) { return StateMachine.doCallback(fsm, fsm['onenter' + to] || fsm['on' + to], name, from, to, args); },
92+
changeState: function(fsm, name, from, to, args) { return StateMachine.doCallback(fsm, fsm['onchangestate'], name, from, to, args); },
9993

100-
if (this.transition)
101-
return this.error(name, from, to, args, StateMachine.Error.PENDING_TRANSITION, "event " + name + " inappropriate because previous transition did not complete");
10294

103-
if (this.cannot(name))
104-
return this.error(name, from, to, args, StateMachine.Error.INVALID_TRANSITION, "event " + name + " inappropriate in current state " + this.current);
95+
buildEvent: function(name, map) {
96+
return function() {
10597

106-
if (false === StateMachine.beforeEvent(this, name, from, to, args))
107-
return StateMachine.CANCELLED;
98+
var from = this.current;
99+
var to = map[from] || map[StateMachine.WILDCARD] || from;
100+
var args = Array.prototype.slice.call(arguments); // turn arguments into pure array
108101

109-
if (from === to) {
110-
StateMachine.afterEvent(this, name, from, to, args);
111-
return StateMachine.NOTRANSITION;
112-
}
102+
if (this.transition)
103+
return this.error(name, from, to, args, StateMachine.Error.PENDING_TRANSITION, "event " + name + " inappropriate because previous transition did not complete");
104+
105+
if (this.cannot(name))
106+
return this.error(name, from, to, args, StateMachine.Error.INVALID_TRANSITION, "event " + name + " inappropriate in current state " + this.current);
107+
108+
if (false === StateMachine.beforeEvent(this, name, from, to, args))
109+
return StateMachine.CANCELLED;
110+
111+
if (from === to) {
112+
StateMachine.afterEvent(this, name, from, to, args);
113+
return StateMachine.NOTRANSITION;
114+
}
115+
116+
// 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)
117+
var fsm = this;
118+
this.transition = function() {
119+
fsm.transition = null; // this method should only ever be called once
120+
fsm.current = to;
121+
StateMachine.enterState( fsm, name, from, to, args);
122+
StateMachine.changeState(fsm, name, from, to, args);
123+
StateMachine.afterEvent( fsm, name, from, to, args);
124+
};
125+
126+
var leave = StateMachine.leaveState(this, name, from, to, args);
127+
if (false === leave) {
128+
this.transition = null;
129+
return StateMachine.CANCELLED;
130+
}
131+
else if ("async" === leave) {
132+
return StateMachine.ASYNC;
133+
}
134+
else {
135+
if (this.transition)
136+
this.transition(); // in case user manually called transition() but forgot to return ASYNC
137+
return StateMachine.SUCCEEDED;
138+
}
113139

114-
// 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)
115-
var fsm = this;
116-
this.transition = function() {
117-
fsm.transition = null; // this method should only ever be called once
118-
fsm.current = to;
119-
StateMachine.enterState( fsm, name, from, to, args);
120-
StateMachine.changeState(fsm, name, from, to, args);
121-
StateMachine.afterEvent( fsm, name, from, to, args);
122140
};
141+
}
123142

124-
var leave = StateMachine.leaveState(this, name, from, to, args);
125-
if (false === leave) {
126-
this.transition = null;
127-
return StateMachine.CANCELLED;
128-
}
129-
else if ("async" === leave) {
130-
return StateMachine.ASYNC;
131-
}
132-
else {
133-
if (this.transition)
134-
this.transition(); // in case user manually called transition() but forgot to return ASYNC
135-
return StateMachine.SUCCEEDED;
136-
}
137-
138-
};
139-
}
143+
}; // StateMachine
140144

141145
//===========================================================================
142146

143-
};
147+
if ("function" === typeof define) {
148+
define("statemachine", [], function() { return StateMachine; });
149+
}
150+
else {
151+
window.StateMachine = StateMachine;
152+
}
153+
154+
}(this));
144155

state-machine.min.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)