mirror of
https://github.com/Hopiu/postal.js.git
synced 2026-03-30 04:40:35 +00:00
199 lines
No EOL
5.6 KiB
JavaScript
199 lines
No EOL
5.6 KiB
JavaScript
var _ = require('underscore');
|
|
/*
|
|
machina.js
|
|
Author: Jim Cowart
|
|
License: Dual licensed MIT (http://www.opensource.org/licenses/mit-license) & GPL (http://www.opensource.org/licenses/gpl-license)
|
|
Version 0.1.0
|
|
*/
|
|
|
|
var slice = [].slice,
|
|
NEXT_TRANSITION = "transition",
|
|
NEXT_HANDLER = "handler",
|
|
transformEventListToObject = function(eventList){
|
|
var obj = {};
|
|
_.each(eventList, function(evntName) {
|
|
obj[evntName] = [];
|
|
});
|
|
return obj;
|
|
},
|
|
parseEventListeners = function(evnts) {
|
|
var obj = evnts;
|
|
if(_.isArray(evnts)) {
|
|
obj = transformEventListToObject(evnts);
|
|
}
|
|
return obj;
|
|
},
|
|
utils = {
|
|
makeFsmNamespace: (function(){
|
|
var machinaCount = 0;
|
|
return function() {
|
|
return "fsm." + machinaCount++;
|
|
};
|
|
})(),
|
|
getDefaultOptions: function() {
|
|
return {
|
|
initialState: "uninitialized",
|
|
eventListeners: {
|
|
"*" : []
|
|
},
|
|
states: {},
|
|
eventQueue: [],
|
|
namespace: utils.makeFsmNamespace()
|
|
};
|
|
}
|
|
},
|
|
Fsm = function(options) {
|
|
var opt, initialState, defaults = utils.getDefaultOptions();
|
|
if(options) {
|
|
if(options.eventListeners) {
|
|
options.eventListeners = parseEventListeners(options.eventListeners);
|
|
}
|
|
if(options.messaging) {
|
|
options.messaging = _.extend({}, defaults.messaging, options.messaging);
|
|
}
|
|
}
|
|
opt = _.extend(defaults , options || {});
|
|
initialState = opt.initialState;
|
|
delete opt.initialState;
|
|
_.extend(this,opt);
|
|
|
|
this.state = undefined;
|
|
this._priorAction = "";
|
|
this._currentAction = "";
|
|
if(initialState) {
|
|
this.transition(initialState);
|
|
}
|
|
machina.fireEvent("newFsm", this);
|
|
};
|
|
|
|
Fsm.prototype.fireEvent = function(eventName) {
|
|
var args = arguments;
|
|
_.each(this.eventListeners["*"], function(callback) {
|
|
try {
|
|
callback.apply(this,slice.call(args, 0));
|
|
} catch(exception) {
|
|
if(console && typeof console.log !== "undefined") {
|
|
console.log(exception.toString());
|
|
}
|
|
}
|
|
});
|
|
if(this.eventListeners[eventName]) {
|
|
_.each(this.eventListeners[eventName], function(callback) {
|
|
try {
|
|
callback.apply(this,slice.call(args, 1));
|
|
} catch(exception) {
|
|
if(console && typeof console.log !== "undefined") {
|
|
console.log(exception.toString());
|
|
}
|
|
}
|
|
});
|
|
}
|
|
};
|
|
|
|
Fsm.prototype.handle = function(msgType) {
|
|
// vars to avoid a "this." fest
|
|
var states = this.states, current = this.state, args = slice.call(arguments,0), handlerName;
|
|
this.currentActionArgs = args;
|
|
if(states[current] && (states[current][msgType] || states[current]["*"])) {
|
|
handlerName = states[current][msgType] ? msgType : "*";
|
|
this._currentAction = current + "." + handlerName;
|
|
this.fireEvent.apply(this, ["Handling"].concat(args));
|
|
states[current][handlerName].apply(this, args.slice(1));
|
|
this.fireEvent.apply(this, ["Handled"].concat(args));
|
|
this._priorAction = this._currentAction;
|
|
this._currentAction = "";
|
|
this.processQueue(NEXT_HANDLER);
|
|
}
|
|
else {
|
|
this.fireEvent.apply(this, ["NoHandler"].concat(args));
|
|
}
|
|
this.currentActionArgs = undefined;
|
|
};
|
|
|
|
Fsm.prototype.transition = function(newState) {
|
|
if(this.states[newState]){
|
|
var oldState = this.state;
|
|
this.state = newState;
|
|
if(this.states[newState]._onEnter) {
|
|
this.states[newState]._onEnter.call( this );
|
|
}
|
|
this.fireEvent.apply(this, ["Transitioned", oldState, this.state ]);
|
|
this.processQueue(NEXT_TRANSITION);
|
|
return;
|
|
}
|
|
this.fireEvent.apply(this, ["InvalidState", this.state, newState ]);
|
|
};
|
|
|
|
Fsm.prototype.processQueue = function(type) {
|
|
var filterFn = type === NEXT_TRANSITION ?
|
|
function(item){
|
|
return item.type === NEXT_TRANSITION && ((!item.untilState) || (item.untilState === this.state));
|
|
} :
|
|
function(item) {
|
|
return item.type === NEXT_HANDLER;
|
|
},
|
|
toProcess = _.filter(this.eventQueue, filterFn, this);
|
|
this.eventQueue = _.difference(this.eventQueue, toProcess);
|
|
_.each(toProcess, function(item, index){
|
|
this.handle.apply(this, item.args);
|
|
}, this);
|
|
};
|
|
|
|
Fsm.prototype.deferUntilTransition = function(stateName) {
|
|
if(this.currentActionArgs) {
|
|
var queued = { type: NEXT_TRANSITION, untilState: stateName, args: this.currentActionArgs };
|
|
this.eventQueue.push(queued);
|
|
this.fireEvent.apply(this, [ "Deferred", this.state, queued ]);
|
|
}
|
|
};
|
|
|
|
Fsm.prototype.deferUntilNextHandler = function() {
|
|
if(this.currentActionArgs) {
|
|
var queued = { type: NEXT_TRANSITION, args: this.currentActionArgs };
|
|
this.eventQueue.push(queued);
|
|
this.fireEvent.apply(this, [ "Deferred", this.state, queued ]);
|
|
}
|
|
};
|
|
|
|
Fsm.prototype.on = function(eventName, callback) {
|
|
if(!this.eventListeners[eventName]) {
|
|
this.eventListeners[eventName] = [];
|
|
}
|
|
this.eventListeners[eventName].push(callback);
|
|
};
|
|
|
|
Fsm.prototype.off = function(eventName, callback) {
|
|
if(this.eventListeners[eventName]){
|
|
this.eventListeners[eventName] = _.without(this.eventListeners[eventName], callback);
|
|
}
|
|
};
|
|
|
|
var machina = {
|
|
Fsm: Fsm,
|
|
bus: undefined,
|
|
utils: utils,
|
|
on: function(eventName, callback) {
|
|
if(!this.eventListeners[eventName]) {
|
|
this.eventListeners[eventName] = [];
|
|
}
|
|
this.eventListeners[eventName].push(callback);
|
|
},
|
|
off: function(eventName, callback) {
|
|
if(this.eventListeners[eventName]){
|
|
this.eventListeners[eventName] = _.without(this.eventListeners[eventName], callback);
|
|
}
|
|
},
|
|
fireEvent: function(eventName) {
|
|
var i = 0, len, args = arguments, listeners = this.eventListeners[eventName];
|
|
if(listeners && listeners.length) {
|
|
_.each(listeners, function(callback) {
|
|
callback.apply(null,slice.call(args, 1));
|
|
});
|
|
}
|
|
},
|
|
eventListeners: {
|
|
newFsm : []
|
|
}
|
|
};
|
|
|
|
module.exports = machina; |