I wrote a simple Javascript State Machine.
It is intended for keeping track of what the current state is for my heavy AJAX app at work.
I also learned a lot about OO in javascript without using jQuery or the prototype framework, by using prototypes. I wanted to do that so that it would be framework independent.
Below is a simple example of how I use the state machine, and shows a bit of how I connected it with Really Simple History
I have included in this example some comments to show my thinking throughout, and perhaps how to use it.
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
| Site.prototype = new StateMachine();
Site.prototype.states = {
Begin: {
enter: function(){
// do some animation
},
exit: function(){
// hide some stuff
}
},
Page: {
enter: function(){
// show some page
},
exit: function(){
// hide it
},
change: function(page){
// example of some event
this.pages.change_page(page);
// events can call events
this.handleEvent('setURL');
},
setURL: function(){
//example of how I integrate with really simple history
dhtmlHistory.add(['pages'].concat(this.pages.current_page.split('/')).join('.').toLowerCase());
}
}
};
// These are some call backs I used for debugging with firebug to ensure
// that events were firing
Site.prototype.beforeEvent = function(name){
console.log('begin event: ', name);
}
Site.prototype.afterEvent = function(name){
console.log('end event: ', name);
}
Site.prototype.beforeStateChange = function(to, from){
console.log('begin state change to: ', to, ' from: ', from);
// as an example of intercepting, I can stop a state from changing by returning false
if(to == 'Page' && this.pages.blocker == 'Page'){
// some logic
return false;
}
return true;
}
Site.prototype.afterStateChange = function(to, from){
console.log('end state change to: ', to, ' from: ', from);
}
// The actual defenition. Instance vars can be set in it.
function Site(){
this.pages = new Page();
}
// global scope, so that it can be accessed globally (as it wouldn't normally
// be because the scope that I actually instantiate it inside prorotype's window onload)
var Davis;
// Once again, just an example of how I integrate it with Really Simple History
function historyChange(newLocation, historyData) {
var hash = newLocation.split('.');
var newState = hash.shift().capitalize();
if(newState == '') newState = 'Begin';
if(Davis.changeState(newState))
{
if(hash.length > 0) Davis.handleEvent('change', hash.join('/'));
}
}
// This window observe for onload is prototype specific... But you can achieve the same other ways.
Event.observe(window, 'load', function() {
Davis = new Site(); // my requirements need me to initialize after load
// Really Simple History stuff
dhtmlHistory.initialize(historyChange);
historyChange(dhtmlHistory.currentLocation || '');
});
|
While implementing this, I also learned a bit about bookmarking and enabling the back button of ajax states. Really Simple History has worked ok for me, but it has some quirks… some of which I haven’t solved yet (especially in IE) and those bugs may be above.
I also got my first dose of unit testing in javascript… and found some really cool library to assist in the development called newjs. However, at the time of this writing, the unit tests are not up to the current code, as a lot has changed… I will deal with that later.
Anyway, while my implementation surely isn’t the best in the world, I hope someone can find use from it.
http://github.com/phillc/Javascript-State-Machine