-
Notifications
You must be signed in to change notification settings - Fork 146
Description
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!