Javascript State Machine

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.

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

Comments