Coder Social home page Coder Social logo

ember-fsm's Introduction

Ember FSM

A promise-aware finite state machine implementation for Ember objects

var trafficSignal = Em.FSM.Machine.create({
  events: {
    cycle: {
      transitions: [
        { initialized: 'red' },
        { red: 'green' },
        { green: 'amber' },
        { amber: 'red' }
      ]
    },

    powerDown: {
      transition: { $all: 'off' }
    }
  }
});

trafficSignal.get('currentState');
// "initialized"

trafficSignal.send('cycle');
trafficSignal.get('currentState');
// "red"

trafficSignal.send('cycle');
trafficSignal.get('currentState')
// "green"

A wild traffic signal demo appears!

Getting Started

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.

Defining a State Machine

SleepyFSM = Ember.FSM.Machine.extend({
  // Here is where you define your state machine's state-specific configuration.
  // This section is optional.
  states: {
    // 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.
  events: {
    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' }
    }
  }
});

State Macros

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.

Transition Guarding

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.

Transition Events & Callbacks

Given the SleepyFSM example above, suppose we ran the following:

var fsm = SleepyFSM.create();
fsm.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

Asynchronicity In Callbacks

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.

Namespacing States

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:

  • ready
  • uploading.requestingUrl
  • uploading.sendingData
  • processing.enqueuing
  • processing.working
  • finished

When you define states like this, Ember.FSM automatically generates the following boolean accessor properties for you:

  • isInReady
  • isInUploading
  • isInUploadingRequestingUrl
  • isInUploadingSendingData
  • isInProcessing
  • isInProcessingEnqueuing
  • isInProcessingWorking
  • isInFinished

Stateful Mixin

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:

App.UploadController = Em.Controller.extend(Em.FSM.Stateful, {
  needs: 'notifier',

  actions: {
    uploadFile: function(file) {
      this.set('file', file);
      this.sendStateEvent('addFile');
    }
  },

  states: {
    initialState: 'nofile'
  },

  stateEvents: {
    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' }
    }
  },

  reset: function() {
    this.set('file', null);
  },

  checkFile: 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
    }
  },

  getUploadURL: 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
  },

  performUpload: function() {
    return $.ajax(this.get('uploadToURL'), {
      type: 'put',
      data: this.get('file')
    });
  },

  finishedUpload: function() {
    this.get('controllers.notifier').success('Upload complete');
    this.sendStateEvent('finishUpload');
  }
});

Contributing

Install Node.js and NPM, there are packages and binaries on the Node.js website that make it easy.

cd my/fork/of/ember-fsm
npm install -g broccoli-cli
npm install
bower install
broccoli serve

Then in another session:

cd my/fork/of/ember-fsm
testem

Then do what testem tells you to do.

Thanks

ember-fsm's People

Contributors

ghedamat avatar heycarsten avatar

Watchers

 avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.