State.js is a JavaScript library for embedding composable, heritable first-class states into arbitrary owner objects.

var state = require('state');      // >>> [Function]
state = require 'state'          # >>> [Function]

The exported state function will be used here in two ways: (1) to define structures called state expressions, which describe behavior in terms of methods, data, events, etc.; and (2) to implement a composite state expression on an owner, producing a tree of States that belong to the owner and describe its potential exhibited behaviors.

var owner = {};

state( owner, {
    A: state({
        aMethod: function () {};
    }),
    B: state({
        aMethod: function () {};
    })
});
owner = {}

state owner,
  A: state
    aMethod: ->
  B: state
    aMethod: ->

The implementing form has two effects: (1) the owner is given a new state method, which closes over the owner’s new state tree and serves as the accessor to its States; and (2) for any method defined at least once in the state tree, a corresponding dispatcher method is created and added to the owner.

owner.state;                       // >>> [Function]
owner.aMethod;                     // >>> [Function]
owner.state                      # >>> [Function]
owner.aMethod                    # >>> [Function]

States and currency

State objects are modules of behavior that may be exhibited interchangeably by their owner object. States may define method overrides, arbitrary data, event listeners, guards, substates, and transition expressions.

var owner = {};
state( owner, {
    A: state({
        aMethod: function () { return "alpha"; }
    }),
    B: state({
        aMethod: function () { return "beta"; }
    })
});

var root   = owner.state('');      // >>> RootState
var stateA = owner.state('A');     // >>> State 'A'
var stateB = owner.state('B');     // >>> State 'B'
state owner = {},
  A: state
    aMethod: -> "alpha"
  B: state
    aMethod: -> "beta"

root   = owner.state ''          # >>> RootState
stateA = owner.state 'A'         # >>> State 'A'
stateB = owner.state 'B'         # >>> State 'B'

States

Exactly one State is designated as the owner’s current state, whose own and inherited behavior is exhibited by the owner.

owner.state();                     // >>> RootState
owner.state() === root;            // >>> true
owner.state() === root.current();  // >>> true (invariant)
owner.state()                    # >>> RootState
owner.state() is root            # >>> true
owner.state() is root.current()  # >>> true (invariant)

The owner may alter its behavior by undergoing transitions, which carry the current state reference, or currency, to a different State.

owner.state('-> A');
owner.state() === stateA;          // >>> true
owner.aMethod();                   // >>> "alpha"

owner.state('-> B');
owner.state() === stateB;          // >>> true
owner.aMethod();                   // >>> "beta"

owner.state('->');
owner.state() === root;            // >>> true
owner.aMethod();                   // >>> undefined

owner.state().owner === owner;     // >>> true (invariant)
owner.state().root === root;       // >>> true (invariant)
owner.state '-> A'
owner.state() is stateA          # >>> true
owner.aMethod()                  # >>> "alpha"

owner.state '-> B'
owner.state() is stateB          # >>> true
owner.aMethod()                  # >>> "beta"

owner.state '->'
owner.state() is root            # >>> true
owner.aMethod()                  # >>> undefined

owner.state().owner is owner     # >>> true (invariant)
owner.state().root is root       # >>> true (invariant)

Object model

State objects may inherit and be composed from other States.

  • Hierarchical single-inheritance is provided by the superstate–substate relation, which defines a state tree rooted from the owner’s unique root state.

  • Compositional multiple-inheritance is provided simultaneously by parastates. The parastate and superstate ancestors of a State are C3–linearized into a monotonic, unambiguous resolution order.

  • Indirect prototypal inheritance is also defined by the protostate–epistate relation, an implication of the owner’s prototype chain.

var p = {};
state( p, {
    A: state,
    B: state({
        BA: state,
        BB: state
    })
});

var o = Object.create( p );
state( o, {
    A: state({
        AA: state.extend('X, Y')
    }),
    X: state,
    Y: state
});
class Class
  state p = @::,
    A: state
    B: state
      BA: state
      BB: state

state o = new Class,
  A: state
    AA: state.extend 'X, Y'
  X: state
  Y: state

State object model

Attributes

Attributes are a set of keyword strings that may precede the body of a state expression. These concisely empower or constrain certain aspects of a State, such as abstraction, destination, and mutability.

function Developer () {}

state( Developer.prototype, 'abstract', {
    develop: function () { this.state('-> Seasoned'); },

    Juvenile: state( 'initial', {
        greet: function () { return "sup"; }
    }),
    Seasoned: state( 'final', {
        greet: function () { return "Hello."; }
    })
});


var dev = new Developer;

dev.state();                   // >>> State 'Juvenile'
dev.greet();                   // >>> "sup"

dev.develop();

dev.state();                   // >>> State 'Seasoned'
dev.greet();                   // >>> "Hello."

dev.state('-> Juvenile');      // (No effect)

dev.state();                   // >>> State 'Seasoned'
dev.greet();                   // >>> "Hello."
class Developer
  state @::, 'abstract',
    develop: -> @state '-> Seasoned'

    Juvenile: state 'initial',
      greet: -> "sup"
    Seasoned: state 'final',
      greet: -> "Hello."


dev = new Developer

dev.state()                    # >>> State 'Juvenile'
dev.greet()                    # >>> "sup"

do dev.develop

