hsm.js
A minimal hierarchal state machine for Javascript.
What is a state machine?
A state machine is a thing that consists of a finite number of states. At any time, it can only be in exactly one of those states. The name 'state machine' is an abbreviation of 'finite state machine.'
What is a hierarchal state machine?
A state machine with a notion of nested states is called a hierarchal state machine. When a nested state is active, each of its parent states are also active.
What is this library?
This library provides a barebones implementation of the above system. It remains unopinionated about two things:
- What a state is
- How to transition between two states
It has two opinions of importance:
- Child states cannot be defined before parent states
- Transitions are always asynchronous
API
Hsm.getParentStateName( stateName )
Given a state, get the value of the parent state.
Hsm.getParentStateName('cheese.is.good');
// => 'cheese.is'constructor( options )
Create a new instance of Hsm. Pass the states option to populate the state machine with those states.
var hsm = new Hsm({
states: {
'': IndexState,
'books': BooksState,
'books.book': BookState
}
});Hsm instances are created in an undefined state. You must transition
to the initial state manually.
setState( stateName, stateDefinition )
Set a new state. stateDefinition can be anything – this library
does nothing with the states. stateName can be anything as well,
but an Error will be thrown if the parent state does not exist.
hsm.setState('food', FoodState);
hsm.setState('food.breakfast', BreakfastState);getState( stateName )
Access the object that represents stateName.
hasState( stateName )
Returns a Boolean representing whether or not stateName has
been set.
currentStateName()
Return the name of the current state.
currentState()
Return the object that represents the current state.
transitionTo( newState )
Asynchronously transition to newState by delegating to transition. This
method is not intended to be overridden. To customize the transition behavior,
use the transition hook described below.
transition( stateDiff, cancel )
Define an asynchronous transition. stateDiff is an object representing the
difference between the current state and the new state.
An example diff between books.book.author and books.comments is:
{
outStates: ['books.book.author', 'books.book'],
inStates: ['books.comments']
}Call cancel to cancel the transition.
A note on transition to index
Defining an index state at any other state gives you an opportunity
to enter a unique state that is only triggered when you land on that state,
but not on a child state.
To get a better understanding of what I mean, consider these states:
{
'': RootState,
'books': BooksRoute,
'books.book': BookRoute
}When you transition to books.book, both books and book will be entered. If
instead you transition to just books, only books is triggered, as you might
have guessed. This is fine in some situations, but what if you want something special
to happen only when you land on books, but not when you enter a child of books? That's
what the index route is for.
Taking those same routes from above:
{
'': RootState,
'books': BooksRoute,
'books.index': BookIndexRoute,
'books.book': BookRoute
}Transitioning to books.book remains the same. But if you transition to books,
both books and index will be activated, giving you a unique state for books by
itself. Transitioning to books and books.index behave identically.