Skip to content

Handling 'back' events #4

@burin

Description

@burin

I've been using machina.js to model workflows and it's been working great so far. One thing I support is the ability for a user to go back in the workflow.

In any given state, a user can "save" their progress, transitioning to the next state, or go "back", which will take them back to the previous state.

Currently, each state has an explicit handler for "back", which calls transition() with the appropriate name of the expected action to go back to. Unfortunately, this only allows for "linear" progress, and conditional transitions are a little tricky to deal with.

Here is a stripped down example of a workflow I have created for adding a flight to an itinerary.

define([
    'machina', 'mediator'
], function(
    machina, mediator
) {
var AddFlightWorkflow = window.workflow = new machina.Fsm({
    initialState: 'uninitialized',

    states: {
        uninitialized: {
            initialize: function() {
                this.transition( 'showingForm' );
            }
        },

        // gathering user input
        showingForm: {
            _onEnter: function() {
                // show flights:add view,
            },
            selectDate: function() {
                this.transition( 'selectingDate' );
            },
            selectCarrier: function() {
                this.transition( 'selectingCarrier' );
            },
            save: function() {
                this.transition( 'selectingOriginDestinationAirports' );
            },
            back: function() {
                // kill this workflow
            }
        },

        selectingDate: {
            _onEnter: function() {
                // show calendar widget
            },
            save: function() {
                // save the new user date selection to the model
                // kill the widget
                // transition back
                this.transition( 'showingForm' );
            },
            back: function() {
                // kill the view/widget
                this.transition( 'showingForm' );
            }
        },

        selectingCarrier: {
            _onEnter: function() {
                // show carrier selection widget
            },
            save: function () {
                // save the user selected carrier
                // kill the widget
                // transition back
                this.transition( 'showingForm' );
            },
            back: function() {
                // kill the view/widget
                this.transition( 'showingForm' );
            }
        },

        // after submitting user data to svc (flight paths)
        selectingOriginDestinationAirports: {
            _onEnter: function() {
                // if the response had more than one option, show view
                // otherwise transition to confirmingFlight
            },
            save: function() {
                this.transition( 'confirmingFlight' );
            },
            back: function() {
                // transition to the last thing
                this.transition( 'showingForm' );
            }
        },

        // prior to sending data to svc (monitor flights)
        confirmingFlight: {
            _onEnter: function() {
                // show confirmation view
            },
            save: function() {
                // finally! we can save all the data
                this.transition( 'uninitialized' );
            },
            back: function() {
                // transition to the last thing
                this.transition( 'selectingOriginDestinationAirports' );
            }

        }
    }
});

mediator.subscribe( 'add_flight_workflow:initialize', function() {
    AddFlightWorkflow.handle('initialize');
});

mediator.subscribe( 'add_flight_workflow:save', function() {
    AddFlightWorkflow.handle('save');
});

mediator.subscribe( 'add_flight_workflow:back', function() {
    AddFlightWorkflow.handle('back');
});

mediator.subscribe( 'add_flight_workflow:selectDate', function() {
    AddFlightWorkflow.handle('selectDate');
});

mediator.subscribe( 'add_flight_workflow:selectCarrier', function() {
    AddFlightWorkflow.handle('selectCarrier');
});

return AddFlightWorkflow;
});

My question is whether this is a good approach to tackle this problem or if there is some alternative. On one hand, it's good because it's very explicit. On the other, it doesn't seem very DRY and I'm not sure how to go "back" if a particular state has two or more possible entry points (confirmingFlight can be transitioned to from showingForm in one case, or selectingOriginDestinationAirports in another)

I tried looking through the machina.js code to see if there was a "previous state" of some sort, but I didn't want to dig into something that could possibly change in the future (private API or such).

Any tips would be appreciated! And thanks for publishing your blog post about FSMs and creating a super clean library with a nice API!

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions