Name: ember-fsm
Owner: Crowdstrike
Description: A promise-aware finite state machine implementation for Ember objects
Created: 2014-10-09 00:39:13.0
Updated: 2014-08-11 11:48:43.0
Pushed: 2014-10-15 00:49:51.0
Size: 251
Language: null
GitHub Committers
User | Most Recent Commit | # Commits |
---|
Other Committers
User | Most Recent Commit | # Commits |
---|
trafficSignal = Em.FSM.Machine.create({
ents: {
cycle: {
transitions: [
{ initialized: 'red' },
{ red: 'green' },
{ green: 'amber' },
{ amber: 'red' }
]
},
powerDown: {
transition: { $all: 'off' }
}
ficSignal.get('currentState');
initialized"
ficSignal.send('cycle');
ficSignal.get('currentState');
red"
ficSignal.send('cycle');
ficSignal.get('currentState')
green"
A wild traffic signal demo appears!
The most recent builds are available in the dist
directory. If you
just want to drop Ember.FSM
into your app and start using it, you're probably
looking for: dist/globals/ember-fsm.js
.
pyFSM = Ember.FSM.Machine.extend({
Here is where you define your state machine's state-specific configuration.
This section is optional.
ates: {
// The default initial state is "initialized"
initialState: 'awake'
// If you'd like, you can choose to explicitly define the names of your
// states:
knownStates: ['sleeping', 'angry', 'awake', 'initialized', 'failed'],
// You can define global per-state callbacks, they will fire whenever the
// state will be entered, was entered, will be exited, or was exited.
sleeping: {
willEnter: function() { },
didEnter: function() { },
willExit: function() { },
didExit: function() { }
}
Here's where you define your state machine's events, it is required.
ents: {
sleep: {
// You can define global per-event callbacks. These will fire for any
// transition before or after this event.
before: function() { },
after: function() { },
// This is where the event's transitions are defined, it is also aliased
// to "transition". It can accept either a single object like one in the
// array below, or an array of transition definition objects:
transitions: [
{ awake: 'sleeping', doUnless: 'unableToSleep' },
{ awake: 'angry', doIf: 'unableToSleep' },
{ sleeping: '$same' }
]
},
// By default this error event is injected into your state machine for you,
// you can override it and provide your own transitions and callbacks if
// you'd like.
error: {
transition: { $all: 'failed' }
}
For the sake of less typing (and less chances of introducing failure) the following macros can be used in transition definitions:
| Macro | Description |
|:———–|:——————————|
| $all
| Expands to all known states. |
| $same
| Expands to the same state as the from state. transition: { sleeping: '$same' }
|
| $initial
| Expands to the initial state. |
You can specify that a transition be excluded or included in the event using
doIf
or doUnless
. Consider SleepyFSM
above, if we set unableToSleep
to
true
then when we send in the sleep
event, it will transition to the state
angry
because the transition { awake: 'sleeping' }
will be excluded from
the list.
doIf
and doUnless
are aliased to guard
and unless
respectively.
Given the SleepyFSM
example above, suppose we ran the following:
fsm = SleepyFSM.create();
send('sleep');
Here is the series of transition events that will occurr and the corresponding callbacks that will run and where they can be defined:
| Current State | Is Active | Event | Runs callbacks |
|:————–|:———-|:————————-|:————————————–|
| awake | false | beforeEvent
| before
on events and transitions |
| awake | true | _activateTransition_
| internal |
| awake | true | willExit
| willExit
on states and transitions |
| awake | true | willEnter
| willEnter
on states and transitions |
| sleeping | true | _setNewState_
| internal |
| sleeping | true | didExit
| didExit
on states and transitions |
| sleeping | true | didEnter
| didEnter
on states and transitions |
| sleeping | false | _deactivateTransition_
| internal |
| sleeping | false | afterEvent
| after
on events and transitions |
Some of the event names above also have aliases:
| Event | Aliases |
|:————–|:——————|
| beforeEvent
| before
|
| afterEvent
| after
|
| didEnter
| enter
, action
|
| didExit
| exit
|
If callbacks return a promise, the next callback in the chain will not fire
until the promise is resolved. The return value of callbacks is stored in the
transition's resolutions
object. Likewise, rejections are stored in the
rejections
object of the transition.
Ember.FSM
doesn't provide true sub-state support, but you can namespace your
states. For example, suppose a portion of your state workflow is related in
some way; you can prefix those states with a namespace:
When you define states like this, Ember.FSM automatically generates the following boolean accessor properties for you:
When it comes to using Ember.FSM
in your application, you'll almost always
want to use Ember.FSM.Stateful
over sub-classing Ember.FSM.Machine
. This way
you can formalize a state workflow around something like file uploads where you
might have to incorporate three different proceesses into on user experience.
Building these sorts of workflows implicitly as-you-code-along can be a recipie
for massive sadness. So why be sad? Formalize that workflow! Here's an example
of how adding Ember.FSM.Stateful
to a controller can remove a lot of the
tedious parts of workflow managment:
UploadController = Em.Controller.extend(Em.FSM.Stateful, {
eds: 'notifier',
tions: {
uploadFile: function(file) {
this.set('file', file);
this.sendStateEvent('addFile');
}
ates: {
initialState: 'nofile'
ateEvents: {
addFile: {
transitions: {
from: ['nofile', 'failed'],
to: 'ready',
before: 'checkFile',
}
},
startUpload: {
transitions: {
from: 'ready',
to: 'uploading',
before: 'getUploadURL',
didEnter: 'performUpload',
after: 'finishedUpload'
}
},
finishUpload: {
transition: { uploading: 'nofile', didEnter: 'reset' }
}
set: function() {
this.set('file', null);
eckFile: function() {
var file = this.get('file');
if (file.size > 0) {
return;
} else {
this.get('controllers.notifier').warn('file must have content');
Em.FSM.reject(); // A helper for throwing an error
}
tUploadURL: function() {
var controller = this;
var fileName = this.get('file.name');
var xhr;
xhr = $.ajax('/api/signed_uploads', {
type: 'put',
data: { file: { name: fileName } }
});
xhr.then(function(payload) {
Em.run(function() {
controller.set('uploadToURL', payload.signed_upload.url);
});
});
return xhr; // Causes transition to block until promise is settled
rformUpload: function() {
return $.ajax(this.get('uploadToURL'), {
type: 'put',
data: this.get('file')
});
nishedUpload: function() {
this.get('controllers.notifier').success('Upload complete');
this.sendStateEvent('finishUpload');
Install Node.js and NPM, there are packages and binaries on the Node.js website that make it easy.
y/fork/of/ember-fsm
install -g broccoli-cli
install
r install
coli serve
Then in another session:
y/fork/of/ember-fsm
em
Then do what testem tells you to do.