Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save camwest/642749 to your computer and use it in GitHub Desktop.
Save camwest/642749 to your computer and use it in GitHub Desktop.
A simple implementation of view states for Backbone.js
// (c) 2010 Cameron Westland, Big Bang Technology Inc.
// Backbone may be freely distributed under the MIT license.
// For all details and documentation:
// http://bigbangtechnology.github.com/stately
/**
Example:
var MyView = Backbone.View.extend(Stately).extend({
states: {
EDITING: "editing",
NORMAL: "normal"
},
transitions: {
"editing": {
"enter_state": function() {
console.log("EDITING: enter_state");
},
"render_state": function() {
console.log("EDITING: render_state");
}
}
},
render: function() {
this.revalidateState(function(){
// render the generic view here
});
},
getState: function() {
if (this.currentState == this.states.EDITING) {
return this.states.NORMAL;
} else {
return this.states.EDITING;
}
}
});
**/
var Stately = function() {
return {
VERSION: "0.0.1",
currentState: "",
/**
Revalidate state is a function that you call when you want to transition
into a new state potentially. It takes a single callback function as argument
which will execute once the state has been set but BEFORE the render_state function
is called for the given state.
**/
revalidateState: function(callback) {
$(this.el).removeClass();
this.currentState = this.getState();
if (this.currentState == null || !(this.currentState.constructor == String)) {
throw("getState must return a valid state");
}
$(this.el).addClass(this.currentState);
this.executeTransition("enter_state");
if (callback instanceof Function) {
callback.call(this);
}
this.executeTransition("render_state");
},
/**
Executes a transition by name if it is defined in the View.
Stately will automatically execute the enter_state and render_state
transitions if they are declared, custom transitions can be written
by declaring them and then running executeTransition manually
**/
executeTransition: function(transitionName) {
if (this.hasTransition(transitionName)) {
this.transitions[this.currentState][transitionName].call(this);
}
},
/**
A helper method to check to see if the transition is defined
for the current state
**/
hasTransition: function(transitionName) {
if (this.transitions.hasOwnProperty(this.currentState) && this.transitions[this.currentState].hasOwnProperty(transitionName)) {
return true;
} else {
return false;
}
},
/**
Sub classes must override getState and return a string. getState is called
automatically when executing revalidateState
**/
getState: function() {
// throws an error if this is not overwritten
throw("Views must implement getState");
}
};
}();
@rxgx
Copy link

rxgx commented May 2, 2011

If you're going to use an object with keywords, why not set them up as a bitmask? For example, { DEFAULT: 0x0, EDITING: 0x1 }.

@camwest
Copy link
Author

camwest commented May 3, 2011

Rgxgx. The benefits of bit masking is just that it is more consise?

@rxgx
Copy link

rxgx commented May 3, 2011

Yes, because you can then test for multiple conditions within a given state using AND (&), OR (|) and NOR (^). For example, am I editing (0x1), have changes (0x2) and active (0x4), would be something like:

if (currentState & EDITING & CHANGED & ACTIVE) {
  doSomething();
} else {
  orNot();
}

@camwest
Copy link
Author

camwest commented May 3, 2011

Thanks that makes perfect sense! Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment