State.js outfits JavaScript objects with a specific behavior model, powered by a hierarchical and prototypal graph of states.

var state = require('state');

var person = {};
state( person, {
    Formal: {
        greet: function () { return "How do you do?"; }
    },
    Casual: {
        greet: function () { return "Hi!"; }
    }
});


person.hasOwnProperty('state');   // >>> true

person.state('-> Formal');
person.state();                   // >>> State 'Formal'
person.greet();                   // >>> "How do you do?"

person.state('-> Casual');
person.state();                   // >>> State 'Casual'
person.greet();                   // >>> "Hi!"
state = require 'state'

person = {}
state person,
  Formal:
    greet: -> "How do you do?"
  Casual:
    greet: -> "Hi!"


person.hasOwnProperty 'state'     # >>> true

person.state '-> Formal'
person.state()                    # >>> State 'Formal'
person.greet()                    # >>> "How do you do?"

person.state '-> Casual'
person.state()                    # >>> State 'Casual'
person.greet()                    # >>> "Hi!"

State instances define behavior for an owner object. They have a distinct object model, which allows them both to be hierarchically nested, and to inherit from States held by prototypes of their owner.

function Person () {}
state( Person.prototype, {
    Formal: {
        greet: function () { return "How do you do?"; },
        
        Highbrow: {
            greet: function () { return "Enchanté."; }
        }
    },
    Casual: {
        greet: function () { return "Hi!"; }
    }
});


var person = new Person;
var friend = new Person;

person.hasOwnProperty('state');   // >>> false

person.state('-> Highbrow');
friend.state('-> Casual');

person.greet();                   // >>> "Enchanté."
friend.greet();                   // >>> "Hi!"
class Person
  state @::
    Formal:
      greet: -> "How do you do?"
      Highbrow:
        greet: -> "Enchanté."
    Casual:
      greet: -> "Hi!"


person = new Person
friend = new Person

person.hasOwnProperty 'state'     # >>> false

person.state '-> Highbrow'
friend.state '-> Casual'

person.greet()                    # >>> "Enchanté."
friend.greet()                    # >>> "Hi!"

Attributes concisely empower or constrain certain aspects of a State, such as mutability, abstraction, and destination.

function Person () {}
state( Person.prototype, 'abstract', {
    Formal: state( 'default initial', {
        greet: function () { return "How do you do?"; }
    }),
    Casual: state( 'final', {
        greet: function () { return "Hi!"; }
    })
});


var person = new Person;

person.greet();                   // >>> "How do you do?"

person.state('->');
person.state();                   // >>> State 'Formal'
person.greet();                   // >>> "How do you do?"

person.state('-> Casual')
person.greet();                   // >>> "Hi!"

person.state('-> Formal')
person.state();                   // >>> State 'Casual'
class Person
  state @::, 'abstract'
    Formal: state 'default initial'
      greet: -> "How do you do?"
    Casual: state 'final'
      greet: -> "Hi!"


person = new Person

person.greet()                    # >>> "How do you do?"

person.state '->'
person.state()                    # >>> State 'Formal'
person.greet()                    # >>> "How do you do?"

person.state '-> Casual'
person.greet()                    # >>> "Hi!"

person.state '-> Formal'
person.state()                    # >>> State 'Casual'

State methods express or override behavior of the owner object. Methods are contextually bound to the State in which they are defined, and may optionally be decorated with additional bindings that describe the method’s complete state–lexical environment.

function inherit ( child, parent ) {
    child.prototype = Object.create( parent.prototype );
    return child.prototype.constructor = child;
}

inherit( Sophisticate, Person );
function Sophisticate ( name ) {
    this.name = name;
}
state( Sophisticate.prototype, 'abstract', {
    Formal: state( 'default initial', {
        Cordial: {
            greet: state.method( function ( person ) {
                var greeting = superstate.call('greet');
                var name = person && person.name;
                return name ?
                    "Hello " + name + ". " + greeting :
                    greeting;
            })
        }
    }),
    Casual: {
        greet: state.method( function ( person ) {
            var name = person && person.name;
            if ( name === 'Lane' ) return "How’s it hanging?";
            if ( name ) return "Hi " + name + ".";
            return protostate.call('greet');
        })
    }
});


var sterling = new Sophisticate('Roger');
var cooper   = new Sophisticate('Bert');
var draper   = new Sophisticate('Don');
var pryce    = new Sophisticate('Lane');

draper.state();               // >>> State 'Formal'
draper.greet( new Person );   // >>> "How do you do?"

draper.state('-> Cordial');
draper.greet( new Person );   // >>> "How do you do?"
draper.greet( cooper );       // >>> "Hello Bert. How do you do?"
draper.greet( sterling );     // >>> "Hello Roger. How do you do?"

draper.state('-> Casual');
draper.greet( new Person );   // >>> "Hi!"
draper.greet( sterling );     // >>> "Hi Roger."
draper.greet( pryce );        // >>> "How’s it hanging?"
class Sophisticate extends Person
  constructor: (@name) ->

  state @::, 'abstract'
    Formal: state 'default initial'
      Cordial:
        greet: state.method (person) ->
          greeting = superstate.call 'greet'
          if name = person?.name then "Hello #{name}. #{greeting}"
          else greeting
    Casual:
      greet: state.method (person) ->
        name = person?.name
        return "How’s it hanging?" if name is 'Lane'
        return "Hi #{name}." if name
        protostate.call 'greet'


[ sterling, cooper, draper, pryce ] =
  new Sophisticate n for n in ['Roger', 'Bert', 'Don', 'Lane']

