Stativus is a browser-independent, framework-independent micro-library that provides full statechart functionality to your web application or web page.
A Statechart is a hierarchical finite state machine that is very useful in building and organizing user interfaces...
We are always working to constantly improve Stativus, so please submit any questions, issues or bugs at the github account...
the very basics of getting a Stativus Statechart up and running.
You will Learn
Create a directory put in the stativus.js create some simple HTML in a file called index.html fill it will the following code...
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html lang="en">
<head>
<!- I'm using JQuery, but you can use whatever -!>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
<script src="./stativus.js"></script>
</head>
<body>
<div id="state-one" style="display: none; background-color: red; color: white;">
<h1>State One (#1)</h1>
</div>
<div id="state-two" style="display: none; background-color: blue; color: white;">
<h1>State Two (#2)</h1>
</div>
<script type="text/javascript">
// Statechart code goes here...
</script>
</body>
</html>
You need to create the statechart:
var statechart = Stativus.createStatechart();
you need to define your first state:
statechart.addState("#state-one", {
enterState: function(){
$('#state-one').show(); // Using JQuery, but you can use whatever...
},
exitState: function(){
$('#state-one').hide(); // Using JQuery, but you can use whatever...
},
fireEvent: function(){
this.goToState('#state-two');
}
});
you need to define your second state:
statechart.addState("#state-two", {
enterState: function(){
$('#state-two').show(); // Using JQuery, but you can use whatever...
},
exitState: function(){
$('#state-two').hide(); // Using JQuery, but you can use whatever...
}
fireEvent: function(){
this.goToState('#state-one');
}
});
initalizing the statechart to tell Stativus where the start state is...
statechart.initStates('#state-one');
with everything put together it should look like this:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html lang="en">
<head>
<!- I'm using JQuery, but you can use whatever -!>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
<script src="./stativus.js"> </script>
</head>
<body>
<div id="state-one" style="display: none; background-color: red; color: white;">
<h1>State One (#1)</h1>
</div>
<div id="state-two" style="display: none; background-color: blue; color: white;">
<h1>State Two (#2)</h1>
</div>
<script type="text/javascript">
var statechart = Stativus.createStatechart();
statechart.addState("#state-one", {
enterState: function(){
console.log('boom');
$('#state-one').show(); // Using JQuery, but you can use whatever...
},
exitState: function(){
$('#state-one').hide(); // Using JQuery, but you can use whatever...
},
fireEvent: function(){
this.goToState('#state-two');
}
});
statechart.addState("#state-two", {
enterState: function(){
$('#state-two').show(); // Using JQuery, but you can use whatever...
},
exitState: function(){
$('#state-two').hide(); // Using JQuery, but you can use whatever...
},
fireEvent: function(){
this.goToState('#state-one');
}
});
statechart.initStates('#state-one');
|
</script>
</body>
</html>
Open the command console and run the following commands:
statechart.sendEvent('fireEvent');
createStatechart()This is the function to actually create the statechart. This is the only function that needs to be called first in order for the other functions to work. This returns a Stativus.Statechart object. You can create as many statecharts as you want. You can even create them in your views to control how the view works in any of the MVC frameworks out there
var statechart = Stativus.createStatechart();
addState( name, def1, def2, ... )This function creates the state and you pass in the definition of the state. An interesting bonus is that you can send in as many of these as you would like. This helps when you have the same action or configuration that needs to go in several states.
statechart.addState( 'first_state', {
// Stativus.State.DefinitionParameters
});
initStates( name )This actually initializes the statechart and make the statechart enter the initial state. If the 'name' is a String then it represents the name of the state in the DEFAULT statechart. If the value is an Hash | Object then it represents a key-value pair of the initial states for all the global concurrent states. global state = KEY and initial state name = VALUE
// Example #1: String
statechart.initStates('first_state');
// Example #2: Hash | Object
statechart.initStates({'default': 'first_state', 'modal_states': 'first_modal');
sendEvent( name, param1, param2, ... )This sends an event to all the global concurrent states. If you are in a state that has the event/action defined then it will respond with this trigger. You can have N-number of parameters of any type and they will be passed to the function definition for the event on the current states.
// Example #1: no params
statechart.sendEvent('viewMenu');
// Example #2: one param
statechart.sendEvent('openMenuItem', 3);
// Example #3: n-params
statechart.sendEvent('myFavoriteEvent', 3, 'Blue', {lots: 'of', 'other': 'Things'});
getState( name, globalState)This is what you call to fetch a state. The second param, 'globalState' is an optional param set to the global concurrent state name. If you don't pass anything in it defaults to 'default'.
// Example #1: no global param
var state = statechart.getState('state_one');
// Example #2: global conncurrent state param
var state = statechart.sendEvent('other_state', 'modal_states');
currentState( globalState )This is a helper function that will give you an array of all the current states that your are in. The param'globalState' is an optional param set to the global concurrent state name. If you don't pass anything in it defaults to 'default'.
// Example #1: no global param
var states = statechart.currentState();
// Example #2: global conncurrent state param
var states = statechart.currentState('modal_states');
getActiveStates( globalState)This is an alias forcurrentState( ... )
inState( globalState )This is a helper function when you are using the DEBUG_MODE version of the code. It will return true or false if you are in a state. This can be helpful for debugging. Not to be used in production. .
if ( statechart.inState('state_one', 'default') ) {
// do something ;
}
loadState: function(name, globalStatechart)This will convert the statechart to a Testing statechart, loading up the named state and permitting testing on there returned start object.
var state = sc.loadState("#testingState");
// or
var state = sc.loadState("#testingState2", "error");
createStateTree: function()This creates a tree representation of the statechart. Where each state
{
events: Array[0]
hasConcurrentSubstates: true
initialSubstate: undefined
isConcurrentSubstate: undefined
isInitialSubstate: undefined
name: "global"
substates: Array[3]
}
goToState( name, data )This is the heart and soul of the statechart. This is what starts the transition from one state to the next. This is ONLY to be called inside an event and as the last line after you do what you need to do.
When you use the optional data paramter, the data that is included will be passed along and attached to the destination state. The data paramter can be either a String or an Object.
statechart.addState('loading', {
// Stativus.State.DefinitionParameters...
Stativus.State.DefinedEvents...
completeLoad: function(data){ // <= Event Definition
// do stuff with the 'data'
if (true) this.goToState('ready', 'data');
else this.goToState('notReady', { err: 'data' });
},
// other events
});
statechart.addState('ready', {
enterState: function() {
var data = this.getData('data'); // will yield 'data'
}
// other events
});
statechart.addState('notReady', {
enterState: function() {
var data = this.getData('err'); // will yield 'data'
}
// other events
});
goToHistoryState( name )Must like goToState() This will transition to the last substate of 'stateName'. This is ONLY to be called inside an event and as the last line after you do what you need to do.
statechart.addState('loading', {
// Stativus.State.DefinitionParameters...
Stativus.State.DefinedEvents...
completeLoad: function(data){ // <= Event Definition
// do stuff with the 'data'
this.goToHistoryState('ready');
},
// other events
});
setData( key, value )This is the function that sets the local data to the state. You would use this when you need to pass/set data to be used in a state.
var state = statechart.getState('state_one');
state.setData('id', 123);
getData( key )This is the function that gets the local data to the state. You would use this when you need to retrieve local state data. The beauty of the state data storage is that it won't conflict with any other states local data.
var state = statechart.getState('state_one');
state.getData('id');
globalConcurrentStateThis is the name of the global concurrent state that this state belongs to. This is set in theaddState()function and does not ever change.
statechart.addState('state_one', {
globalConcurrentState: 'modal_states'
// more Stativus.State.DefinitionParameters...
});
parentState
This value sets who the parent state of this substate is. You do not need to add these in order, even though
its a good idea. If you do not set a value or set it to null the statechart assumes that this is a top level state. It is added once inaddState()function and never changes.
statechart.addState('substate_one', {
parentState: 'state_one'
// more Stativus.State.DefinitionParameters...
});
initialSubstateThis value sets in a parent state to tell it what substate to enter when someone callsthis.goToState('< parentStateName >'). Technically, it isn't required persay, but it bad practice to just enter a parent state with substates and not go to a substate.
This is added in the addState()function and never changes.
statechart.addState('state_one', {
initialSubstate: 'substate_one',
// more Stativus.State.DefinitionParameters...
});
substatesAreConcurrent
This value sets in a parent state to tell it that it should enter all of its substates. This gives you the fine
grain control in substates to do multiple things at the same time. You do not use initialSubstate with this because it doesn't make any sense.
This is added in the addState()function and never changes.
statechart.addState('state_one', {
substatesAreConcurrent: true,
// more Stativus.State.DefinitionParameters...
});
states
You can define substates within a state for a more concise way of defining your statechart. You do this by defining an
array of hashes with the same APIs as Stativus.State.DefinitionParameters andStativus.State.DefinitionEvents. There are two ways
to do this. This is added in the addState()function and never changes.
// Example #1:
statechart.addState('state_one', {
states: [
{
name: 'substate_one',
enterState: function(){
// define function
}
}
],
// more Stativus.State.DefinitionParameters...
});
// Example #2: n-level nesting
statechart.addState('state_one', {
states: [
{
name: 'substate_one',
enterState: function(){
// define function
},
states: [
{
name: 'sub-substate_one'
// more Stativus.State.DefinitionParameters...
}
]
}
],
// more Stativus.State.DefinitionParameters...
});
// Example #3: With shared functionality
statechart.addState('state_one', {
states: [
['#substate_one', mySharedFunctions, /* <= shared function hash >*/ {
enterState: function(){
// define function
}
}],
['#substate_two', mySharedFunctions, /* <= shared function hash >*/ {
enterState: function(){
// define function
}
}],
// more Stativus.State.DefinitionParameters...
});
enterState: function()
This is the first of the important default state events that need to be defined. This event gets called when a state
gets entered. This is optional and is added in the addState()function and never changes.
statechart.addState('state_one', {
// Stativus.State.DefinitionParameters...
enterState: function(){
// code that sets up the view or state.
},
// more Stativus.State.DefinitionEvents...
});
willEnterState: function( statechart )This is the pre-loading event to be used for async functionality. You would need to return true from this function to pause the statechart allowing the async event to happen then you need to call done() to restart the statechart transition from state to state
sc.addState("#second", {
willEnterState: function(done){
$('#content .boosh').animate({
left: 911,
complete: function () {
done(); // REQUIRED!!: call this function to
// restart the statechart transitions
}
});
return true; // REQUIRED!!: return true so Stativus knows to stop the
// transitions and wait for animation or other async code.
},
enterState: function(){ ... }
});
didEnterState: function()This is the post-loading event that you can use to do any last minute functionality
sc.addState("#second", {
didEnterState: function(){
// setup refresh timers or fire an event
}
});
exitState: function()
This is the second most important of the default state events that need to be defined. This event gets called when a state
gets exited. This is optional and is added in the addState()function and never changes.
statechart.addState('state_one', {
// Stativus.State.DefinitionParameters...
exitState: function(){
// code that tears down the view or state.
},
// more Stativus.State.DefinitionEvents...
});
willExitState: function( done )This is the pre-exit event to be used for async functionality. You would need to return true from this function to pause the statechart allowing the async event to happen then you need to call done() to restart the statechart transition from state to state
sc.addState("#second", {
willExitState: function(done){
$('#content .boosh').animate({
right: 911,
complete: function () {
done(); // REQUIRED!!: call this function to
// restart the statechart transitions
}
});
return true; // REQUIRED!!: return true so Stativus knows to stop the
// transitions and wait for animation or other async code.
},
exitState: function(){ ... }
});
didExitState: function()This is the post-exiting event that you can use to do any last minute functionality
sc.addState("#second", {
didExitState: function(){
// kill variables; fire events; or send data.
}
});
enterState: function()This is a function that will trigger an `enterState` call that can then test the state.
var state = statechart.loadState('state1');
state.enterState();
willEnterState: function(done)This is a function that will trigger an `willEnterState` call that can then test the state. Example is using `qUnit` tests
var state = SC.loadState("#async");
state.willEnterState(function(){
start();
});
stop();
willExitCompleted: function()This is a function that will return if the asynchronous call was made in the 'willEnterState' function
var state = SC.loadState("#async");
state.willEnterState(function(){
start();
ok(state.willExitCompleted(), 'Will exit async call was completed');
equal(mockObject.willExitCount, 1, 'willExitCount was successfully called');
});
stop();
exitState: function()This is a function that will trigger an `exitState` call that can then test the state.
var state = statechart.loadState('state1');
state.exitState();
willExitState: function(done)This is a function that will trigger an `willExitState` call that can then test the state. Example is using `qUnit` tests
var state = SC.loadState("#async");
state.willExitState(function(){
start();
});
stop();
willExitCompleted: function()This is a function that will return if the asynchronous call was made in the 'willEnterState' function
var state = SC.loadState("#async");
state.willExitState(function(){
start();
ok(state.willExitCompleted(), 'Will exit async call was completed');
equal(mockObject.willExitCount, 1, 'willExitCount was successfully called');
});
stop();
transitionTo: function(name)This is a function that will return true if the last action resulted in the state needing to transition to a different state
var state = SC.loadState("#async");
state.enterState(); // switch state that immediately transitions to a new state
ok(state.transitionedTo('newState'), 'state proper transition');
This is the version that is un-minified version of the code that has all of the functionality of the
console.logand errors when your code is in correctly designed. These errors include the following:
enterState() are documented with ENTER: *state_name*exitState()'s are documented with EXIT: EVENT: fired [] with argument(s) willEnterState()This is the minified version of the code for production use.
This is a special version of the code that does event propigation from user events.
/*! @license
==========================================================================
Stativus -- A Statechart Micro Library
Copyright: ©2011-2012 Evin Grano All rights reserved.
Portions ©2011-2012 Evin Grano
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
For more information about Stativus, visit http://stativ.us
==========================================================================
@license */
How often I am arrogant and impressed with myself. And then I am gently reminded:
For who sees anything different in you? What do you have that you did not receive? If then you received it, why do you boast as if you did not receive it? - (1 Corinthians 4:7 ESV)I am in need of a rescuer and that rescuer is Jesus Christ.
And there is salvation in no one else, for there is no other name under heaven given among men by which we must be saved. - (Acts 4:12 ESV)
Mike is my technical partner in crime. He did most of the development for the SproutCore Statecharts of which Stativus is derived. He has been a constant friend and pushes me to make better and bolder choices.
Erich was the first to introduce me to Statecharts and I will forever be grateful. Erich has to be one of the smartest and most well rounded technologists I have ever met.