transition-manager
Version:
Transition Manager. Framework independent transition manager to transition elements using states and actions.
326 lines (275 loc) • 8.05 kB
JavaScript
'use strict';
Object.defineProperty(exports, '__esModule', {
value: true
});
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
var _commonLoggerJs = require('../common/logger.js');
var _commonLoggerJs2 = _interopRequireDefault(_commonLoggerJs);
var _commonDispatcher = require('../common/dispatcher');
var _utilsDefault = require('../utils/default');
var _utilsDefault2 = _interopRequireDefault(_utilsDefault);
var _utilsMixin = require('../utils/mixin');
var _utilsMixin2 = _interopRequireDefault(_utilsMixin);
/* create class and extend logger */
var FSM = _utilsMixin2['default']({ name: 'StateMachine' }, _commonLoggerJs2['default']);
(function () {
var _states = {},
_currentState = null,
_initial = null,
_actionQueue = [],
_history = [],
_cancelled = false,
_transitionCompleted = true,
_stateChangedHandler = null,
_options = {
history: false,
limitq: true,
qtransitions: true,
debug: false
};
/**
* check to se if th animation has cancelled in
* between state transitions
* @return {Boolean} cancelled
*/
function isCancelled() {
if (_cancelled) {
_transitionCompleted = true;
_cancelled = false;
return true;
}
return false;
}
/**
* transition to the next state
* @param {object} nextState new state object
* @param {string} action actionID
* @param {object} data data sent with the action
* @param {string} actionIdentifier state and action combined to make a unique string
*/
function _transitionTo(nextState, action, data) {
_cancelled = false;
_transitionCompleted = false;
if (isCancelled()) {
return false;
}
if (_currentState) {
var previousState = _currentState;
if (_options.history) {
_history.push(previousState.name);
}
}
_currentState = nextState;
if (action) {
_stateChangedHandler(action, data);
} else {
_transitionCompleted = true;
FSM.log('State transition Completed! Current State :: ' + _currentState.name);
_commonDispatcher.stateChanged.dispatch(_currentState);
}
}
/**
* If states have queued up
* loop through and action all states in the queue until
* none remain
*/
function _processActionQueue() {
if (_actionQueue.length > 0) {
var stateEvent = _actionQueue.shift();
if (!_currentState.getTarget(stateEvent.action)) {
_processActionQueue();
} else {}FSM.action(stateEvent.action, stateEvent.data);
return false;
}
FSM.log('State transition Completed! Current State :: ' + _currentState.name);
_commonDispatcher.stateChanged.dispatch(_currentState);
}
/**
* start FSM
* set the initial state
*/
FSM.start = function () {
if (!_initial) {
return FSM.log('ERROR - FSM must have an initial state set');
}
_transitionTo(_initial, null);
return this;
};
/**
* return the action history
* @return {aray}
*/
FSM.getHistory = function () {
return _history;
};
/**
* DO ACTION
* do action and change the current state if
* the action is available and allowed
* @param {string} action to carry out
* @param {object} data to send with the state
*/
FSM.action = function (action, data) {
if (!_currentState) {
return FSM.log('ERROR : You may need to start the fsm first');
}
/* if transitioning, queue up next action */
if (!_transitionCompleted && _options.qtransitions) {
FSM.log('transition in progress, adding action *' + action + ' to queue');
/* store the action data */
var actionStore = { action: action, data: data };
if (_options.limitq) {
_actionQueue[0] = actionStore;
} else {
_actionQueue[_actionQueue.length] = actionStore;
}
return false;
}
var target = _currentState.getTarget(action),
newState = _states[target],
_actionId = _currentState.id(action);
/* if a new target can be found, change the current state */
if (newState) {
FSM.log('Changing state :: ' + _currentState.name + ' >>> ' + newState.name);
_transitionTo(newState, _actionId, data);
} else {
FSM.error('State name ::: ' + _currentState.name + ' OR Action: ' + action + ' is not available');
}
};
/**
* cancel the current transition
*/
FSM.cancel = function () {
_cancelled = true;
};
/**
* transition completed
* called externally once all processes have completed
*/
FSM.transitionComplete = function () {
_transitionCompleted = true;
_processActionQueue();
};
/**
* add a new state to the FSM
* @param {object} state - FSM STATE
* @param {Boolean} isInitial
*/
FSM.addState = function (state, isInitial) {
if (!_states || _states[state.name]) {
return null;
}
_states[state.name] = state;
if (isInitial) {
_initial = state;
}
return state;
};
/**
* initialise - pass in setup options
* @param {object} options
*/
FSM.init = function (options) {
_utilsDefault2['default'](_options, options);
FSM.initLogger(_options.debug);
FSM.log('initiated');
};
/**
* create states and transitions based on config data passed in
* if states are an array, loop and assign data
* to new state objects
* @param {array/object} config - [{ name, transitions, initial }]
*/
FSM.create = function (config) {
var _this = this;
if (config instanceof Array) {
config.forEach(function (item) {
_this.create(item);
}, this);
return this;
}
var initial = _states.length === 0 || config.initial,
state = new FSM.State(config.name, initial),
stateTransitions = config.stateTransitions || [];
stateTransitions.forEach(function (transition) {
state.addTransition(transition.action, transition.target, transition._id);
});
FSM.addState(state, initial);
};
/**
* return the current state
* @return {object} FSM state
*/
FSM.getCurrentState = function () {
return _currentState;
};
/**
* dispose the state machin
*/
FSM.dispose = function () {
_states = null;
};
/* sets a statesChanged method instead of using a signal */
Object.defineProperty(FSM, 'stateChangedMethod', { set: function set(method) {
_stateChangedHandler = method;
} });
/****************************** [ Create FSM State] ******************************/
/**
* FSM state class
* @param {string} name state name
*/
FSM.State = function (name, initial) {
this._transitions = {}; // available transitions
this._name = name; // name
this._data = {}; // data to assosciate with the action
this._initial = initial;
};
FSM.State.prototype = {
_fetchTransition: function _fetchTransition(action, method) {
if (this._transitions[action]) {
return this._transitions[action][method];
}
return false;
},
/**
* add the available trasitions for each state
* @param {string} action e.g.'GOTOHOME'
* @param {string} target e.g. 'HOME'
*/
addTransition: function addTransition(action, target, actionIdnentifier) {
if (this._transitions[action]) {
return false;
}
this._transitions[action] = { target: target, _id: actionIdnentifier };
},
getActionId: function getActionId(action) {
return this._fetchTransition(action, '_id');
},
getTarget: function getTarget(action) {
return this._fetchTransition(action, 'target');
}
};
/**
* create getters for the state
* - name
* - transitions
* - data
*/
Object.defineProperty(FSM.State.prototype, 'name', { get: function get() {
return this._name;
} });
Object.defineProperty(FSM.State.prototype, 'transitions', { get: function get() {
return this._transitions;
} });
Object.defineProperty(FSM.State.prototype, 'data', { get: function get() {
return this._data;
} });
Object.defineProperty(FSM.State.prototype, 'initial', { get: function get() {
return this._initial;
} });
Object.defineProperty(FSM.State.prototype, 'id', { get: function get() {
return this.getActionId;
} });
})();
exports['default'] = FSM;
module.exports = exports['default'];