Skip to content

Instantly share code, notes, and snippets.

@whatupdave
Created March 2, 2013 23:53
Show Gist options
  • Save whatupdave/5073808 to your computer and use it in GitHub Desktop.
Save whatupdave/5073808 to your computer and use it in GitHub Desktop.
An old javascript state machine implementation
/*
Possible names: Reaktor, Fat Controller
ideas:
bind/unbind between states?
allow machines to be shared, overriding states on instance
$('#target').machine({
stopped : {
enter : ['a.pause:hide', 'a.play:show'],
exit : ...,
'a.play:click' : 'playing'
},
playing : {
'a.pause:click' : ['a.pause:hide', 'a.play:show', 'paused'],
'a.stop:click' : 'stopped'
},
paused : {
'a.play:click' : 'playing',
'a.stop:click' : 'stopped'
}
},{ debug : true})
state names
actions
are jquery events in the form selector:event
transitions
can be string containing new state or function that returns new state
can be an array of actions, the last one providing the new state
*/
(function($) {
var RESERVED_STATES = ['enter', 'exit'];
$.fn.machine = function(states, options) {
options = $.extend({
error: true,
debug: false
}, options || {});
var name = this.selector;
if (typeof console === 'undefined') {
console = { log: function(){} };
}
var log = {
debug: function(message) { if (options.debug) console.log('Machine['+name+']: ' + message); },
error: function(message) { if (options.error) console.log('Machine['+name+'] Error: ' + message); }
}
return this.each(function() {
var self = this;
var currentState = first(states).index;
log.debug('State: ' + currentState);
function executeCallbacks(callbacks, args) {
if (typeof callbacks === 'undefined') return;
if (typeof callbacks === "function") {
return callbacks.apply(self, args);
} else if (typeof callbacks === "string") {
if (callbacks.indexOf(':') > -1) {
var parts = callbacks.split(':');
var selector = parts[0];
var method = parts[1];
if ($(selector, self).length == 0) {
log.error('Missing element ' + selector);
}
if ($(selector, self)[method]) {
($(selector, self)[method]).apply($(selector, self), args);
} else { log.error('Undefined method: ' + method)}
} else if (callbacks.indexOf('!') > -1) {
var event = callbacks.split('!')[1];
$(self).trigger(event);
}
return callbacks;
} else if (callbacks.length) {
var results = $.map(callbacks, function(v){
return executeCallbacks(v, args);
});
return last(results);
}
}
function changeState(newState, action) {
log.debug('transitioning ' + currentState + ' -[' + action + ']-> ' + newState)
executeCallbacks(states[currentState]['exit'], []);
currentState = newState;
executeCallbacks(states[currentState]['enter'], []);
log.debug('State: ' + currentState);
}
function eventTriggered(action, args) {
log.debug(action + ' triggered');
var newState = executeCallbacks(states[currentState][action], args);
if (newState && states[newState]) {
changeState(newState, action);
}
}
var bindings = [];
$.each(states, function(state, transition){
$.each(transition, function(action, newState) {
if ($.inArray(action, RESERVED_STATES) > -1) return;
var selector = '';
var event = action;
var element = $(self);
if(action.indexOf(':') > -1) {
var parts = action.split(':');
selector = parts[0];
event = parts[1];
}
if(selector.indexOf('*') == 0) {
element = $(selector.split('*')[1]);
} else if (selector) {
element = $(selector, self);
}
var eventId = selector + ':' + event;
if (element.length == 0) {
log.error(action + ' does not exist');
} else if ($.inArray(eventId, bindings) == -1) {
log.debug('binding ' + eventId);
bindings.push(eventId);
element.bind(event, function(){
var args = Array.prototype.slice.call(arguments);
eventTriggered(action, args);
});
}
});
});
var newState = executeCallbacks(states[currentState]['enter'], []);
if (newState && states[newState]) {
changeState(newState, 'enter');
}
var url = window.location.toString();
if (url.indexOf('?') > -1) {
var params = url.split('?')[1].split('&');
$.each(params, function(i, v){
$(self).trigger('?' + v.split('=')[0]);
});
}
});
};
function last(array) {
if (array && array.length && array.length > 0) return array[array.length - 1];
}
function first(object){
var returnIndex;
var returnValue;
$.each(object, function(index, value){
returnIndex = index;
returnValue = value;
return false;
});
return {index: returnIndex, value: returnValue};
}
})(jQuery);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment