Handlers
November 21, 2016 ยท View on GitHub
Overview
Handlers are StateMachine's mechanism to hook into lifecycle events and run custom code. They work just the same as event handlers in the browser, with Event objects being dispatched and passed to the event handler function, where you can take action, run code, etc.
There are a variety of event types / Event classes that describe the lifecycle a StateMachine system, all of which are described in the Events section of the documentation.
This section describes how hook into events by way of "shorthand" handler syntax.
Handler syntax
StateMachine keeps a map of event / callback pairs internally, which it uses to fire the correct events and call the correct handlers as the system transitions from state to state.
Because this map is somewhat complicated, and one of the aims of the project was to make using a finite state machine easy, a DSL (domain specific language) to reference lifecycle events has been developed.
Briefly, any lifecycle hook can be referenced using a few key words with some additional grammar, making it easy to specify handlers where it might otherwise might have been difficult, for example state.intro.next.start.
As an example, here are some typical use cases (note that patterns are always strings):
'change'
'pause'
'state.add'
'intro'
'intro:leave'
'intro@next'
'@next'
'(intro form)@next'
By combining keywords and grammar together in various different ways, you can quickly specify the exact object and/or lifecycle target to hook into.
You can experiment with an interactive handler demo here:
The specific syntax for keywords and grammars is summarised below:
| Format | Description |
|---|---|
alias | Any single word that resolves to a namespaced type, state or action |
namespace.type | Absolute syntax for namespaced types |
@action | An action name |
#state | Alternative state name identifier to using an alias |
:type | An event type, for action or state events |
(foo bar baz) | Grouping for multiple patterns, which are expanded to multiple handlers |
Handler assignment
You can assign handlers via the constructor options like so:
var options = {
handlers: {
'change' : function (event, fsm) { ... },
'intro@next' : function (event, fsm, foo, bar) { ... },
}
};
var fsm = new StateMachine(options);
Alternatively, you can assign them via fsm.on():
var fsm = new StateMachine();
fsm.on('change', function (event, fsm) { ... });
The method is chainable, so you can add multiple handlers this way, though bear in mind that you can also use the grouping syntax (a b c) to assign the same handler to multiple events.
Handler execution
Any event handler callback should be of a specific format:
function (event, fsm, ...rest) { ... }
All handlers are passed first the specific Event instance describes the lifecycle hook, then a reference to the owning StateMachine then any optional parameters that may have been passed for some cases.
Inside the event handler you are free to call whatever code you like, using this to refer to the configured scope.
If you need to pause, resume or cancel a transition, you can do it via the fsm parameter, like so:
function onChange(event, fsm)
{
fsm.pause();
asynchronousCall(function onComplete() {
fsm.resume();
});
}
Handler id patterns
This section outlines in detail, the specific handler id patterns that you can use to hook into lifecycle events.
You can experiment yourself with writing handler ids and seeing how they parse here:
Note that the StateMachine's lifecycle means that some hooks effectively overlap each other; for example action:start and state:leave are effectively the same thing, but your particular implementation may require, or benefit from, the subtle semantic / ordering differences between them.
Everyday patterns
You'll use these patterns pretty much every time you use StateMachine, as they map to events common / logical to the majority of use cases.
#
A namespaced event alias or single state: alias
This is a convenience / catch-all pattern that maps a single word to one of two options:
- a namespaced event, for example
changeorpause - a single state, for example
intro
Examples:
change // system.change
pause // transition.pause
intro // a state in your system called "intro"
Note that namespaced event aliases take precendence over named states, so be careful not to name your states where they can never have handlers attached due to conflicts.
#
A namespaced event: namespace.event
An namespaced reference to an event, such as system.change or state.add.
Examples:
state.add // catches any states added
action.remove // catches any actions removed
transition.pause // absolute path to the alias `pause`
#
A single state: state
A single named state in (or not yet in) your system.
Note that because of default options, single state patterns default to state:enter meaning that any handlers assigned using this pattern will fire as the state is entered.
Examples:
intro
form
summary
#
A single state's action: state@action
An action called from a specific state.
This pattern gives you allows you to specifically target a state and action, giving you fine-grained control over when to fire a handler.
Appropriate use cases might be submitting a form or recovering from an error state.
Examples:
form@submit
error@back
#
A single action: @action
A single named action in (or not yet in) your system.
Note that because of default options, single action patterns default to action:start, meaning that any handlers assigned using this pattern will fire as the action starts.
Also note that as an action can be used across multiple states, that you can use this to target multiple states.
Examples:
@next
@restart
@quit
Occasional patterns
You'll probably only need these patterns rarely, but the grammar allows them, so here they are.
#
A single state's type: state:type
This pattern allows you to hook into a single state's :enter or :leave event.
The :enter type is the state event type configured by-default, so you may find this useful when you need to target an action's leave event.
Examples:
intro:leave
#
A single action's type: @action:type
This pattern allows you to hook into an action's :start or :end event.
The :start type is the action event type configured by-default, so you may find this useful when you need to target an action's end event.
Examples:
@submit:end
#
Any state or action's type: :type
This catch-all pattern allows you hook into any state or action's events, so in the case of states, :enter or leave or the case of actions :start or :end.
Note that in an already-rich event model, there are no events for transition start and end, so as actions begin and end transitions, you can use action events to simulate this:
:start // transition.start
:end // transition.end
Examples:
:start // start any action
:leave // leave any state