Skip to content

Commit

Permalink
first build
Browse files Browse the repository at this point in the history
  • Loading branch information
sebastiancarlsson committed May 25, 2015
1 parent b17da6d commit 608da44
Show file tree
Hide file tree
Showing 7 changed files with 327 additions and 6 deletions.
2 changes: 1 addition & 1 deletion dist/PromiseFSM-AngularJS.1.x.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*!
* PromiseFSM 0.1.0
* Mon, 25 May 2015 15:35:30 GMT
* Mon, 25 May 2015 15:40:56 GMT
*
* https://github.com/sebastiancarlsson/promise-fsm-js/
*
Expand Down
2 changes: 1 addition & 1 deletion dist/PromiseFSM-AngularJS.1.x.min.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion dist/PromiseFSM-Node.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*!
* PromiseFSM 0.1.0
* Mon, 25 May 2015 15:35:30 GMT
* Mon, 25 May 2015 15:40:56 GMT
*
* https://github.com/sebastiancarlsson/promise-fsm-js/
*
Expand Down
2 changes: 1 addition & 1 deletion dist/PromiseFSM-Node.min.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

295 changes: 295 additions & 0 deletions dist/PromiseFSM.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,295 @@
/*!
* PromiseFSM 0.1.0
* Mon, 25 May 2015 15:40:56 GMT
*
* https://github.com/sebastiancarlsson/promise-fsm-js/
*
* (c) 2015 Sebastian Carlsson
*
* MIT license
* https://github.com/sebastiancarlsson/promise-fsm-js/blob/master/LICENSE
*
**/
var PromiseFSM = (function() {
"use strict";

function StateMachine(name, options) {
this.name = name;
this.states = [];
this.transitions = [];
this.listeners = [];

this.locked = false;
//this.transitioning = false;

if (options.actions) this.actions = options.actions;
if (options.states) this.states = options.states;
this.verbose = options.verbose || false;

this.interface = {
$getState: this.getState.bind(this),
$addTransition: this.addTransition.bind(this),
$removeTransition: this.removeTransition.bind(this),
$addEventListener: this.addEventlistener.bind(this),
$removeEventListener: this.removeEventListener.bind(this)
};

if(this.verbose === true) {
log("Initializing state machine \"" + this.name + "\"");
}

if(!promiseAdapter) {
throw new Error("PromiseFSM - No promise adapter. Promise adapter must be set through PromiseFSM.setPromiseAdapter() before initialization");
}

if(options.states && Object.prototype.toString.call(this.states) === '[object Array]' && options.states.length > 1) {
this.states = options.states;
} else {
throw new Error("PromiseFSM - You have to define at least two states. E.g. options.states = [\"state1\", \"state2\"]");
}

if(options.actions && Object.prototype.toString.call(this.actions) === '[object Object]' && Object.keys(options.actions).length > 0) {
this.actions = options.actions;
for(var key in this.actions) {
this.interface[key] = this.transition.bind(this, this.actions[key].from, this.actions[key].to);
}
} else {
throw new Error("PromiseFSM - You have to define at least one action. E.g. options.actions = { myAction: { from: \"state1\", to: \"state2\" } }");
}

if(options.initialState) {
this.initialState = this.state = options.initialState;
} else {
this.initialState = this.state = this.states[0];
}
}

StateMachine.prototype.getState = function() {
return this.state;
};

StateMachine.prototype.transition = function(from, to) {
var deferred = promiseAdapter.defer();
if(this.locked) {
if(this.verbose) {
warn("State change failed - machine is locked");
}

deferred.reject(new Error(errorNames.LOCKED));
return deferred.promise;
}

if(!this.isSwitchLegal(from, to)) {
if(this.verbose) {
warn("State change failed - illegal transition attempt from \"" + this.state + "\" to \"" + newState + "\"");
}

deferred.reject(new Error(errorNames.ILLEGAL_TRANSITION));
return deferred.promise;
}

this.locked = true;
this.dispatchEvent(new StateMachineEvent(eventNames.LOCKED, from, to));

// TODO add guards here
this.dispatchEvent(new StateMachineEvent(eventNames.EXIT_STATE, from, to));
//this.transitioning = true;

if(this.verbose) {
log("\"" + from + "\" -> \"" + to + "\"");
}

var callbacks = [];
var i = this.transitions.length;
while(i--) {
if(this.transitions[i].from === from && this.transitions[i].to === to) {
callbacks.push(this.transitions[i].callback);
}
}

if(callbacks.length > 0) {
if(this.verbose) {
log("Pending state change from \"" + from + "\" -> \"" + to + "\"...");
}

var subPromises = [];
var subDeferred;
i = callbacks.length;
while(i--) {
subDeferred = promiseAdapter.defer();
subPromises.push(subDeferred.promise);
callbacks[i](subDeferred.resolve);
}

var promise = promiseAdapter.all(subPromises);
promise.then(this.completeSwitch.bind(this, to));
return promise;
} else {
this.completeSwitch(to);
deferred.resolve();
return deferred.promise;
}
};

StateMachine.prototype.completeSwitch = function(to) {
var from = this.state;
this.state = to;
this.dispatchEvent(new StateMachineEvent(eventNames.ENTER_STATE, from, to));

this.locked = false;
this.dispatchEvent(new StateMachineEvent(eventNames.UNLOCKED, from, to));

//this.transitioning = false;
this.dispatchEvent(new StateMachineEvent(eventNames.STATE_CHANGED, from, to));

if(this.verbose) {
log("Transition completed. New state is \"" + to + "\".");
}
};

StateMachine.prototype.isSwitchLegal = function(from, to) {
if(from !== this.state) {
return false;
}

if (from === to) {
return false;
}

if (this.states.indexOf(to) === -1) {
console.log('doesnt exist');
return false;
}

for(var i in this.actions) {
if (this.actions[i].to === to) {
if (typeof this.actions[i].from === 'string' && (this.actions[i].from === from || this.actions[i].from === '*')) {
return true;
} else if (Object.prototype.toString.call(this.actions[i].from) === '[object Array]') {
var toArray = this.actions[i].from;
var j = toArray.length;
while (j--) {
if (toArray[j] === from) {
return true;
}
}
}
}
}

return false;
};

StateMachine.prototype.addTransition = function(from, to, callback) {
if(this.getTransitionIndex(from, to, callback) > -1) {
warn("Duplicate transition added, only the first will be called");
} else {
this.transitions.push({
from: from,
to: to,
callback: callback
});
}
};

StateMachine.prototype.removeTransition = function(from, to, callback) {
var i = this.getTransitionIndex(from, to, callback);
if(i > -1) {
this.transitions.splice(i, 1);
}
};

StateMachine.prototype.getTransitionIndex = function(from, to, callback) {
var i = this.transitions.length;
while(i--) {
if(this.transitions[i].from === from && this.transitions[i].to === to && this.transitions[i].callback === callback) {
return i;
}
}
return -1;
};

StateMachine.prototype.addEventlistener = function(type, callback) {
this.listeners.push({
type: type,
callback: callback
});
};

StateMachine.prototype.removeEventListener = function(type, callback) {
var i = this.listeners.length;
while(i--) {
if(this.listeners[i].type === type && this.listeners[i].callback === callback) {
this.listeners.splice(i, 1);
}
}
};

StateMachine.prototype.dispatchEvent = function(evt) {
var i = this.listeners.length;
while(i--) {
if(this.listeners[i].type === evt.type) {
this.listeners[i].callback(evt);
}
}
};

StateMachine.prototype.getInterface = function() {
return this.interface;
};

function log(text) {
if(console) {
console.log("%cPromiseFSM", "color: #00f", text);
}
}

function warn(text) {
if(console) {
console.warn("%cPromiseFSM", "color: #00f", text);
}
}

var eventNames = {
LOCKED: "PromiseFSM/events/locked",
EXIT_STATE: "PromiseFSM/events/exitState",
ENTER_STATE: "PromiseFSM/events/enterState",
UNLOCKED: "PromiseFSM/events/unlocked",
STATE_CHANGED: "PromiseFSM/events/stateChanged"
};

var errorNames = {
ILLEGAL_TRANSITION: "ILLEGAL_TRANSITION",
LOCKED: "LOCKED"
};

function StateMachineEvent(type, from, to) {
this.type = type;
this.from = from;
this.to = to;
}

var promiseAdapter;
var machines = {};

return {
EVENTS: eventNames,
ERRORS: errorNames,
setPromiseAdapter: function(adapter) {
promiseAdapter = adapter;
},
create: function(name, options) {
machines[name] = new StateMachine(name, options);
return machines[name].getInterface();
},
getMachine: function(name) {
var machine = machines[name];
if(machine) {
return machine.getInterface();
} else {
return undefined;
}
}
};

}());
13 changes: 13 additions & 0 deletions dist/PromiseFSM.min.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 15 additions & 2 deletions gulpfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,23 @@ header += "\n";

var lib = fs.readFileSync("./src/PromiseFSM.js", "utf8");

gulp.task('default', sequence('jshint', 'test', 'clean', 'build'));
gulp.task('default', sequence('jshint', 'test', 'clean', 'build-vanilla', 'build'));

gulp.task('build-vanilla', function() {
return gulp.src(['src/PromiseFSM.js'])
.pipe(insert.prepend(header))
.pipe(gulp.dest('dist'))
.pipe(uglify({
preserveComments: 'some'
}))
.pipe(rename({
suffix: ".min"
}))
.pipe(gulp.dest('dist'));
});

gulp.task('build', function() {
return gulp.src(['src/PromiseFSM.js', 'src/templates/*.js'])
return gulp.src(['src/templates/*.js'])
.pipe(insert.prepend(header))
.pipe(replace("%code%", lib))
.pipe(gulp.dest('dist'))
Expand Down

0 comments on commit 608da44

Please sign in to comment.