All Projects → kendagriff → Workflow.js

kendagriff / Workflow.js

JS-based Finite State Machine for Backbone.js

Programming Languages

coffeescript
4710 projects

workflow.js

Finite-state machine (FSM) for Backbone.js with an elegant syntax. Works as a drop-in extension of Backbone.Model. What's it got on other JS-based state machines? It's simple, it works out-of-the-box with Backbone.js, and it supports multiple state machines. This FSM is loosely modeled after the Ruby workflow gem from geekq.

Dependencies

  • JQuery
  • Backbone.js (Tested in 0.9.1, but probably works in most previous versions.)
  • Underscore.js (Tested in 1.3.1, but same as above – probably works in past versions.)

Setup

Just add these dependencies to your site's <head>, in order:

<script src="jquery.js"></script>
<script src="underscore.js"></script>
<script src="backbone.js"></script>
<script src="workflow.js"></script>

Usage

Let's start with a complete example (given in CoffeeScript):

class User extends Backbone.Model
  defaults:
    signed_up_at: null

  workflow:
    initial: 'visitor'
    events: [
      { name: 'signUp', from: 'visitor', to: 'user' }
      { name: 'bail', from: 'visitor', to: 'lostUser' }
      { name: 'closeAccount', from: 'user', to: 'visitor' }
      { name: 'promote', from: 'user', to: 'superUser' }
    ]

  initialize: =>
    _.extend @, new Backbone.Workflow(@)
    @bind 'transition:to:signUp', @onSignUp
  
  onSignUp: =>
    @set { signed_up_at: new Date() }


user = new User()
user.get('workflow_state') # => "visitor"

user.triggerEvent('signUp')
user.get('workflow_state') # => "user"
user.get('signed_up_at') # => Fri Feb 17 2012 17:07:41 GMT-0700 (MST)

Here's a more detailed rundown.

Step 1: Extend Backbone.Model

Add this code to your initialize function:

initialize: =>
    _.extend @, new Backbone.Workflow(@)

Step 2: Define Your Workflow

Create an object named workflow, as a property on your model, and define its events and states:

class User extends Backbone.Model
  workflow:
    initial: 'visitor'
    events: [
      { name: 'signUp', from: 'visitor', to: 'user' }
      { name: 'bail', from: 'visitor', to: 'lostUser' }
      { name: 'closeAccount', from: 'user', to: 'visitor' }
      { name: 'promote', from: 'user', to: 'superUser' }
    ]

Step 3: Instantiate Your Model

The workflow_state attribute is offered as the default persistence field. This can be changed: see Customizations below.

user = new User()
user.get('workflow_state') # => "visitor"

Step 5: Initiate a Transition

user.triggerEvent('signUp')
user.get('workflow_state') # => "user"

Binding Events To Transitions

Perhaps the most helpful feature of workflow.js is the ability to bind Backbone events to transitions in your views (or model). For example:

user.bind 'transition:from:visitor', -> alert("I'll only be a visitor for a moment longer!")

Or, its near equivalent:

user.bind 'transition:to:user', -> alert("I'm no longer just a visitor!")

Transitions are handled before (transition:from), and after (transition:to), the user defined call back.

Customizations

Customize workflow.js by passing an attributes hash to the Backbone.Workflow constructor:

new Backbone.Workflow(@, { attrName: 'my_custom_db_field' })

Configurable parameters include:

  • attrName: Backbone.Model attribute used to persist state at the server level. Default is "workflow_state"

Multiple Workflows

To define multiple workflows, simply create a workflow object with a name of your choosing. Instantiate the workflows by passing a configuration array to the Workflow constructor.

Like this:

class DualPersonalityUser extends Backbone.Model
  jekyll_workflow:
    initial: 'happy'
    events: [
      { name: 'stub_toe', from: 'happy', to: 'hurting' }
      { name: 'get_massage', from: 'hurting', to: 'happy' }
    ]

  hyde_workflow:
    initial: 'catatonic'
    events: [
      { name: 'stub_toe', from: 'catatonic', to: 'ticked' }
      { name: 'get_massage', from: 'ticked', to: 'catatonic' }
    ]

  initialize: =>
    workflows = [
      { name: 'jekyll_workflow', attrName: 'jekyll_workflow_state' }
      { name: 'hyde_workflow', attrName: 'hyde_workflow_state' }
    ]
    _.extend @, new Backbone.Workflow(@, {}, workflows)

attrName refers to the Backbone fields you want to persist the workflow states to. You will also need to modify the methods you use to interact with the workflows.

To trigger an event, pass as the second argument the name of your workflow:

@user.triggerEvent('stub_toe', 'jekyll_workflow')

You'll handle transitions simililarly:

@user.on 'transition:from:jekyll_workflow:happy'
@user.on 'transition:to:jekyll_workflow:hurting'
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].