mirror of
https://github.com/Hopiu/postal.js.git
synced 2026-03-16 22:20:23 +00:00
199 lines
No EOL
5.7 KiB
JavaScript
199 lines
No EOL
5.7 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; |