Skip to content

Commit

Permalink
Promise feature improvements
Browse files Browse the repository at this point in the history
  • Loading branch information
Matteo Tafani Alunno committed Aug 11, 2015
1 parent 1c83a30 commit 0c94728
Show file tree
Hide file tree
Showing 9 changed files with 412 additions and 229 deletions.
2 changes: 1 addition & 1 deletion bower.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "angular-state-machine",
"version": "1.1.1",
"version": "1.2.0",
"main": "./dist/angular-state-machine.min.js",
"description": "AngularJS service to implement a simple finite state machine.",
"repository": {
Expand Down
171 changes: 106 additions & 65 deletions dist/angular-state-machine.dev.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/**
* AngularJS service to implement a finite state machine.
* @version v1.1.1 - 2015-08-11
* @version v1.2.0 - 2015-08-11
* @link https://github.com/tafax/angular-state-machine
* @author Matteo Tafani Alunno <matteo.tafanialunno@gmail.com>
* @license MIT License, http://www.opensource.org/licenses/MIT
Expand Down Expand Up @@ -165,6 +165,15 @@ function StateMachine(strategy, machineConfiguration) {
strategy.initialize(machineConfiguration);
};

/**
* Gets the current state name.
*
* @returns {String}
*/
this.getCurrentState = function() {
return strategy.getCurrentState();
};

/**
* Gets an array of the states.
*
Expand Down Expand Up @@ -273,6 +282,7 @@ FSM.provider('stateMachine', function StateMachineProvider() {
function MachineStrategy(){}

MachineStrategy.prototype.initialize = function(machineConfiguration){};
MachineStrategy.prototype.getCurrentState = function(){};
MachineStrategy.prototype.getStates = function(machineConfiguration){};
MachineStrategy.prototype.getMessages = function(machineConfiguration){};
MachineStrategy.prototype.hasMessage = function(machineConfiguration, message){};
Expand All @@ -297,6 +307,9 @@ function SyncStrategy($q, $injector) {

this.$q = $q;
this.$injector = $injector;

// Handles the promises to create a chain.
this.lastPromise = null;
}

/**
Expand All @@ -318,6 +331,19 @@ SyncStrategy.prototype.initialize = function(machineConfiguration) {
this.currentState.params = {};
};

/**
* Gets the current state.
*
* @returns {String}
*/
SyncStrategy.prototype.getCurrentState = function() {
var fsm = this;
return this.$q.when(this.lastPromise)
.then(function(){
return fsm.currentState.name;
});
};

/**
* Gets an array of the states.
*
Expand Down Expand Up @@ -358,9 +384,13 @@ SyncStrategy.prototype.hasMessage = function(machineConfiguration, message) {
* @returns {boolean}
*/
SyncStrategy.prototype.isAvailable = function(machineConfiguration, message) {
var transitions = machineConfiguration.getTransitions();
var edges = transitions[this.currentState.name];
return edges.hasOwnProperty(message);
var fsm = this;
return this.$q.when(this.lastPromise)
.then(function(){
var transitions = machineConfiguration.getTransitions();
var edges = transitions[fsm.currentState.name];
return edges.hasOwnProperty(message);
});
};

/**
Expand All @@ -370,9 +400,13 @@ SyncStrategy.prototype.isAvailable = function(machineConfiguration, message) {
* @returns {Array}
*/
SyncStrategy.prototype.available = function(machineConfiguration) {
var transitions = machineConfiguration.getTransitions();
var edges = transitions[this.currentState.name];
return Object.keys(edges);
var fsm = this;
return this.$q.when(this.lastPromise)
.then(function(){
var transitions = machineConfiguration.getTransitions();
var edges = transitions[fsm.currentState.name];
return Object.keys(edges);
});
};

/**
Expand All @@ -387,76 +421,83 @@ SyncStrategy.prototype.send = function(machineConfiguration, message, parameters

var fsm = this;

// Checks if the configuration has the message and it is available for the current state.
if (!fsm.hasMessage(machineConfiguration, message) || !fsm.isAvailable(machineConfiguration, message)) {

return;
}
// Retrieves all transitions.
var transitions = machineConfiguration.getTransitions();

// Gets the edge related with the message.
var edge = transitions[fsm.currentState.name][message];


// If the edge is an array it defines a list of transitions that should have a predicate
// and a final state. The predicate is a function that returns true or false and for each message
// only one predicate should return true.
if (edge instanceof Array) {
var passed = [];
// Checks the predicate for each transition in the edge.
for (var i in edge) {
var transition = edge[i];
// Checks predicate and if it passes add the final state to the passed ones.
if (fsm.$injector.invoke(transition.predicate, this, fsm.currentState)) {
passed.push(transition.to);
}
this.lastPromise = this.$q.when(this.lastPromise).then(function(){
// Checks if the configuration has the message and it is available for the current state.
if (!fsm.hasMessage(machineConfiguration, message) || !fsm.isAvailable(machineConfiguration, message)) {
return;
}
// Retrieves all transitions.
var transitions = machineConfiguration.getTransitions();

// Gets the edge related with the message.
var edge = transitions[fsm.currentState.name][message];


// If the edge is an array it defines a list of transitions that should have a predicate
// and a final state. The predicate is a function that returns true or false and for each message
// only one predicate should return true.
if (edge instanceof Array) {
var passed = [];
// Checks the predicate for each transition in the edge.
for (var i in edge) {
var transition = edge[i];
// Checks predicate and if it passes add the final state to the passed ones.
if (fsm.$injector.invoke(transition.predicate, this, fsm.currentState)) {
passed.push(transition.to);
}
}

// Checks if more than one predicate returned true. It is an error.
if (passed.length > 1) {
throw 'Unable to execute transition in state \'' + fsm.currentState.name + '\'. ' +
'More than one predicate is passed.';
// Checks if more than one predicate returned true. It is an error.
if (passed.length > 1) {
throw 'Unable to execute transition in state \'' + fsm.currentState.name + '\'. ' +
'More than one predicate is passed.';
}

// Replace the edge with the unique finale state.
edge = passed[0];
}

// Replace the edge with the unique finale state.
edge = passed[0];
}
// Retrieves the next state that will be the final one for this transition.
var states = machineConfiguration.getStates();
var state = states[edge];

// Retrieves the next state that will be the final one for this transition.
var states = machineConfiguration.getStates();
var state = states[edge];
// Creates a copy of the current state. It is more secure against accidental changes.
var args = {};
args = angular.merge(args, fsm.currentState);
delete args.action;

// Creates a copy of the current state. It is more secure against accidental changes.
var args = {};
args = angular.merge(args, fsm.currentState);
delete args.action;
// If some parameters are provided it merges them into the current state.
if (parameters) {
angular.merge(args.params, parameters);
}

// If some parameters are provided it merges them into the current state.
if (parameters) {
args.params = angular.merge(args.params, parameters);
}
var result = undefined;
if (state.action && (typeof state.action === 'function' || Object.prototype.toString.call(state.action) === '[object Array]')) {
result = fsm.$injector.invoke(state.action, fsm, args);
}

// Executes the action defined in the state by passing the current state with the parameters. Since it is not
// possibile to determine if the result is a promise or not it is wrapped using $q.when and treated as a promise
return fsm.$q.when(fsm.$injector.invoke(state.action, fsm, args)).then(function (result) {
// Executes the action defined in the state by passing the current state with the parameters. Since it is not
// possibile to determine if the result is a promise or not it is wrapped using $q.when and treated as a promise
return fsm.$q.when(result).then(function (result) {

// Checks the result of the action and sets the parameters of the new current state.
if (!result && fsm.currentState.params) {
state.params = fsm.currentState.params;
}
else {
// Creates the parameters if the state doesn't have them.
if (!state.hasOwnProperty('params')) {
state.params = {};
// Checks the result of the action and sets the parameters of the new current state.
if (!result && fsm.currentState.params) {
state.params = fsm.currentState.params;
}
else {
// Creates the parameters if the state doesn't have them.
if (!state.hasOwnProperty('params')) {
state.params = {};
}

// Merges the state parameters with the result.
state.params = angular.merge(state.params, result);
}
// Merges the state parameters with the result.
angular.merge(state.params, result);
}

// Sets the new current state.
fsm.currentState = state;
// Sets the new current state.
fsm.currentState = state;
});
});

return this.lastPromise;
};
Loading

0 comments on commit 0c94728

Please sign in to comment.