state()
The State module is exported as a function named state. This can be used either to build a state expression that declares the content for a State, or to apply a state expression to an object to give the object a working state implementation.
state( owner, attributes, expression )
- [
owner] : object - [
attributes] : string - [
expression] : ( object |StateExpression)
If an arbitrary owner object is provided, state() bestows owner with a new state implementation based on the supplied expression and attributes, and returns the owner’s initial State.
If no owner is provided, state() creates and returns a formal StateExpression based on the contents of expression and attributes.
Calling state with no arguments returns an empty StateExpression. Similarly, within an expression, a reference to state (rather than an invocation) implies the expression of an empty state as well.
var owner = {};
state( owner, 'abstract', {
aState: state( 'initial default', {
aMethod: function () {}
}),
anotherState: {
aSubstate: state
}
});
owner = {}
state owner, 'abstract'
aState: state 'initial default'
aMethod: ->
anotherState:
aSubstate: state
state.method
Facilitates the declaration of a lexical state method, a function bound to the lexical environment of a particular State. This is generally used to populate the methods category of a state expression.
state.method( bindings, fn )
- [
bindings] : object - [
fn] : function
Returns a factory function that will be called internally during the construction of a State instance. The method it produces is a transformation of the provided fn, closed over any provided bindings, along with additional bindings for a set of special variables:
autostate: The preciseStatethat contains the method. If the method is inherited via protostate, thenautostateis a protostate ofthis; otherwise,autostate === this.protostate: The protostate ofautostate.superstate: The superstate ofthis.owner: The owner ofthis.
If no fn is provided, then a function partially applied with bindings is returned, which accepts a fn and returns the factory.
var q = {}
state( q, {
A: {
inherited: function () {
return {
autostate: autostate,
protostate: protostate,
superstate: superstate,
owner: owner
};
}
}
});
var p = ( function () {
var question = null;
var answer = 42;
// Don’t call this ...
function plain ( param ) { // [1]
return {
question: question,
answer: answer,
param: param,
autostate: autostate,
protostate: protostate,
"this": this,
superstate: superstate,
owner: owner
};
}
var p = Object.create( q );
state( p, {
A: {
// ... call this
lexical: state.method({ // [2]
question: question,
answer: answer
})( plain ),
inherited: state.method(
{ q: question, a: answer },
function ( param ) {
var stuff;
var ok = true;
stuff = autostate.call( 'lexical', param );
ok && ( ok =
stuff.question === q &&
stuff.answer === a &&
stuff.param === param
);
stuff = protostate.call('inherited');
ok && ( ok =
stuff.autostate === protostate &&
stuff.protostate === undefined &&
stuff.superstate === superstate.protostate() &&
stuff.owner.isPrototypeOf( owner )
);
return ok;
}
)
}
});
return p;
}() );
var o = Object.create( p );
o.state('-> A');
o.lexical("foo");
// >>> { question: null,
// answer: 42,
// param: "foo",
// autostate: [State] <- 'A' of `Class.prototype`
// protostate: [State] <- 'A' of `Class.prototype`
// this: [State], <- 'A' of `o`
// superstate: [State], <- root state of `o`
// owner: [Class] <- `Class.prototype`
// }
o.inherited("bar");
// >>> true
class Superclass
state @::,
A:
inherited: ->
{ autostate, protostate, superstate, owner }
class Class
question = null
answer = 42
# Don’t call this ...
plain = ( param ) -> # [1]
return { question, answer, param, autostate, protostate,
this:this, superstate, owner }
state @::,
A:
# ... call this
lexical: state.method({ question, answer }) plain # [2]
inherited: state.method
q: question, a: answer
( param ) ->
ok = yes
stuff = autostate.call 'lexical', param
ok and =
stuff.question is q and
stuff.answer is a and
stuff.param is param
stuff = protostate.call 'inherited'
ok and =
stuff.autostate is protostate and
stuff.protostate is undefined and
stuff.superstate is superstate.protostate() and
stuff.owner.isPrototypeOf owner
o = new Class
o.state '-> A'
o.lexical "foo"
# >>> { question: null,
# answer: 42,
# param: "foo",
# autostate: [State] <- 'A' of `Class.prototype`
# protostate: [State] <- 'A' of `Class.prototype`
# this: [State], <- 'A' of `o`
# superstate: [State], <- root state of `o`
# owner: [Class] <- `Class.prototype`
# }
o.inherited "bar"
# >>> true
The
plainfunction would throw aReferenceErrorif invoked: references toquestionandanswerfrom the closure scope andparamin the local scope are valid, butautostate, etc. do not exist.The
lexicalmethod is the transformation ofplainin the state–lexical environment of stateAofClass.prototype. All of the state-related references from the body ofplainare now valid.
State
A State models a set of behaviors on behalf of an owner object. The owner may undergo transitions that change its current state from one to another, and in so doing adopt a different set of behaviors.
Distinct behaviors are modeled in each state by defining method overrides, to which calls made on the owner will be redirected so long as a state remains current.
States are structured as a rooted tree, where substates inherit from a single superstate. While a substate is current, it and all of its ancestor superstates are active.
In addition, a state also recognizes the owner object’s prototypal inheritance, identifying an identically named and positioned state in the prototype as its protostate. Stateful behavior is inherited from protostates first, then from superstates.
Attributes
State attributes are added to a state expression by preceding the expression argument of a call to state() with a space-delimited string argument that names the attributes to be applied.
state( obj, 'abstract', {
Alive: state( 'default initial mutable', {
update: function () { /*...*/ }
}),
Dead: state( 'final', {
update: function () { /*...*/ }
})
});
state obj, 'abstract'
Alive: state 'default initial mutable'
update: -> # ...
Dead: state 'final'
update: -> # ...
mutable
By default, states are weakly immutable — their data, methods, guards, substates, and transitions cannot be altered once the state has been constructed. Applying the mutable attribute lifts the restriction of immutability, exposing instance methods such as mutate, addMethod, addSubstate, etc., which can be used to alter the contents of the state.
function Mover () {}
state( Mover.prototype, {
Moving: state
});
Mover.prototype.state().mutate({
aMethod: function () {},
Stationary: state
});
Mover.prototype.state('Stationary'); // >>> undefined [1]
var mover = new Mover;
state( mover, 'mutable' );
mover.state().mutate({
aMethod: function () {},
Stationary: state
});
mover.state('-> Stationary');
mover.state(); // >>> State 'Stationary'
mover.state().isMutable(); // >>> true [2], [3]
mover.state('-> Moving');
mover.state(); // >>> State 'Moving'
mover.state().isMutable(); // >>> true [3]
class Mover
state @::
Moving: state
Mover::state().mutate
aMethod: ->
Stationary: state
Mover::state 'Stationary' # >>> undefined [1]
mover = new Mover
state mover, 'mutable'
mover.state().mutate
aMethod: ->
Stationary: state
mover.state '-> Stationary'
mover.state() # >>> State 'Stationary'
mover.state().isMutable() # >>> true [2], [3]
mover.state '-> Moving'
mover.state() # >>> State 'Moving'
mover.state().isMutable() # >>> true [3]
Without a
mutableattribute, the states ofMover.prototypeare weakly immutable, so their content cannot be altered, and the attempt to callmutatehas no effect.Because the
moverinstance’s root state was declaredmutable, content can be added later to that state or to any state that inherits from it.Substates, including virtual states and any state added later, automatically inherit the
mutableattribute assigned to their superstates.
The mutable attribute is inherited from both superstates and protostates, unless any also bear the immutable attribute.
See also:
isMutable,mutate(method),mutate(event)
finite
Declaring a state finite guarantees its hierarchical structure by hiding its addSubstate and removeSubstate methods after the state has been constructed.
function Mover () {}
state( Mover.prototype, 'finite', {
Stationary: state,
Moving: {
Walking: state
}
});
var mover = new Mover;
state( mover, {
Moving: {
Running: state('mutable')
}
});
mover.state('Moving').isFinite(); // >>> true [1]
mover.state('Running').isFinite(); // >>> true [1]
mover.state('Running').addSubstate('Sprinting'); // >>> undefined [2]
class Mover
state @::, 'finite'
Stationary: state
Moving:
Walking: state
mover = new Mover
state mover,
Moving:
Running: state 'mutable'
mover.state('Moving').isFinite() # >>> true [1]
mover.state('Running').isFinite() # >>> true [1]
mover.state('Running').addSubstate 'Sprinting' # >>> undefined [2]
The
moverinstance may define new states of its own, but its states will also inherit thefiniteattribute fromMover.prototype.Even though
mover’sRunningstate is declaredmutable, it is subject to the restrictions of its inheritedfiniteattribute, sincefiniteis more powerful thanmutable. Therefore an attempt to add a substate later will fail.
The finite attribute is inherited from both superstates and protostates, and is imposed with higher precedence than mutable.
See also:
isFinite
immutable
Adding immutable makes a state strongly immutable, whereupon immutability is permanent and absolute: immutable contradicts and overrules mutable, and implies finite, irrespective of whether any of the attributes are literal or inherited.
function Mover () {}
state( Mover.prototype, 'immutable', {
Moving: state
});
var mover = new Mover;
state( mover, 'mutable', { // [1]
Stationary: state // [2]
});
mover.state('').isMutable(); // >>> false [1]
mover.state('Stationary').isMutable(); // >>> false [2]
mover.state().addMethod( 'thisWontWork', function () {
// ...
}); // >>> undefined [3]
class Mover
state @::, 'immutable'
Moving: state
mover = new Mover
state mover, 'mutable' # [1]
Stationary: state # [2]
mover.state('').isMutable() # >>> false [1]
mover.state('Stationary').isMutable() # >>> false [2]
mover.state().addMethod 'thisWontWork', -> # >>> undefined [3]
The
moverinstance was declaredmutable, but its prototype isimmutable, so applyingmutableon the inheriting root state here is superfluous:mutableis negated andimmutableis inherited.The call to
statedoes allowmoverto extend its prototype’simmutablestate, although the newStationarystate will also inheritimmutable.Attempts to alter the state’s contents after it has been constructed will have no effect.
The immutable attribute is inherited from both superstates and protostates, and has top precedence over mutable and finite.
An inheriting owner object may still extend the state implementation of its prototype with states that are new or extend protostates, but any of these that inherit from an immutable state will also bear the immutable attribute themselves.
See also:
isImmutable
abstract
A state that is abstract cannot itself be current. Consequently a transition that targets an abstract state will be forcibly redirected to the appropriate concrete descendant of the abstract state.
The redirection target of an abstract state is determined by seeking its first substate marked default. If no default substate exists, the first substate is targeted. If the redirection target is itself abstract, then the process is repeated until a concrete descendant is found. If an abstract state has no concrete descendants, currency is directed as deep as possible via the first substate at each level.
The abstract attribute is inherited from protostates.
function Mover () {}
state( Mover.prototype, 'abstract', {
Moving: state( 'abstract', {
Walking: state,
Running: state
})
});
var mover = new Mover;
mover.state('->');
mover.state(); // >>> State 'Walking'
mover.state('-> Moving');
mover.state(); // >>> State 'Walking'
class Mover
state @::, 'abstract'
Moving: state 'abstract'
Walking: state
Running: state
mover = new Mover
mover.state '->'
mover.state() # >>> State 'Walking'
mover.state '-> Moving'
mover.state() # >>> State 'Walking'
See also:
isAbstract
concrete
Including the concrete attribute will override the abstract attribute that would otherwise have been inherited from an abstract protostate.
Any state that is not abstract is by definition concrete, even if not literally attributed as such, and will return true in a call to isConcrete.
The concrete attribute is inherited from protostates.
function Mover () {}
state( Mover.prototype, 'abstract', {
Moving: state( 'abstract', {
Walking: state
})
});
var mover = new Mover;
state( mover, {
Moving: state('concrete')
});
mover.state('-> Moving');
mover.state(); // >>> State 'Moving'
class Mover
state @::, 'abstract'
Moving: state 'abstract'
Walking: state
mover = new Mover
state mover,
Moving: state 'concrete'
mover.state '-> Moving'
mover.state() # >>> State 'Moving'
See also:
isConcrete
default
Marking a state default designates it as the intended redirection target for any transition that has targeted its abstract superstate.
The default attribute is inherited from protostates.
function Mover () {}
state( Mover.prototype, 'abstract', {
Stationary: state,
Moving: state( 'default abstract', {
Walking: state,
Running: state('default')
})
});
var mover = new Mover;
mover.state('->'); // >>> State 'Running'
mover.state('-> Moving'); // >>> State 'Running'
class Mover
state @::, 'abstract'
Stationary: state
Moving: state 'default abstract'
Walking: state
Running: state 'default'
mover = new Mover
mover.state '->'
mover.state() # >>> State 'Running'
mover.state '-> Moving'
mover.state() # >>> State 'Running'
See also:
isDefault,defaultSubstate
initial
Marking a state initial specifies which state is to be assumed immediately following the state() application. No transition or any enter or arrive events result from this initialization.
For an object that inherits its entire state implementation from its prototype, the inheritor’s initial state will be set to the prototype’s current state.
The initial attribute is inherited from protostates.
function Mover () {}
state( Mover.prototype, {
Stationary: state('initial'),
Moving: state
});
var mover1 = new Mover;
mover1.state(); // >>> State 'Stationary'
Mover.prototype.state('-> Moving');
var mover2 = new Mover;
mover2.state(); // >>> State 'Moving'
var mover3 = new Mover;
state( mover3, {
Stationary: state,
Moving: {
Walking: state,
Running: state('initial')
}
}); // >>> State 'Running'
class Mover
state @::
Stationary: state 'initial'
Moving: state
mover1 = new Mover
mover1.state() # >>> State 'Stationary'
Mover::state '-> Moving'
mover2 = new Mover
mover2.state() # >>> State 'Moving'
mover3 = new Mover
state mover3,
Stationary: state
Moving:
Walking: state
Running: state 'initial'
mover3.state() # >>> State 'Running'
See also:
isInitial,initialSubstate
conclusive
Once a conclusive state is entered, it cannot be exited, although transitions may still freely traverse within its substates.
The conclusive attribute is inherited from protostates.
function Mover () {}
state( Mover.prototype, {
Stationary: state,
Moving: state( 'conclusive', {
Walking: state,
Running: state
})
});
var mover = new Mover;
mover.state('-> Stationary');
mover.state(); // >>> State 'Stationary'
mover.state('-> Walking');
mover.state(); // >>> State 'Walking'
mover.state('-> Stationary');
mover.state(); // >>> State 'Walking'
mover.state('-> Running');
mover.state(); // >>> State 'Running'
class Mover
state @::
Stationary: state
Moving: state 'conclusive'
Walking: state
Running: state
mover = new Mover
mover.state '-> Stationary'
mover.state() # >>> State 'Stationary'
mover.state '-> Walking'
mover.state() # >>> State 'Walking'
mover.state '-> Stationary'
mover.state() # >>> State 'Walking'
mover.state '-> Running'
mover.state() # >>> State 'Running'
See also:
isConclusive
final
Once an object’s currency arrives at a final state, no further transitions are allowed.
A final state is not necessarily conclusive: a transition may enter a final state on its way to a descendant, and still exit from it later, so long as it never arrives at the final state.
The final attribute is inherited from protostates.
function Mover () {}
state( Mover.prototype, {
Stationary: state,
Moving: state( 'final', {
Walking: state
})
});
var mover = new Mover;
mover.state('-> Walking');
mover.state(); // >>> State 'Walking'
mover.state('-> Stationary');
mover.state(); // >>> State 'Stationary'
mover.state('-> Moving');
mover.state(); // >>> State 'Moving'
mover.state('-> Walking');
mover.state(); // >>> State 'Moving'
mover.state('-> Stationary');
mover.state(); // >>> State 'Moving'
class Mover
state @::
Stationary: state
Moving: state 'final'
Walking: state
mover = new Mover
mover.state '-> Walking'
mover.state() # >>> State 'Walking'
mover.state '-> Stationary'
mover.state() # >>> State 'Stationary'
mover.state '-> Moving'
mover.state() # >>> State 'Moving'
mover.state '-> Walking'
mover.state() # >>> State 'Moving'
mover.state '-> Stationary'
mover.state() # >>> State 'Moving'
See also:
isFinal
Events
construct
function ( expression ) {}
( expression ) ->
expression: (StateExpression| object )
Immediately after a State instance has been fully constructed, it emits a construct event.
Listeners receive the expression object from which the state was constructed.
Since construction is not complete until the state’s substates have themselves been constructed, the full construct event sequence of a state tree proceeds bottom-up.
var mover = {};
state( mover, {
construct: function ( expression ) {
console.log( "root state constructed" );
},
Moving: {
construct: function ( expression ) {
console.log( "State '" + this.name() + "' constructed" );
},
Walking: {
construct: function ( expression ) {
console.log(
"State '" + this.name() + "' constructed" );
}
}
}
});
// log <<< State 'Walking' constructed
// log <<< State 'Moving' constructed
// log <<< root state constructed
mover = {}
state mover,
construct: ( expression ) ->
console.log "root state constructed"
Moving:
construct: ( expression ) ->
console.log "State '#{ @name() }' constructed"
Walking:
construct: ( expression ) ->
console.log "State '#{ @name() }' constructed"
# log <<< State 'Walking' constructed
# log <<< State 'Moving' constructed
# log <<< root state constructed
destroy
function () {}
() ->
A state is properly deallocated with a call to the destroy method of either itself or a superstate. The destroy event is emitted immediately prior to the state and its contents being cleared.
Listeners of destroy are called with no arguments.
depart
function ( transition ) {}
( transition ) ->
transition:Transition
At the beginning of a transition, exactly one depart event is always emitted by the state from which the transition originates.
Listeners receive a reference to the involved transition.
exit
function ( transition ) {}
( transition ) ->
transition:Transition
During the ascending phase of a transition, an exit event is emitted by the origin state and any of its superstates that will no longer be active as a result of the transition.
Listeners receive a reference to the involved transition.
enter
function ( transition ) {}
( transition ) ->
transition:Transition
During the descending phase of a transition, an enter event is emitted by each state that will become newly active, including the target state.
Listeners receive a reference to the involved transition.
arrive
function ( transition ) {}
( transition ) ->
transition:Transition
At the end of a transition, exactly one arrive event is always emitted by the transition’s target state.
Listeners receive a reference to the involved transition.
mutate
function ( mutation, delta, before, after ) {}
( mutation, delta, before, after ) ->
mutation: objectdelta: objectbefore: objectafter: object
When a state’s contents are altered, it emits a mutate event containing the changes made relative to its immediately prior condition.
Listeners receive the contents of the mutation experienced by the state, the delta containing the contents displaced by the mutation, and a full expression of the state’s contents both before and after the mutation.
noSuchMethod
function ( methodName, args ) {}
( methodName, args ) ->
methodName: stringargs:Array
When a method is called on an object for which no implementation exists given its current state, a noSuchMethod event is emitted.
Listeners receive the methodName of the method that was called, and an args array of the arguments that were passed to the call.
noSuchMethod:name
function ( arg0, arg1, ... ) {}
( args... ) ->
argN:var
A generic noSuchMethod event is immediately followed by the emission of a specific noSuchMethod:name event, where name specifies the method that was called.
Listeners receive the arguments as they were passed to the call.
Methods
name
this.name()
Returns the string name of this state.
var mover = {};
state( mover, {
Moving: {
Running: state('initial')
}
});
mover.state().name(); // >>> "Running"
mover.state().path(); // >>> "Moving.Running"
mover = {}
state mover,
Moving:
Running: state 'initial'
mover.state().name() # >>> "Running"
mover.state().path() # >>> "Moving.Running"
See also: path
express
this.express( typed )
- [
typed = false] : boolean
Returns an object containing an expression of the contents of this state, such as would be sufficient to create a new State structurally identical to this. If typed is true, a formal StateExpression object is returned.
var mover = {};
state( mover, 'mutable abstract', {
Stationary: state( 'initial default', {
move: function () { return "!"; }
}),
Moving: {
Walking: {
move: function () { return "step step"; }
},
Running: {
move: function () { return "boing boing"; },
Sprinting: state
}
}
});
// Add a 'report' method to each of the states.
O.forEach( mover.state('').substates( true ), function ( substate ) {
substate.addMethod( 'report', function () {
console.log( "I'm in state '" + this.name() + "'" );
});
});
mover.report(); // log <<< "I'm in state 'Stationary'"
// Express the root state.
var expression = mover.state('').express(); // >>> Object
// Use `expression` to clone the state implementation of `mover` into
// some other unrelated object.
var other = {};
state( other, expression );
other.report(); // log <<< "I'm in state 'Stationary'"
mover = {}
state mover, 'mutable abstract'
Stationary: state 'initial default'
move: -> "!"
Moving:
Walking:
move: -> "step step"
Running:
move: -> "boing boing"
Sprinting: state
# Add a 'report' method to each of the states.
for substate in mover.state('').substates true
substate.addMethod 'report', ->
console.log "I'm in state '#{ @name() }'"
mover.report() # log <<< "I'm in state 'Stationary'"
# Express the root state.
expression = mover.state('').express() # >>> Object
# Use `expression` to clone the state implementation of `mover` into
# some other unrelated object.
other = {}
state other, expression
other.report() # log <<< "I'm in state 'Stationary'"
mutate
this.mutate( expression )
expression: (StateExpression| object )
Transactionally mutates this state by adding, updating, or removing items as implied by the contents of expression. Removal is indicated with the unique O.NIL reference.
Returns this.
If the transaction causes a mutation, this emits a mutate event.
var NIL = O.NIL;
var mover = {};
state( mover, 'mutable', {
mutate: function () {
console.log( "I feel different" );
},
Stationary: state,
Evil: state
});
// Update `Stationary`, create `Moving`, and delete `Evil`
mover.state('').mutate({
Stationary: {
move: function () { return "!"; }
},
Moving: {
Walking: state,
Running: state
},
Evil: NIL
});
// log <<< "I feel different"
{NIL} = O
mover = {}
state mover, 'mutable'
mutate: -> console.log "I feel different"
Stationary: state
Evil: state
# Update `Stationary`, create `Moving`, and delete `Evil`
mover.state('').mutate
Stationary: move: -> "!"
Moving:
Walking: state
Running: state
Evil: NIL
# log <<< "I feel different"
realize
this.realize()
Transforms this virtual state into a “real” state that can bear content of its own. If this is already real instead of virtual, then calling realize has no effect.
Returns this.
function Mover () {}
state( Mover.prototype, 'mutable', {
Moving: {
Running: state
}
});
var mover = new Mover;
mover.state('-> Moving');
var s = mover.state(); // >>> State 'Moving'
s.isVirtual(); // >>> true
s.realize();
s.isVirtual(); // >>> false
class Mover
state @::, 'mutable'
Moving:
Running: state
mover = new Mover
mover.state '-> Moving'
s = mover.state() # >>> State 'Moving'
s.isVirtual() # >>> true
s.realize()
s.isVirtual() # >>> false
If this is both virtual and mutable, then calling any of its add... methods necessarily uses realize to transform into a real state before content is added.
mover.state('-> Running');
s = mover.state(); // >>> State 'Running'
s.isVirtual(); // >>> true
s.addMethod( 'move', function () { return "Boing"; } );
s.isVirtual(); // >>> false
mover.state '-> Running'
s = mover.state() # >>> State 'Running'
s.isVirtual() # >>> true
s.addMethod 'move', -> "Boing"
s.isVirtual() # >>> false
destroy
this.destroy()
Attempts to cleanly destroy this state and all of its descendant states. A destroy event is issued by each state as it is destroyed.
Returns true if this state is successfully destroyed, or false otherwise.
If the root state is destroyed, the owner is given back any methods it bore prior to its state implementation.
Methods in this section are used to inspect the State object model.
owner
this.owner()
Returns the object that is this state’s owner.
function Mover () {}
state( Mover.prototype, {
Moving: {
getState: function () { return this; },
getOwner: function () { return this.owner(); }
}
});
var mover = new Mover;
mover.state('-> Moving');
mover.getState() === mover.state('Moving'); // >>> true
mover.getOwner() === mover // >>> true
class Mover
state @::
Moving:
getState: -> this
getOwner: -> @owner()
mover = new Mover
mover.state '-> Moving'
mover.getState() is mover.state('Moving') # >>> true
mover.getOwner() is mover # >>> true
root
this.root()
Returns the State that is this state’s root state, i.e., the top-level superstate of this state.
var mover = {};
state( mover, {
Moving: {
Running: state
}
});
mover.state().root(); // >>> State ''
mover.state('').root(); // >>> State ''
mover.state('Moving').root(); // >>> State ''
mover = {}
state mover
Moving:
Running: state
mover.state().root() # >>> State ''
mover.state('').root() # >>> State ''
mover.state('Moving').root() # >>> State ''
superstate
this.superstate()
Returns the State that is this state’s superstate.
var mover = {};
state( mover, {
Moving: {
Running: state
}
});
mover.state('Running').superstate(); // >>> State 'Moving'
mover.state('Moving').superstate(); // >>> State ''
mover.state('').superstate(); // >>> undefined
mover = {}
state mover
Moving:
Running: state
mover.state('Running').superstate() # >>> State 'Moving'
mover.state('Moving').superstate() # >>> State ''
mover.state('').superstate() # >>> undefined
derivation
this.derivation( byName )
- [
byName = false] : boolean
Returns an Array containing each State from the root state to this state, starting with the immediate substate from the root.
If byName is true, the returned Array contains the string names of each state, rather than the States themselves.
var mover = {};
state( mover, {
Moving: {
Running: {
Sprinting: state
}
}
});
var s = mover.state('Sprinting')
s.derivation();
// >>> [ State 'Moving', State 'Running', State 'Sprinting' ]
s.derivation( true );
// >>> [ "Moving", "Running", "Sprinting" ]
mover = {}
state mover
Moving:
Running:
Sprinting: state
s = mover.state 'Sprinting'
s.derivation()
# >>> [ State 'Moving', State 'Running', State 'Sprinting' ]
s.derivation true
# >>> [ "Moving", "Running", "Sprinting" ]
path
this.path()
Returns a string that is the absolute selector referencing this state.
var mover = {};
state( mover, {
Moving: {
Running: {
Sprinting: state
}
}
});
mover.state('Sprinting').path(); // >>> "Moving.Running.Sprinting"
mover = {}
state mover
Moving:
Running:
Sprinting: state
mover.state('Sprinting').path() # >>> "Moving.Running.Sprinting"
depth
this.depth()
Returns the number of superstates separating this state from its root state.
var mover = {};
state( mover, {
Moving: {
Running: state
}
});
mover.state('Running').depth(); // >>> 2
mover.state('').depth(); // >>> 0
mover = {}
state mover
Moving:
Running: state
mover.state('Running').depth() # >>> 2
mover.state('').depth() # >>> 0
common
this.common( other )
other: (State| string )
Returns the State that is the nearest common ancestor of both this state and the provided other state.
var mover = {};
state( mover, {
Stationary: state
Moving: {
Walking: state,
Running: {
Sprinting: state
}
}
});
mover.state('Sprinting').common('Walking'); // >>> State 'Moving'
mover = {}
state mover,
Stationary: state
Moving:
Walking: state
Running:
Sprinting: state
mover.state('Sprinting').common 'Walking' # >>> State 'Moving'
is
this.is( other )
other: (State| string )
Returns a boolean indicating whether this state is the provided other state.
var mover = {};
state( mover, {
Moving: {
Running: {
Sprinting: state
}
}
});
var s = mover.state('Sprinting'); // >>> State 'Sprinting'
s.is('Moving.Running.Sprinting'); // >>> true
mover = {}
state mover
Moving:
Running:
Sprinting: state
s = mover.state 'Sprinting' # >>> State 'Sprinting'
s.is 'Moving.Running.Sprinting' # >>> true
isIn
this.isIn( other )
other: (State| string )
Returns a boolean indicating whether this state is or is a substate of the provided other state.
var mover = {};
state( mover, {
Moving: {
Running: {
Sprinting: state
}
}
});
var s = mover.state('Sprinting'); // >>> State 'Sprinting'
s.isIn('Running'); // >>> true
mover = {}
state mover
Moving:
Running:
Sprinting: state
s = mover.state 'Sprinting' # >>> State 'Sprinting'
s.isIn 'Running' # >>> true
hasSubstate
this.hasSubstate( other )
other: (State| string )
Returns a boolean indicating whether this state is or is a superstate of the provided other state.
var mover = {};
state( mover, {
Moving: {
Running: {
Sprinting: state
}
}
});
var s = mover.state('Moving'); // >>> State 'Moving'
s.has('Sprinting'); // >>> true
s.has('Moving'); // >>> true
mover = {}
state mover
Moving:
Running:
Sprinting: state
s = mover.state 'Moving' # >>> State 'Moving'
s.has 'Sprinting' # >>> true
s.has 'Moving' # >>> true
isSuperstateOf
this.isSuperstateOf( other )
other: (State| string )
Returns a boolean indicating whether this state is a superstate of the provided other state.
var mover = {};
state( mover, {
Moving: {
Running: {
Sprinting: state
}
}
});
var s = mover.state('Moving'); // State 'Moving'
s.isSuperstateOf('Sprinting'); // >>> true
s.isSuperstateOf('Moving'); // >>> false
mover = {}
state mover
Moving:
Running:
Sprinting: state
s = mover.state 'Moving' # >>> State 'Moving'
s.isSuperstateOf 'Sprinting' # >>> true
s.isSuperstateOf 'Moving' # >>> false
See also: superstate
protostate
this.protostate()
Returns a State that is this state’s protostate: the state analogous to this within the state tree of the nearest prototype of this.owner() in which such a state exists.
Returns undefined if no protostate exists anywhere in the owner’s prototype chain.
function Mover () {}
state( Mover.prototype, {
Stationary: state,
Moving: {
Walking: state,
Running: {
Sprinting: state('initial')
}
}
});
var mover, protostate, epistate;
mover = new Mover;
protostate = Mover.prototype.state(); // >>> State 'Sprinting'
epistate = mover.state(); // >>> State 'Sprinting'
protostate === epistate; // >>> false
protostate === epistate.protostate(); // >>> true
class Mover
state @::
Stationary: state
Moving:
Walking: state
Running:
Sprinting: state 'initial'
mover = new Mover
protostate = Mover::state() # >>> State 'Sprinting'
epistate = mover.state() # >>> State 'Sprinting'
protostate is epistate # >>> false
protostate is epistate.protostate() # >>> true
isProtostateOf
this.isProtostateOf( other )
other: (State| string )
Returns a boolean indicating whether this state is a protostate of the provided other state.
function Animal () {}
state( Animal.prototype, 'abstract', {
Alive: state('default'),
Dead: state
});
O.inherit( Bird, Animal );
function Bird () {}
var canary, a, b, c;
canary = new Bird; // >>> Bird
a = Animal.prototype.state(); // >>> State 'Alive'
b = Bird.prototype.state(); // >>> State 'Alive'
c = canary.state(); // >>> State 'Alive'
a.isProtostateOf( c ); // >>> true
b.isProtostateOf( c ); // >>> true [1]
a === b; // >>> false [1]
canary.state('-> Dead');
canary.state(); // >>> State 'Dead'
a = Animal.prototype.state('Dead'); // >>> State 'Dead'
b = Bird.prototype.state('Dead'); // >>> State 'Dead'
c = canary.state(); // >>> State 'Dead'
a.isProtostateOf( c ); // >>> true
b.isProtostateOf( c ); // >>> false [2]
a === b; // >>> true [2]
class Animal
state @::, 'abstract'
Alive: state 'default'
Dead: state
class Bird extends Animal
canary = new Bird # >>> Bird
a = Animal::state() # >>> State 'Alive'
b = Bird::state() # >>> State 'Alive'
c = canary.state() # >>> State 'Alive'
a.isProtostateOf c # >>> true
b.isProtostateOf c # >>> true [1]
a is b # >>> false [1]
canary.state '-> Dead'
canary.state() # >>> State 'Dead'
a = Animal::state 'Dead' # >>> State 'Dead'
b = Bird::state 'Dead' # >>> State 'Dead'
c = canary.state() # >>> State 'Dead'
a.isProtostateOf c # >>> true
b.isProtostateOf c # >>> false [2]
a is b # >>> true [2]
See also: protostate
defaultSubstate
this.defaultSubstate( viaProto )
- [
viaProto = true] : boolean
Returns the State that is this state’s first substate bearing the default attribute, or just the first substate if none are found.
var mover = {};
state( mover, 'abstract', {
Stationary: state,
Moving: state( 'default abstract', {
Walking: state,
Running: {
Sprinting: state('initial')
}
})
});
mover.state('').defaultSubstate(); // >>> State 'Moving' [1]
mover.state('Moving').defaultSubstate(); // >>> State 'Walking' [2]
mover.state(); // >>> State 'Sprinting'
mover.state('->');
mover.state(); // >>> State 'Walking' [3]
mover = {}
state mover, 'abstract'
Stationary: state
Moving: state 'default abstract'
Walking: state
Running:
Sprinting: state 'initial'
mover.state('').defaultSubstate() # >>> State 'Moving' [1]
mover.state('Moving').defaultSubstate() # >>> State 'Walking' [2]
mover.state() # >>> State 'Sprinting'
mover.state '->'
mover.state() # >>> State 'Walking' [3]
Movingis explicitly markeddefault.Since
Moving, which is itself abstract, has no descendant states markeddefault, its first substateWalkingserves as its default state.A transition targeting the root state will fall through to
Walking, since both the root and its default stateMovingare abstract.
initialSubstate
this.initialSubstate()
Returns the State that is this state’s most deeply nested state bearing the initial attribute, by way of its greatest initial descendant state.
query
Alias: match
this.query( selector, against, descend, ascend, viaProto )
selector: string- [
against] :State - [
descend = true] : boolean - [
ascend = true] : boolean - [
viaProto = true] : boolean
Matches a selector string with the state or states it represents in the context of this state. If no match exists in this context, the selector is reevaluated in the context of all its substates and descendant states, and if necessary in the context of its superstates and all of their descendants, until all possible locations in the state tree have been exhausted.
Returns the matched State, or an Array containing the set of matched states. If a state to be tested against is provided, then a boolean is returned, indicating whether against is the matched state itself or is included in the matching set.
Setting descend to false disables recursion through the substates of this, and likewise setting ascend to false disables the subsequent recursion through its superstates.
Calling an owner object’s accessor method with a selector string invokes query on the owner’s current state.
$
this.$( selector )
selector: string
Convenience method that mimics the behavior of the owner’s accessor method. If the first argument is a transition arrow selector string, the call is aliased to change. If passed a plain selector string, the call is aliased to query.
this.$('-> Awake')
@$ '-> Awake'
Aliases to change, instigating a transition to the Awake state.
this.$('Awake')
@$ 'Awake'
Aliases to query, returning the State named 'Awake'.
The currency methods in this section determine or decide which of an owner object’s States are presently current or active, and thus presently influence the behavior exhibited by the owner.
current
this.current()
Returns the current State of this state’s owner.
isCurrent
this.isCurrent()
Returns a boolean indicating whether this state is the owner’s current state.
isActive
this.isActive()
Returns a boolean indicating whether this state or one of its substates is the owner’s current state.
change
Aliases: go, be
this.change( target, options )
target: (State| string )- [
options] : object
Attempts to execute a state transition. Handles asynchronous transitions, generation of appropriate events, and construction of any necessary temporary virtual states. Respects guards supplied in both the origin and target states.
The target parameter may be either a State object within the purview of this controller, or a string that resolves to a likewise targetable State when evaluated from the context of the most recently current state.
The options parameter is an optional map that may include:
args:Array— arguments to be passed to a transition’sactionfunction.success: function — callback to be executed upon successful completion of the transition.failure: function — callback to be executed if the transition attempt is blocked by a guard.
The attribute methods in this section are predicates that inspect the attributes that have been affixed to a State.
isVirtual
this.isVirtual()
Returns a boolean indicating whether this state bears the virtual attribute.
A virtual state is a lightweight inheritor of a protostate located higher in the owner object’s prototype chain. Notably, as virtual states are created automatically, no modifier keyword exists for the virtual attribute.
function Mover () {}
state( Mover.prototype, {
Stationary: state('initial'),
Moving: state
});
var mover = new Mover;
mover.state(); // >>> State 'Stationary'
mover.state().isVirtual(); // >>> true [1]
mover.state('->');
mover.state(); // >>> State ''
mover.state().isVirtual(); // >>> false [2]
class Mover
state @::
Stationary: state 'initial'
Moving: state
var mover = new Mover
mover.state() # >>> State 'Stationary'
mover.state().isVirtual() # >>> true [1]
mover.state '->'
mover.state() # >>> State ''
mover.state().isVirtual() # >>> false [2]
The
moverinstance, which inherits its stateful implementation fromMover.prototype, does not have a realStationarystate of its own, so a virtualStationarystate is created automatically and adopted asmover’s initial state.Root states are never virtualized. Even an object that inherits all statefulness from its prototypes is given a real root state.
isMutable
this.isMutable()
Returns a boolean indicating whether this state bears the mutable attribute.
By default, states are weakly immutable; i.e., once a State has been constructed, its declared data, methods, guards, substates, and transitions cannot be altered. By including the mutable attribute in the state’s expression, this restriction is lifted. Mutability is also inherited from any of a state’s superstates or protostates.
See also:
mutable
isFinite
this.isFinite()
Returns a boolean indicating whether this state bears the finite attribute.
If a state is declared finite, no substates or descendant states may be added, nor may any be removed without also destroying the state itself.
See also:
finite
isImmutable
this.isImmutable()
Returns a boolean indicating whether this state bears the immutable attribute.
A literal or inherited immutable attribute causes a state to become strongly immutable, wherein it guarantees immutability absolutely, throughout all inheriting states. The immutable attribute also implies finite, and contradicts and overrides any literal or inherited mutable attribute.
See also:
immutable
isAbstract
this.isAbstract()
Returns a boolean indicating whether this state is abstract.
An abstract state is used only as a source of inheritance, and cannot itself be current. A transition that directly targets an abstract state will be automatically redirected to one of its substates.
See also:
abstract
isConcrete
this.isConcrete()
Returns a boolean indicating whether this state is concrete.
All non-abstract states are concrete. Marking a state with the concrete attribute in a state expression will override any abstract attribute, particularly such as would otherwise be inherited from a protostate.
See also:
concrete
isDefault
this.isDefault()
Returns a boolean indicating whether this state bears the default attribute.
Marking a state default designates it as the specific redirection target for any transition that targets its abstract superstate.
See also:
default,defaultSubstate
isInitial
this.isInitial()
Returns a boolean indicating whether this state bears the initial attribute.
Marking a state initial specifies which state a newly stateful object should assume.
Objects inheriting from a stateful prototype will have their initial state set to the prototype’s current state.
See also:
initial,initialSubstate
isConclusive
this.isConclusive()
Returns a boolean indicating whether this state bears the conclusive attribute.
Once a state marked conclusive is entered, it cannot be exited, although transitions may still freely traverse within its substates.
See also:
conclusive
isFinal
this.isFinal()
Returns a boolean indicating whether this state bears the final attribute.
Once a state marked final is entered, no further outbound transitions within its local region are allowed.
See also:
final
The methods below allow states to store and manipulate arbitrary data.
data
this.data( viaSuper, viaProto )
- [
viaSuper = true] : boolean - [
viaProto = true] : boolean
Returns an object clone of the data attached to this state, including any data inherited from protostates and superstates, unless specified otherwise by the inheritance flags viaSuper and viaProto.
this.data( edit )
edit: object
Adds, updates, and/or removes data properties on this state, and returns this.
For any keys in edit whose values are set to the O.NIL directive, the matching properties are deleted from this state’s data.
If the operation results in a change to this state’s data, a mutate event is emitted.
has
this.has( key, viaSuper, viaProto )
key: string- [
viaSuper = true] : boolean - [
viaProto = true] : boolean
Predicate that determines whether a data property with the given key exists on this state, or is inherited from a protostate or superstate. Supports long.key lookups for deeply nested properties.
get
this.get( key, viaSuper, viaProto )
key: string- [
viaSuper = true] : boolean - [
viaProto = true] : boolean
Returns the value of the data property with the given key on this state, or one inherited from the nearest protostate, or the nearest superstate. Supports long.key lookups for deeply nested properties. Returns undefined if key cannot be resolved.
let
this.let( key, value )
key: stringvalue: var
Creates a new data property or updates an existing data property on this state, and returns the assigned value. Succeeds only if this state is mutable. Supports long.key assignments to deeply nested properties.
set
this.set( key, value )
key: stringvalue: var
Updates an existing data property and returns the assigned value. If the property is inherited from a mutable superstate, then the property is updated in place, equivalent to calling let on that superstate. If the data property does not yet exist in the superstate chain, it is created on this. Properties inherited from protostates are not affected. Supports long.key assignments to deeply nested properties.
delete
this.delete( key )
key: string
Deletes an existing data property on this state. Returns boolean true if the deletion was successful or unnecessary, or false otherwise, in the same manner as the native delete operator. Supports long.key lookups for deeply nested properties.
The methods in this section deal with adding, removing, inspecting, and the application of state methods.
method
this.method( methodName, viaSuper, viaProto, out )
methodName: string- [
viaSuper = true] : boolean - [
viaProto = true] : boolean - [
out] : object
Returns the function that is the method held on this state whose name is methodName.
If the named method does not exist on this state, then it will be inherited, in order, first from protostates of this (unless viaProto is false), and if no such method exists there, then from superstates of this (unless viaSuper is false).
If an out object is supplied, then the returned function is attached to out.method, and the State context to which the method will be bound when invoked with this.apply or this.call is attached to out.context.
methodNames
this.methodNames()
Returns an Array of names of methods defined locally on this state.
addMethod
this.addMethod( methodName, fn )
methodName: stringfn: function
Adds fn as a method named methodName to this state, which will be callable directly from the owner, but with its context bound to this.
Returns fn.
removeMethod
this.removeMethod( methodName )
methodName: string
Dissociates the method named methodName from this state and returns its function.
hasMethod
this.hasMethod( methodName )
methodName: string
Returns a boolean indicating whether this state possesses or inherits a method named methodName.
hasOwnMethod
this.hasOwnMethod( methodName )
methodName: string
Returns a boolean indicating whether this state directly possesses a method named methodName.
apply
this.apply( methodName, args )
methodName: string- [
args] :Array
Finds the state method named by methodName, applies it with the provided args in the appropriate context, and returns its result.
If the method was originally defined in the owner, the context will be the owner. Otherwise, the context will either be the precise State in which the method is defined, or if the method resides in a protostate, the corresponding State belonging to the inheriting owner.
If the named method does not exist locally and cannot be inherited, a noSuchMethod event is emitted and the call returns undefined.
call
this.call( methodName, args... )
methodName: string- [
args...] : individual arguments
The variadic companion to apply.
The event methods in this section are an implementation of the common event emitter pattern.
See also: Events
event
this.event( eventType, id )
eventType: string- [
id] : ( string | number | function )
Returns a registered event listener, or the number of listeners registered, for a given eventType.
If an id as returned by addEvent is provided, the event listener associated with that id is returned. If no id is provided, the number of event listeners registered to eventType is returned.
addEvent
Aliases: on, bind
this.addEvent( eventType, fn, context )
eventType: stringfn: function- [
context = this] : object
Binds an event listener fn to the specified eventType and returns a unique identifier for the listener.
removeEvent
Aliases: off, unbind
this.removeEvent( eventType, id )
eventType: string- [
id] : ( string | number | function )
Unbinds the event listener with the specified id that was supplied by addEvent.
emit
Aliases: trigger
this.emit( eventType, args, context, viaSuper, viaProto )
eventType: string- [
args = []] :Array - [
context = this] : object - [
viaSuper = true] : boolean - [
viaProto = true] : boolean
Invokes all listeners bound to the given eventType.
Arguments for the listeners can be passed as an array to the args parameter.
Listeners are invoked in the context of this state, or as specified by context.
Listeners bound to superstates and protostates of this are also invoked, unless otherwise directed by setting viaSuper or viaProto to false.
guard
this.guard( guardType )
guardType: string
Returns an object containing the guard predicates and/or expressions for the specified guardType held on this state.
A guard is a map of functions or values that will be evaluated as either a predicate or boolean expression, respectively, to provide a determination of whether the owner’s currency will be admitted into or released from the state to which the guard is applied.
Valid guardTypes include admit and release.
Guards are inherited from protostates, but not from superstates.
addGuard
this.addGuard( guardType, guard )
guardType: stringguard: object
Adds a guard to this state, or augments an existing guard with additional entries.
removeGuard
this.removeGuard( guardType, keys )
guardType: string- [
keys] : (Array| string )
Removes a guard from this state, or removes specific entries from an existing guard.
substate
this.substate( stateName, viaProto )
stateName: string- [
viaProto = true] : boolean
Returns the substate of this state named stateName. If no such substate exists locally within this, and viaProto is true, then the nearest identically named substate held on a protostate will be returned.
substates
this.substates( deep, virtual )
- [
deep = false] : boolean - [
virtual = false] : boolean
Returns an Array of this state’s substates.
If deep is true, the returned array is a depth-first flattened list of all of this state’s descendant states.
If virtual is true, the returned array may include any active virtual states held by an owner object that is inheriting currency from a prototype.
addSubstate
this.addSubstate( stateName, stateExpression )
stateName: stringstateExpression: (StateExpression| object |State)
Creates a State based on the provided stateExpression, adds it as a substate of this state, and returns the new State.
If a substate with the same stateName already exists, it is first destroyed and then replaced.
removeSubstate
this.removeSubstate( stateName )
stateName: string
Removes the substate named by stateName from this state, if possible, and returns the removed State.
If the owner object is in the midst of a transition involving the state targeted for removal, then the removal will fail, returning false.
transition
this.transition( transitionName )
transitionName: string
Returns the transition expression named by transitionName registered to this state.
transitions
this.transitions()
Returns an object containing all of the transition expressions registered to this state.
addTransition
this.addTransition( transitionName, transitionExpression )
transitionName: stringtransitionExpression: (TransitionExpression| object )
Registers a transition expression to this state.
removeTransition
(Not implemented.)
Transition
A Transition is a transient State temporarily adopted as the owner object’s current state as it changes from one of its proper States to another.
A transition acts within the domain of the least common ancestor between its origin and target states. During this time it behaves as if it were a substate of that domain state, inheriting method calls and propagating events in the familiar fashion.
Events
enter
function () {}
() ->
Immediately after currency is transferred to the transition from the source state, the transition emits an enter event.
start
function ( args... ) {}
( args... ) ->
args...:var
In the change call that instigated a transition, if the options argument included an args array, then these elements of options.args are passed as arguments to the transition’s start method, which relays them to listeners of the start event.
end
function ( args... ) {}
( args... ) ->
args...:var
A transition’s action phase is ended by calling its end method, and any arguments it receives are relayed to listeners of the transition’s end event.
If the transition bears no action, then arguments relayed to listeners of the transition’s start event are also relayed to listeners of the end event, which is emitted immediately after start.
exit
function () {}
() ->
Just before currency is transferred from a transition to its target state, an exit event is emitted by the transition.
Methods
superstate
this.superstate()
Returns the State to which this transition is currently attached during its traversal from origin through domain toward target.
origin
this.origin()
Returns the owner object’s most recently current State that is not itself a Transition — i.e., the state from which this transition originally emerged.
source
this.source()
Returns the State or Transition that was current immediately prior to this transition.
target
this.target()
Returns the intended destination State for this transition.
wasAborted
this.wasAborted()
Returns false if this transition has reached its target and completed successfully.
Returns true if this was aborted prior to reaching target.
Returns undefined if this has not yet been completed or aborted.
start
this.start( args... )
Automatically invoked as a result of a call to State.change.
end
this.end()
Used to signal the end of this transition’s action.
Upon reaching the top of its domain, a transition will invoke its action function, which is responsible for calling end() to signal that it is finished and ready for the transition to begin the descending phase of the traversal to target.