draper.state()              # >>> State 'Formal'
draper.greet new Person     # >>> "How do you do?"

draper.state '-> Cordial'
draper.greet new Person     # >>> "How do you do?"
draper.greet cooper         # >>> "Hello Bert. How do you do?"
draper.greet sterling       # >>> "Hello Roger. How do you do?"

draper.state '-> Casual'
draper.greet new Person     # >>> "Hi!"
draper.greet sterling       # >>> "Hi Roger."
draper.greet pryce          # >>> "How’s it hanging?"

Events relate the progress of a transition, and signal the construction, destruction, or mutation of a State. They can also be emitted for any custom event type.

function Mover () {}
state( Mover.prototype, {

    Stationary: {
        Idle: state('initial'),
        Alert: state
    },
    Moving: {
        Walking: state,
        Running: {
            Sprinting: state
        }
    },

    // Use the root state’s `construct` event to programmatically
    // set up all of the states to log their transitional events.
    construct: function () {
        var states, events, s, e, i, ls, j, le;

        function bindEventToState ( e, s ) {
            s.on( e, function () {
                console.log( e + " " + this.name() );
            });
        }

        states = [ this ].concat( this.substates( true ) );
        events = ['depart', 'exit', 'enter', 'arrive'];

        for ( i = 0, ls = states.length; i < ls; i++ ) {
            s = states[i];
            for ( j = 0, le = events.length; j < le; j++ ) {
                e = events[j];
                bindEventToState( e, s );
            }
        }
    }
});


var m = new Mover;

m.state('-> Alert');
// log <<< "depart Idle"
// log <<< "exit Idle"
// log <<< "enter Alert"
// log <<< "arrive Alert"

m.state('-> Sprinting');
// log <<< "depart Alert"
// log <<< "exit Alert"
// log <<< "exit Stationary"
// log <<< "enter Moving"
// log <<< "enter Running"
// log <<< "enter Sprinting"
// log <<< "arrive Sprinting"
class Mover
  state @::
    Stationary:
      Idle: state 'initial'
      Alert: state
    Moving:
      Walking: state
      Running:
        Sprinting: state

    # Use the root state’s `construct` event to programmatically
    # set up all of the states to log their transitional events.
    construct: ->
      events = ['depart', 'exit', 'enter', 'arrive']
      for s in [this].concat @substates true
        for e in events
          do ( s, e ) -> s.on e, -> console.log "#{e} #{@name()}"


m = new Mover

m.state '-> Alert'
# log <<< "depart Idle"
# log <<< "exit Idle"
# log <<< "enter Alert"
# log <<< "arrive Alert"

m.state '-> Sprinting'
# log <<< "depart Alert"
# log <<< "exit Alert"
# log <<< "exit Stationary"
# log <<< "enter Moving"
# log <<< "enter Running"
# log <<< "enter Sprinting"
# log <<< "arrive Sprinting"

State instances are immutable by default, but may optionally be configured as mutable, allowing modular pieces of behavior to be implemented dynamically into a state.

var theRomansDo = {
    Formal: {
        greet: function () { return "Quid agis?"; }
    },
    Casual: {
        greet: function () { return "Salve!"; }
    }
};

function doAs ( behavior ) {
    return function () {
        return this.mutate( behavior );
    };
}


inherit( Traveler, Person );
function Traveler () {}
state( Traveler.prototype, 'mutable abstract', {
    goTo: function ( place ) {
        this.emit( 'in' + place );
    },

    events: {
        inRome: doAs( theRomansDo )
    },

    Formal: state('default')
});


var traveler = new Traveler;
traveler.greet();             // >>> "How do you do?"

traveler.goTo('Rome');

traveler.greet();             // >>> "Quid agis?"
traveler.state('-> Casual');
traveler.greet();             // >>> "Salve!"
theRomansDo =
  Formal:
    greet: -> "Quid agis?"
  Casual:
    greet: -> "Salve!"

doAs = (behavior) -> -> @mutate behavior


class Traveler extends Person
  state @::, 'mutable abstract'
    goTo: (place) -> @emit "in#{place}"

    events:
      inRome: doAs theRomansDo

    Formal: state 'default'


traveler = new Traveler
traveler.greet()              # >>> "How do you do?"

traveler.goTo 'Rome'

traveler.greet()              # >>> "Quid agis?"
traveler.state '-> Casual'
traveler.greet()              # >>> "Salve!"

Transitions between states can be conditionally guarded, defined generically across multiple states, and either synchronous or asynchronous.

function ContainerView () {}
state( ContainerView.prototype, 'abstract' {
    Grid: state('default initial'),
    List: state,

    transitions: {
        GridToList: {
            origin: 'Grid', target: 'List',
            action: function () {
                // Rearrange subviews into a vertical column
                // Change states of the subviews, if applicable
                // Load model data as appropriate for this state
                this.end();
            }
        },
        ListToGrid: {
            origin: 'List', target: 'Grid',
            action: function () {
                // ...
                this.end();
            }
        }
    }
});
class ContainerView
  state @::, 'abstract'
    Grid: state 'default initial'
    List: state

    transitions:
      GridToList:
        origin: 'Grid', target: 'List'
        action: ( args... ) ->
          # Rearrange subviews into a vertical column
          # Change states of the subviews, if applicable
          # Load model data as appropriate for this state
          @end()

      ListToGrid:
        origin: 'List', target: 'Grid'
        action: ( args... ) ->
          # ...
          @end()

Articles

Documentation

Installation Getting started Overview Concepts

API reference

state() State Transition

Annotated source

Unit tests

View on GitHub