Skip to content

Instantly share code, notes, and snippets.

@foca
Created August 25, 2012 08:23
Show Gist options
  • Star 14 You must be signed in to star a gist
  • Fork 9 You must be signed in to fork a gist
  • Save foca/3462441 to your computer and use it in GitHub Desktop.
Save foca/3462441 to your computer and use it in GitHub Desktop.
Stateful is a simple implementation of the State Pattern for JavaScript.
// Stateful is a simple implementation of the state pattern for javascript.
//
// Read more on this design pattern here:
// -> http://sourcemaking.com/design_patterns/state
//
// Initialize Stateful by passing it an object, the name of the initial state
// (defaults to "default"), and an optional hash of interfaces that will be
// applied for each state. If these interfaces are not passed, they default to
// the object's constructor's States property. So, for example:
//
// Example:
//
// function TrafficLight() {
// this.state = new Stateful(this, "stop");
// }
//
// TrafficLight.States = {
// stop: {
// color: "red",
// time: 8,
//
// next: function() {
// this.state.transition("go");
// },
//
// onEnterState: function() {
// // Turn on traffic camera to see who crosses on a red light
// },
//
// onExitState: function() {
// // Turn off traffic camera
// }
// },
//
// go: {
// color: "green",
// time: 10,
//
// next: function() {
// this.state.transition("caution");
// }
// },
//
// caution: {
// color: "yellow",
// time: 2,
//
// next: function() {
// this.state.transition("stop");
// }
// }
// }
//
// var light = new TrafficLight();
// light.color //=> "red"
// light.next()
// light.color //=> "green"
// light.next()
// light.color //=> "yellow"
// light.next()
// light.color //=> "red"
//
// Each state interface can have "special" `onEnterState` / `onExitState`
// methods that get called automatically whenever you switch states.
//
function Stateful(object, initialState, interfaces, onInitialize) {
var currentState;
this.interfaces = interfaces = interfaces || object.constructor.States;
if (typeof interfaces == "undefined") {
throw "An object with the set of interfaces for each state is required";
}
function trigger() {
if (typeof object.trigger == "function") {
object.trigger.apply(object, arguments);
}
}
function applyState(state) {
var previousInterface = interfaces[currentState];
var newInterface = interfaces[state];
if (typeof newInterface == "undefined") {
throw "Invalid state: " + state;
}
if (previousInterface) {
trigger("state:exit", currentState);
if (typeof previousInterface.onExitState == "function") {
object.onExitState();
}
for (property in previousInterface) {
delete object[property];
}
delete object["onEnterState"];
delete object["onExitState"];
trigger("state:exited", currentState);
}
trigger("state:enter", state);
for (property in newInterface) {
object[property] = newInterface[property]
}
if (typeof newInterface.onEnterState == "function") {
object.onEnterState();
}
trigger("state:entered", state);
}
function transitionTo(state) {
applyState(state);
currentState = state;
trigger("state:change");
}
function isCurrentState(state) {
return currentEvents === state;
}
if (typeof onInitialize == "function") {
onInitialize(this, object);
}
transitionTo(initialState || "default");
return {
is: isCurrentState,
transition: transitionTo
};
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment