File: development/js/mixins/fsm.js
import {Mixin} from '../utils/classes';
import FSM from '../utils/fsm';
/**
* @module Mixins
*/
/**
* The FSM utility as a mixin, provides a finite state machine
* @class Mixins.FSM
*/
const STATES = Symbol('__finiteStateMaschine');
const FSMMixin = Mixin({
/**
* Allows to add a new state (see example for more info)
*
* @method addState
* @for Mixins.FSM
* @param {object} state The new state
* @return {this} The instance
*
* @example
* import FSM from './mixins/fsm';
*
* const MyClass = FSM(class {
* constructor() {
* // New states MyClass can be in are added
* this.addState({
* name: 'init',
* events: {next: 'after'}
* }).
* addState({
* name: 'after',
* events: {next: 'last', prev: 'init'}
* }).
* addState({
* name: 'last',
* events: {prev: 'after'}
* });
* }
* });
*/
addState(state) {
if (!this[STATES]) {
this[STATES] = FSM();
}
this[STATES].addState(state);
return this;
},
/**
* Allows to add an array of new states all at once
*
* @method addStates
* @for Mixins.FSM
* @param {array} states The new states
* @return {this} The instance
*
* @example
* import FSM from './mixins/fsm';
*
* const MyStates = [
* {name: 'init', events: {next: 'after'}},
* {name: 'after', events: {next: 'last', prev: 'init'}},
* {name: 'last', events: {prev: 'after'}}
* ];
*
* const MyClass = FSM(class {
* constructor() {
* // New states MyClass can be in are added
* this.addStates(MyStates);
* }
* });
*/
addStates(states) {
states.forEach(this.addState.bind(this));
return this;
},
/**
* Returns the name of the current state or null if no state is set
*
* @method getState
* @for Mixins.FSM
* @return {string|null} Name of the current state or null
*
* @example
* import FSM from './mixins/fsm';
*
* const MyStates = [
* {name: 'init', events: {next: 'after'}},
* {name: 'after', events: {next: 'last', prev: 'init'}},
* {name: 'last', events: {prev: 'after'}}
* ];
*
* const MyClass = FSM(class {
* constructor() {
* // New states MyClass can be in are added
* this.addStates(MyStates);
* }
* });
*
* let myInstance = new MyClass();
* myInstance.getState(); // -> 'init'
*/
getState() {
return this[STATES] ? this[STATES].getState() : null;
},
/**
* Removes a state from the FSM, if it is not the active one. Please note
* that you have to take care to preserve a valid path of events to
* consume, or otherwise the FSM will be trapped in a single state
* (which may be fine if that is your intention)
*
* @method removeState
* @for Mixins.FSM
* @param {object} state The state to remove
* @return {this} The instance
*
* @example
* import FSM from './mixins/fsm';
*
* const MyStates = [
* {name: 'init', events: {next: 'after'}},
* {name: 'after', events: {next: 'last', prev: 'init'}},
* {name: 'last', events: {prev: 'after'}}
* ];
*
* const MyClass = FSM(class {
* constructor() {
* // New states MyClass can be in are added
* this.addStates(MyStates);
* this.removeState(MyStates[2]); // remove the 'last' state
* }
* });
*/
removeState(state) {
if (this[STATES]) {
this[STATES].removeState(state);
}
return this;
},
/**
* Transitions from the current state to the state specified by the given
* name of event to consume.
*
* @method consume
* @for Mixins.FSM
* @param {string} eventName Name of the event to consume
* @return {this} The instance
*
* @example
* import FSM from './mixins/fsm';
*
* const MyStates = [
* {name: 'init', events: {next: 'after'}},
* {name: 'after', events: {next: 'last', prev: 'init'}},
* {name: 'last', events: {prev: 'after'}}
* ];
*
* const MyClass = FSM(class {
* constructor() {
* // New states MyClass can be in are added
* this.addStates(MyStates);
* }
* });
*
* let myInstance = new MyClass();
* myInstance.getState(); // -> 'init'
*
* myInstance.consume('next').consume('next').getState(); // -> 'last'
*/
consume(eventName) {
if (this[STATES]) {
this[STATES].consume(eventName);
}
return this;
},
/**
* Destroys all states associated with the FSM
*
* @method destroyStates
* @for Mixins.FSM
* @return {undefined} Nothing
*
* @example
* import FSM from './mixins/fsm';
*
* const MyStates = [
* {name: 'init', events: {next: 'after'}},
* {name: 'after', events: {next: 'last', prev: 'init'}},
* {name: 'last', events: {prev: 'after'}}
* ];
*
* const MyClass = FSM(class {
* constructor() {
* // New states MyClass can be in are added
* this.addStates(MyStates);
* }
* });
*
* let myInstance = new MyClass();
* myInstance.destroyStates();
*/
destroyStates() {
if (this[STATES]) {
this[STATES].destroyStates();
this[STATES] = undefined;
}
}
});
export default FSMMixin;