Skip to content

Commit 956e092

Browse files
committed
initial version
0 parents  commit 956e092

17 files changed

Lines changed: 2261 additions & 0 deletions

README.md

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
Javascript Finite State Machine
2+
===============================
3+
4+
This standalone javascript micro-framework provides a finite state machine for your pleasure.
5+
6+
* You can find the [code here](https://github.com/jakesgordon/javascript-state-machine)
7+
* You can find a [description here](https://github.com/jakesgordon/javascript-state-machine) - COMING SOON
8+
* You can find a [working demo here](https://github.com/jakesgordon/javascript-state-machine) - COMING SOON
9+
10+
* All code is in state-machine.js
11+
* No 3rd party library is required
12+
* Demo can be found in /index.html
13+
* QUnit tests can be found in /test/index.html
14+
15+
Usage
16+
=====
17+
18+
Include `state-machine.min.js` in your application.
19+
20+
In its simplest form, create a standalone state machine using:
21+
22+
var fsm = StateMachine.create({
23+
state: 'green',
24+
events: [
25+
{ name: 'warn', from: 'green', to: 'yellow' },
26+
{ name: 'panic', from: 'yellow', to: 'red' },
27+
{ name: 'calm', from: 'red', to: 'yellow' },
28+
{ name: 'clear', from: 'yellow', to: 'green' }
29+
]});
30+
31+
... will create an object with a method for each event:
32+
33+
* fsm.warn() - transition from 'green' to 'yellow'
34+
* fsm.panic() - transition from 'yellow' to 'red'
35+
* fsm.calm() - transition from 'red' to 'yellow'
36+
* fsm.clear() - transition from 'yellow' to 'green'
37+
38+
along with the following members:
39+
40+
* fsm.current - contains the current state
41+
* fsm.is(s) - return true if state `s` is the current state
42+
* fsm.can(e) - return true if event `e` can be fired in the current state
43+
* fsm.cannot(e) - return true if event `e` cannot be fired in the current state
44+
45+
Multiple Transitions
46+
====================
47+
48+
If an event should be available from multiple states, simply use an array in the event `from` argument:
49+
50+
var fsm = StateMachine.create({
51+
state: 'green',
52+
events: [
53+
{ name: 'warn', from: ['green'], to: 'yellow' },
54+
{ name: 'panic', from: ['green', 'yellow'], to: 'red' },
55+
{ name: 'calm', from: ['red'], to: 'yellow' },
56+
{ name: 'clear', from: ['red', 'yellow'], to: 'green' }
57+
]});
58+
59+
Hooks
60+
=====
61+
62+
4 hooks are available if FSM has methods using the following naming conventions:
63+
64+
* onbefore**event** - fired before an event
65+
* onafter**event** - fired after an event
66+
* onenter**state** - fired when entering a state
67+
* onleave**state** - fired when leaving a state
68+
69+
The order of the hooks should be as expected:
70+
71+
* onbefore**event** - fired before an event
72+
* onleave**state** - fired when leaving existing state
73+
* onenter**state** - fired when entering new state
74+
* onafter**event** - fired after event
75+
76+
For convenience, the 2 most useful hooks can be shortened:
77+
78+
* on**event** - convenience shorthand for onafter**event**
79+
* on**state** - convenience shorthand for onenter**state**
80+
81+
Hooks can be added after the FSM is created:
82+
83+
var fsm = StateMachine.create({
84+
state: 'green',
85+
events: [
86+
{ name: 'warn', from: 'green', to: 'yellow' },
87+
{ name: 'panic', from: 'yellow', to: 'red' },
88+
{ name: 'calm', from: 'red', to: 'yellow' },
89+
{ name: 'clear', from: 'yellow', to: 'green' }
90+
]});
91+
92+
fsm.onpanic = function() { alert('panic!'); };
93+
fsm.onclear = function() { alert('all clear!'); };
94+
fsm.ongreen = function() { document.body.className = 'green'; };
95+
fsm.onyellow = function() { document.body.className = 'yellow'; };
96+
fsm.onred = function() { document.body.className = 'red'; };
97+
98+
fsm.panic()
99+
fsm.clear()
100+
...
101+
102+
Alternatively, hooks can be added before the FSM is created (to be sure of including the
103+
initial state transition), by turning an existing object into an FSM using the `target`
104+
option:
105+
106+
var fsm = {
107+
onpanic : function() { alert('panic!'); },
108+
onclear : function() { alert('all clear!'); },
109+
ongreen : function() { document.body.className = 'green'; },
110+
onyellow : function() { document.body.className = 'yellow'; },
111+
onred : function() { document.body.className = 'red'; }
112+
};
113+
114+
StateMachine.create({
115+
target: fsm,
116+
state: 'green',
117+
events: [
118+
{ name: 'warn', from: 'green', to: 'yellow' },
119+
{ name: 'panic', from: 'yellow', to: 'red' },
120+
{ name: 'calm', from: 'red', to: 'yellow' },
121+
{ name: 'clear', from: 'yellow', to: 'green' },
122+
]});
123+
124+
125+
fsm.panic()
126+
fsm.clear()
127+
...
128+
129+
In this way, you can also turn all instances of a _class_ into an FSM by applying
130+
the state machine functionality in a constructor function, and adding your hooks
131+
into the prototype:
132+
133+
MyFSM = function() {
134+
StateMachine.create({
135+
target: this,
136+
state: 'green',
137+
events: [
138+
{ name: 'warn', from: 'green', to: 'yellow' },
139+
{ name: 'panic', from: 'yellow', to: 'red' },
140+
{ name: 'calm', from: 'red', to: 'yellow' },
141+
{ name: 'clear', from: 'yellow', to: 'green' }
142+
]});
143+
144+
// other constructor behavior
145+
146+
};
147+
148+
MyFSM.prototype = {
149+
150+
onpanic: function() { alert('panic'); },
151+
onclear: function() { alert('all is clear'); },
152+
153+
// other prototype methods
154+
155+
};
156+
157+
This should be easy to adjust to fit your appropriate mechanism for object construction.
158+
159+
160+
161+
162+
163+

Rakefile

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
2+
desc "create minified version of state-machine.js"
3+
task :minify do
4+
require 'minifier/minifier'
5+
Minifier.enabled = true
6+
Minifier.minify('state-machine.js')
7+
end
8+

demo/demo.css

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
#demo { width: 400px; margin: 0 auto; }
2+
3+
#controls { text-align: center; }
4+
5+
#demo #diagram { width: 400px; height: 275px; }
6+
#demo #output { width: 100%; height: 30em; }
7+
8+
#demo.green #diagram { background: url(images/alerts.green.png); }
9+
#demo.yellow #diagram { background: url(images/alerts.yellow.png); }
10+
#demo.red #diagram { background: url(images/alerts.red.png); }
11+

demo/demo.js

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
Demo = {
2+
3+
run: function() {
4+
StateMachine.create({
5+
target: this,
6+
state: 'green',
7+
events: [
8+
{ name: 'warn', from: ['green'], to: 'yellow' },
9+
{ name: 'panic', from: ['green', 'yellow'], to: 'red' },
10+
{ name: 'calm', from: ['red'], to: 'yellow' },
11+
{ name: 'clear', from: ['red', 'yellow'], to: 'green' },
12+
]});
13+
},
14+
15+
onbeforestartup: function() { this.log("STATE MACHINE IS STARTING UP"); },
16+
17+
onbeforewarn: function() { this.log("START EVENT: warn!", true); },
18+
onbeforepanic: function() { this.log("START EVENT: panic!", true); },
19+
onbeforecalm: function() { this.log("START EVENT: calm!", true); },
20+
onbeforeclear: function() { this.log("START EVENT: clear!", true); },
21+
22+
onwarn: function() { this.log("FINISH EVENT: warn!"); },
23+
onpanic: function() { this.log("FINISH EVENT: panic!"); },
24+
oncalm: function() { this.log("FINISH EVENT: calm!"); },
25+
onclear: function() { this.log("FINISH EVENT: clear!"); },
26+
27+
onleavegreen: function() { this.log("LEAVE STATE: green"); },
28+
onleaveyellow: function() { this.log("LEAVE STATE: yellow"); },
29+
onleavered: function() { this.log("LEAVE STATE: red"); },
30+
31+
ongreen: function() { this.log("ENTER STATE: green"); },
32+
onyellow: function() { this.log("ENTER STATE: yellow"); },
33+
onred: function() { this.log("ENTER STATE: red"); },
34+
35+
log: function(msg, separate) {
36+
this.count = (this.count || 0) + (separate ? 1 : 0);
37+
38+
var output = document.getElementById('output');
39+
output.value = this.count + ": " + msg + "\n" + (separate ? "\n" : "") + output.value;
40+
41+
document.getElementById('demo').className = this.current;
42+
document.getElementById('panic').disabled = this.cannot('panic');
43+
document.getElementById('warn').disabled = this.cannot('warn');
44+
document.getElementById('calm').disabled = this.cannot('calm');
45+
document.getElementById('clear').disabled = this.cannot('clear');
46+
}
47+
48+
};

demo/images/alerts.green.png

18.1 KB
Loading

demo/images/alerts.red.png

18.1 KB
Loading

demo/images/alerts.yellow.png

18.2 KB
Loading

index.html

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<title>Javascript Finite State Machine</title>
5+
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
6+
<link href="demo/demo.css" media="screen, print" rel="stylesheet" type="text/css" />
7+
</head>
8+
9+
<body>
10+
11+
<div id="demo" class='green'>
12+
13+
<h1> Finite State Machine </h1>
14+
15+
<div id="controls">
16+
<button id="clear" onclick="Demo.clear();">all clear</button>
17+
<button id="calm" onclick="Demo.calm();">calm down</button>
18+
<button id="warn" onclick="Demo.warn();">warn</button>
19+
<button id="panic" onclick="Demo.panic();">panic!</button>
20+
</div>
21+
22+
<div id="diagram">
23+
</div>
24+
25+
<textarea id="output">
26+
</textarea>
27+
28+
</div>
29+
30+
31+
<script src="state-machine.js"></script>
32+
<script src="demo/demo.js"></script>
33+
<script>
34+
Demo.run();
35+
</script>
36+
37+
</body>
38+
</html>

minifier/LICENSE.TXT

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
YUI Compressor Copyright License Agreement (BSD License)
2+
3+
Copyright (c) 2010, Yahoo! Inc.
4+
All rights reserved.
5+
6+
Redistribution and use of this software in source and binary forms,
7+
with or without modification, are permitted provided that the following
8+
conditions are met:
9+
10+
* Redistributions of source code must retain the above
11+
copyright notice, this list of conditions and the
12+
following disclaimer.
13+
14+
* Redistributions in binary form must reproduce the above
15+
copyright notice, this list of conditions and the
16+
following disclaimer in the documentation and/or other
17+
materials provided with the distribution.
18+
19+
* Neither the name of Yahoo! Inc. nor the names of its
20+
contributors may be used to endorse or promote products
21+
derived from this software without specific prior
22+
written permission of Yahoo! Inc.
23+
24+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
25+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
26+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
27+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
28+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
29+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
30+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
31+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
32+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
33+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
34+
35+
This software also requires access to software from the following sources:
36+
37+
The Jarg Library v 1.0 ( http://jargs.sourceforge.net/ ) is available
38+
under a BSD License � Copyright (c) 2001-2003 Steve Purcell,
39+
Copyright (c) 2002 Vidar Holen, Copyright (c) 2002 Michal Ceresna and
40+
Copyright (c) 2005 Ewan Mellor.
41+
42+
The Rhino Library ( http://www.mozilla.org/rhino/ ) is dually available
43+
under an MPL 1.1/GPL 2.0 license, with portions subject to a BSD license.
44+
45+
Additionally, this software contains modified versions of the following
46+
component files from the Rhino Library:
47+
48+
[org/mozilla/javascript/Decompiler.java]
49+
[org/mozilla/javascript/Parser.java]
50+
[org/mozilla/javascript/Token.java]
51+
[org/mozilla/javascript/TokenStream.java]
52+
53+
The modified versions of these files are distributed under the MPL v 1.1
54+
( http://www.mozilla.org/MPL/MPL-1.1.html )

minifier/minifier.rb

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
module Minifier
2+
3+
class << self
4+
attr_accessor :enabled
5+
attr_accessor :extensions
6+
end
7+
8+
self.extensions = ['.js', '.css']
9+
10+
def self.available?
11+
@available ||= !`which java`.empty? # warning: linux only way of checking if java is available
12+
end
13+
14+
def self.enabled?(name = nil)
15+
enabled && available? && (name.nil? || extensions.include?(File.extname(name)))
16+
end
17+
18+
def self.minified_name(name)
19+
if enabled?(name)
20+
ext = File.extname(name)
21+
name.sub(ext, ".min#{ext}")
22+
else
23+
name
24+
end
25+
end
26+
27+
def self.minify(name)
28+
if name && enabled?(name) && File.exists?(name)
29+
minified_name = minified_name(name)
30+
`java -jar "#{File.dirname(__FILE__)}/yuicompressor-2.4.6.jar" "#{name}" -o "#{minified_name}"`
31+
end
32+
end
33+
34+
end

0 commit comments

Comments
 (0)