Autonomous Machine

List Ordering with a JavaScript State Machine

I've been thinking a lot about state machines recently- specifically, if the use of an event-driven state machine could be a useful pattern when creating JavaScript interfaces. I did a little poking around online, and the best resource I could find was a series of articles on IBM's developerWorks. The articles are actually quite good, and I'd recommend reading them. They include some nice background information on the hows and whys of state machine usage.

The one valid criticism I have of the articles is they spend a lot of time developing special code to deal with browser idiosyncrasies and event handling although the articles date from early 2007, well after the rise of JavaScript frameworks. I decided to see what I could to do build a lightweight state machine model using mostly framework code, in this case Prototype and Low Pro.

A Somewhat Contrived Example

Since I want to mostly show the syntax I'm using, here is a basic example for a simple widget: a web radio. Here are the states and transitions this example will be using:

State machine diagram for radio widget

States

Here are the states shown in that diagram, converted to JavaScript (I've stripped most of the functionality out for brevity, a link to the full code is below). I've stolen the idea of using functions as event handlers like this from the developerWorks articles. I like the flexibility it offers, and also that in order to transition to a new state, one of these functions simply needs to return the name of the new state. I've also added a shortcut- if a event handling function is only going to return a state name, that string can just be used in the definition in place of the function.

The state 'start' and its event 'init' are simply there so we can define a place for the machine to start.

Radio.definition = {
    start: {
        init: 'Stopped'
    },

    Stopped: {
        play: 'Playing'
    },
    
    Paused: {
        play: 'Playing',
        stop: function(event) {
            return 'Stopped';
        }
    },
    
    Playing: {
        enter: function() {
            // called when we enter this state
        },
        pause: 'Paused',
        stop: 'Stopped',
        exit: function() {
            // called when we leave this state
        }
    }
};

Events

The next thing to consider is how to map DOM events to the events defined above. I decided to define them for each state using a simple CSS selector to event name mapping. Just as with the state definitions, each CSS selector is either mapped to a string (this time an event name) or a function that will return an event name.

Radio.events = {
    Stopped: {
        'button.play:click': 'play'
    },
    Playing: {
        'button.stop:click': 'stop',
        'button.pause:click': function(event) {
            // ...
            return 'pause';
        }
    },
    Paused: {
        'button.stop:click': 'stop',
        'button.play:click': 'play'
    }
};

All Together Now

The state and event mapping definitions are brought together in a fairly standard Low Pro behavior, inheriting from another behavior containing the state machine logic. Any functions that are used as a part of the state and event definitions above are evaluated in the context of a behavior instance, so this is a convenient place for utility and helper functions.

Radio.machine = Behavior.create(State.behavior, {
    definition: Radio.definition,
    events: Radio.events,
    incrementCounter: function() {
        // ...
    },
    updateCounter: function() {
        // ...
    }
});

The complete behavior is applied using the standard Low Pro Event.addBehavior().

Event.addBehavior({
    'div.radio': Radio.machine
});

Here's a working example of the radio using the complete code.

Sort, Sort, Sort

Obviously, a state machine could be used to model a much more complicated interaction. A client application I'm working on had a need for some customizable sorting, and I decided to write the code from the ground up in order to have complete control over the sort lifecycle. After some thought, I decided to try to model the behavior of many desktop UI systems: a clone of the element being dragged would move with the mouse, and an insertion point would appear when and where a legal insertion could occur. I thought this was the best interface for most use cases as it allows the user to see both an object's new and old positions throughout the drag process. I also like the clear indication of when a valid drop can occur.

The states and events used by the ordering widget are shown in the following diagram:

State machine diagram for ordering widget

I won't paste all of the code to the sorter here, but I've posted it on GitHub as well as a working example. It worked out rather well, and I found the use of a state machine helped in keeping the code well organized and extensible, despite the fairly complicated interaction being modeled.