dev.state()                    # >>> State 'Seasoned'
dev.greet()                    # >>> "Hello."

dev.state '-> Juvenile'        # (No effect)

dev.state()                    # >>> State 'Seasoned'
dev.greet()                    # >>> "Hello."

Methods

State methods express or override behavior of the owner. Method calls received by the owner are dispatched to its current state’s own or inherited implementation of the corresponding method.

By default, state methods are invoked in the context of the owner, just like normal methods. To gain insight into its place in the owner’s state tree, a method definition can instead be contextually bound to the State for which the method acts. (This will be the State in which the method is defined, unless the method is inherited from a protostate, in which case the context will be the inheriting epistate.)

function Avenger ( name ) {
    this.name = name;
}

Avenger.prototype.greet = function () { return "Hello."; };

state( Avenger.prototype, 'abstract', {
    Terse: state('default'),
    Verbose: state({
        greet: state.bind( function () {
            return this.superstate.call('greet') +
                " My name is " + this.owner.name + "...";
        })
    })
});


var inigo = new Avenger('Inigo');

inigo.state();              // >>> State 'Terse'
inigo.greet();              // >>> "Hello."

inigo.state('-> Verbose');  // >>> State 'Verbose'
inigo.greet();              // >>> "Hello. My name is Inigo..."
class Avenger
  constructor: ( @name ) ->

  greet: -> "Hello."

  state @::, 'abstract',
    Terse: state 'default'
    Verbose: state
      greet: state.bind ->
        "#{ @superstate.call 'greet' } My name is #{ @owner.name }..."


inigo = new Avenger 'Inigo'

inigo.state()              # >>> State 'Terse'
inigo.greet()              # >>> "Hello."

inigo.state '-> Verbose'   # >>> State 'Verbose'
inigo.greet()              # >>> "Hello. My name is Inigo..."

A state method can also be wrapped in a decorator that fixes the method with bindings to the precise State where it is defined (invariant across the protostate–epistate relation).

Events

State provides built-in events that relate the progress of a transition as it traverses the state hierarchy, along with events that signal the construction, destruction, or mutation of a State. Events 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
        }
    }
});

// Set up each state to log its transitional events.
( function () {
    var states, eventNames, i, j;

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

    states = Mover.prototype.state('**');
    eventNames = ['depart', 'exit', 'enter', 'arrive'];

    for ( i = 0; i < states.length; i++ ) {
        for ( j = 0; j < eventNames.length; j++ ) {
            bindEventToState( eventNames[j], states[i] );
        }
    }
}() );


var mover = new Mover;

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

mover.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

# Set up each state to log its transitional events.
eventNames = ['depart', 'exit', 'enter', 'arrive']
for substate in Mover::state '**'
  for eventName in eventNames
    do ( substate, eventName ) -> substate.on eventName, ->
      console.log "#{ eventName } #{ substate.name }"


mover = new Mover

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

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

Mutability

State instances are nominally immutable by default. This restriction forces any changes in the owner’s State-based behavior to be expressed in terms of transitions between States.

Alternatively, a State may be explicitly expressed as mutable, such that modular pieces of behavior may be inserted and implemented dynamically into a live State, thereby allowing changes in behavior to be exhibited via mutations as well.

function Actor () {}
state( Actor.prototype, 'abstract', {
    Casual: state({
        greet: function () { return "Hi!"; }
    }),
    Formal: state( 'default', {
        greet: function () { return "How do you do?"; }
    })
});
class Actor
  state @::, 'abstract',
    Casual: state
      greet: -> "Hi!"
    Formal: state 'default',
      greet: -> "How do you do?"

A free bit of behavior can be expressed with just a plain object.

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

This factory produces a boxed function that will instill an enclosed behavior into a receiving State.

function doAs ( behavior ) {
    return state.bind( function () {
        this.mutate( behavior );
    });
}
doAs = ( behavior ) -> state.bind -> @mutate behavior

A traveler assimilates itself by overwriting previously defined behavior with the appropriately chosen new behavior.

function Traveler () {}
Traveler.prototype = Object.create( Actor );
Traveler.prototype.constructor = Traveler;
state( Traveler.prototype, 'mutable', {
    travelTo: state.bind( function ( place ) {
        this.emit( 'in' + place );
    }),
    events: {
        inRome: doAs( theRomansDo )
    }
});


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

traveler.travelTo('Rome');

traveler.greet();             // >>> "Quid agis?"
traveler.state('-> Casual');  // >>> State 'Casual'
traveler.greet();             // >>> "Salve!"
class Traveler extends Actor
  state @::, 'mutable',
    travelTo: state.bind ( place ) -> @emit "in#{ place }"
    events:
      inRome: doAs theRomansDo


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

traveler.travelTo 'Rome'

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

Transitions

A Transition instance is an ephemeral type of State that is automatically created and occupied as an owner’s current state while it is in the process of moving between States.

Transitions are defined by transition expressions, which are an optional component of a state expression. A transition may be defined generically across multiple states, is defined as either synchronous or asynchronous, and can be conditionally guarded.

function Container () {}
Container.prototype = Object.create( View );
Container.prototype.constructor = Container;

state( Container.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 Container extends View
  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()

Further reading

Articles

Documentation

Installation Getting started Overview Concepts

API reference

state() State Transition

Annotated source

View on GitHub