All Projects → heycarsten → ember-fsm

heycarsten / ember-fsm

Licence: MIT License
[Maintenance Mode] A promise-aware finite state machine implementation for Ember

Programming Languages

javascript
184084 projects - #8 most used programming language
HTML
75241 projects
CSS
56736 projects

Projects that are alternatives of or similar to ember-fsm

ember-query-params
Ember service for your query params
Stars: ✭ 44 (+18.92%)
Mutual labels:  ember, ember-addon
ember-deep-set
Deeply set values on an Ember Object or POJO
Stars: ✭ 30 (-18.92%)
Mutual labels:  ember, ember-addon
kstatemachine
KStateMachine is a Kotlin DSL library for creating finite state machines (FSM) and hierarchical state machines (HSM).
Stars: ✭ 63 (+70.27%)
Mutual labels:  fsm, state-machine
ember-do-forms
ember-do-forms handles the icky parts of forms that you don't want to, and leaves the rest to you.
Stars: ✭ 18 (-51.35%)
Mutual labels:  ember, ember-addon
ember-uikit
The ember implementation of UIkit
Stars: ✭ 24 (-35.14%)
Mutual labels:  ember, ember-addon
ember-named-yields
Named Yields for Ember Components
Stars: ✭ 17 (-54.05%)
Mutual labels:  ember, ember-addon
ember-bootstrap-power-select
Integrate ember-power-select into your ember-bootstrap forms
Stars: ✭ 37 (+0%)
Mutual labels:  ember, ember-addon
ember-useragent
An Ember addon for Fastboot-enabled UserAgent parsing via UAParser.js.
Stars: ✭ 34 (-8.11%)
Mutual labels:  ember, ember-addon
simple-state-machine
A simple Java state machine for Spring Boot projects
Stars: ✭ 25 (-32.43%)
Mutual labels:  fsm, state-machine
ember-custom-actions
Custom API actions for Ember applications
Stars: ✭ 73 (+97.3%)
Mutual labels:  ember, ember-addon
pastafarian
A tiny event-based finite state machine
Stars: ✭ 20 (-45.95%)
Mutual labels:  fsm, state-machine
UnityHFSM
A simple yet powerful class based hierarchical finite state machine for Unity3D
Stars: ✭ 243 (+556.76%)
Mutual labels:  fsm, state-machine
ember-cli-ifa
Ember CLI addon for injecting fingerprinted asset map file into Ember app
Stars: ✭ 54 (+45.95%)
Mutual labels:  ember, ember-addon
ember-shadow-dom
Write templates for your components inside of a Shadow DOM root.
Stars: ✭ 26 (-29.73%)
Mutual labels:  ember, ember-addon
ember-ref-bucket
This is list of handy ember primitives, created to simplify class-based dom workflow
Stars: ✭ 31 (-16.22%)
Mutual labels:  ember, ember-addon
ember-google-analytics-embed
An Ember Addon for adding analytics visualizations using the Google Analytics Embed API.
Stars: ✭ 26 (-29.73%)
Mutual labels:  ember, ember-addon
ember-cli-yadda
Write cucumber specs for ember-cli applications
Stars: ✭ 41 (+10.81%)
Mutual labels:  ember, ember-addon
use-state-machine
Use Finite State Machines with React Hooks
Stars: ✭ 28 (-24.32%)
Mutual labels:  fsm, state-machine
ember-autoresize-modifier
Autoresize Element Modifier for Ember.js
Stars: ✭ 15 (-59.46%)
Mutual labels:  ember, ember-addon
ember-google-charts
Google's Material charts made easy for Ember apps - http://sir-dunxalot.github.io/ember-google-charts/
Stars: ✭ 31 (-16.22%)
Mutual labels:  ember, ember-addon

Ember FSM

⚠️ ⚠️ ⚠️

Maintenance Mode

Ember FSM was a lot of fun to develop 4 years ago when this all made a lot of sense to me, it's still used in some places and I will continue to maintain it to prevent apps from breaking. If you think the Ember community still needs a library like this with newer/better features please fork it and I will let people know where to go! Thanks for understanding 🙂

⚠️ ⚠️ ⚠️

A promise-aware finite state machine implementation for Ember

A wild (and current) traffic light demo appears!

import FSM from 'ember-fsm';

let trafficSignal = 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"

Getting Started

Install as an Ember Addon

ember install ember-fsm

Do you need this?

Try really hard not to need it, if you need it, I'm sorry. -- @heycarsten

Defining a State Machine

import FSM from 'ember-fsm';

let SleepyFSM = 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() { },
      didEnter() { },
      willExit() { },
      didExit() { }
    }
  },

  // 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() { },
      after() { },

      // 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:

let 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 FSM.Stateful over sub-classing 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.

Note: States and events are renamed in the mixin to fsmStates and fsmEvents respectively, to avoid conflict with core Ember properties.

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 to a controller can remove a lot of the tedious parts of workflow managment:

import Ember from 'ember';
import FSM from 'ember-fsm';

// controllers/upload.js
export default Ember.Controller.extend(FSM.Stateful, {
  needs: 'notifier',

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

  fsmStates: {
    initialState: 'nofile'
  },

  fsmEvents: {
    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() {
    this.set('file', null);
  },

  checkFile() {
    let file = this.get('file');

    if (file.size > 0) {
      return;
    } else {
      this.get('controllers.notifier').warn('file must have content');
      FSM.reject(); // A helper for throwing an error
    }
  },

  getUploadURL() {
    let fileName = this.get('file.name');

    let xhr = Ember.$.ajax('/api/signed_uploads', {
      type: 'put',
      data: { file: { name: fileName } }
    });

    xhr.then((payload) => {
      Ember.run(() => {
        this.set('uploadToURL', payload.signed_upload.url);
      });
    });

    return xhr; // Causes transition to block until promise is settled
  },

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

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

Running tests

  • ember test – Runs the test suite on the current Ember version
  • ember test --server – Runs the test suite in "watch mode"
  • ember try:each – Runs the test suite against multiple Ember versions

Running the dummy application

Thanks

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].