@qooxdoo/framework
Version:
The JS Framework for Coders
438 lines (381 loc) • 13.7 kB
JavaScript
/* ************************************************************************
qooxdoo - the new era of web development
http://qooxdoo.org
Copyright:
2006, 2007, 2011 Derrell Lipman
License:
MIT: https://opensource.org/licenses/MIT
See the LICENSE file in the project's top-level directory for details.
Authors:
* Derrell Lipman (derrell)
************************************************************************ */
/**
* Create a new possible transition from one state to another.
*/
qx.Class.define("qx.util.fsm.Transition",
{
extend : qx.core.Object,
/**
* @param transitionName {String}
* The name of this transition, used in debug messages.
*
* @param transitionInfo {Object}
* <pre>
* An object optionally containing any of the following properties:
*
* context -
* A context in which all of the following functions should be run.
*
* predicate -
* A function which is called to determine whether this transition is
* acceptable. An acceptable transition will cause the transition's
* "ontransition" function to be run, the current state's "onexit"
* function to be run, and the new state's "onentry" function to be
* run.
*
* The predicate function's signature is function(fsm, event) and it
* is saved in the predicate property of the transition object. In
* the predicate function:
*
* fsm -
* The finite state machine object to which this state is
* attached.
*
* event -
* The event that caused a run of the finite state machine
*
* The predicate function should return one of the following three
* values:
*
* - true means the transition is acceptable
*
* - false means the transition is not acceptable, and the next
* transition (if one exists) should be tried to determine if it
* is acceptable
*
* - null means that the transition determined that no further
* transitions should be tried. This might be used when the
* transition ascertained that the event is for a target that is
* not available in the current state, and the event has called
* fsm.queueEvent() to have the event delivered upon state
* transition.
*
* It is possible to create a default predicate -- one that will cause
* a transition to be acceptable always -- by either not providing a
* predicate property, or by explicitly either setting the predicate
* property to 'true' or setting it to a function that unconditionally
* returns 'true'. This default transition should, of course, always
* be the last transition added to a state, since no transition added
* after it will ever be tried.
*
* nextState -
* The state to which we transition, if the predicate returns true
* (meaning the transition is acceptable). The value of nextState may
* be:
*
* - a string, the state name of the state to transition to
*
* - One of the constants:
* - qx.util.fsm.FiniteStateMachine.StateChange.CURRENT_STATE:
* Remain in whatever is the current state
* - qx.util.fsm.FiniteStateMachine.StateChange.POP_STATE_STACK:
* Transition to the state at the top of the saved-state
* stack, and remove the top element from the saved-state
* stack. Elements are added to the saved-state stack using
* fsm.pushState(). It is an error if no state exists on the
* saved-state stack.
* - qx.util.fsm.FiniteStateMachine.StateChange.TERMINATE:
* TBD
*
* autoActionsBeforeOntransition -
* autoActionsAfterOntransition -
* Automatic actions which take place at the time specified by the
* property name. In all cases, the action takes place immediately
* before or after the specified function.
*
* The property value for each of these properties is an object which
* describes some number of functions to invoke on a set of specified
* objects (typically widgets).
*
* See {@link qx.util.fsm.State} for an example of autoActions.
*
* ontransition -
* A function which is called if the predicate function for this
* transition returns true. Its signature is function(fsm, event) and
* it is saved in the ontransition property of the transition object.
* In the ontransition function:
*
* fsm -
* The finite state machine object to which this state is
* attached.
*
* event -
* The event that caused a run of the finite state machine
*
* Additional properties may be provided in transInfo. They will not be
* used by the finite state machine, but will be available via
* this.getUserData("<propertyName>") during the transition's predicate
* and ontransition functions.
* </pre>
*/
construct : function(transitionName, transitionInfo)
{
var context;
// Call our superclass' constructor
this.base(arguments);
// Save the state name
this.setName(transitionName);
// If a context was specified, retrieve it.
context = transitionInfo.context || window;
// Save it for future use
this.setUserData("context", context);
// Save data from the transitionInfo object
for (var field in transitionInfo)
{
// If we find one of our properties, call its setter.
switch(field)
{
case "predicate":
this.setPredicate(
this.__bindIfFunction(transitionInfo[field], context));
break;
case "nextState":
this.setNextState(transitionInfo[field]);
break;
case "autoActionsBeforeOntransition":
this.setAutoActionsBeforeOntransition(
this.__bindIfFunction(transitionInfo[field], context));
break;
case "autoActionsAfterOntransition":
this.setAutoActionsAfterOntransition(
this.__bindIfFunction(transitionInfo[field], context));
break;
case "ontransition":
this.setOntransition(
this.__bindIfFunction(transitionInfo[field], context));
break;
case "context":
// already handled
break;
default:
// Anything else is user-provided data for their own use. Save it.
this.setUserData(field, transitionInfo[field]);
// Log it in case it was a typo and they intended a built-in field
this.debug("Transition " + transitionName + ": " +
"Adding user-provided field to transition: " + field);
break;
}
}
},
properties :
{
/**
* The name of this transition
*/
name :
{
check : "String",
nullable : true
},
/**
* The predicate function for this transition. This is documented in the
* constructor, and is typically provided through the constructor's
* transitionInfo object, but it is also possible (but highly NOT
* recommended) to change this dynamically.
*/
predicate :
{
init : function(fsm, event)
{
return true;
},
transform : "__transformPredicate"
},
/**
* The state to transition to, if the predicate determines that this
* transition is acceptable. This is documented in the constructor, and
* is typically provided through the constructor's transitionInfo object,
* but it is also possible (but highly NOT recommended) to change this
* dynamically.
*/
nextState :
{
init : qx.util.fsm.FiniteStateMachine.StateChange.CURRENT_STATE,
transform : "__transformNextState"
},
/**
* Automatic actions to take prior to calling the transition's
* ontransition function. This is documented in the constructor, and is
* typically provided through the constructor's transitionInfo object, but
* it is also possible (but highly NOT recommended) to change this
* dynamically.
*/
autoActionsBeforeOntransition :
{
init : function(fsm, event) {},
transform : "__transformAutoActionsBeforeOntransition"
},
/**
* Automatic actions to take immediately after calling the transition's
* ontransition function. This is documented in the constructor, and is
* typically provided through the constructor's transitionInfo object, but
* it is also possible (but highly NOT recommended) to change this
* dynamically.
*/
autoActionsAfterOntransition :
{
init : function(fsm, event) {},
transform : "__transformAutoActionsAfterOntransition"
},
/**
* The function run when the transition is accepted. This is documented
* in the constructor, and is typically provided through the constructor's
* transitionInfo object, but it is also possible (but highly NOT
* recommended) to change this dynamically.
*/
ontransition :
{
init : function(fsm, event) {},
transform : "__transformOntransition"
}
},
members:
{
/**
* Validate the predicate. Converts all incoming values to functions.
*
* @param value {var} incoming value
* @return {Function} predicate function
*/
__transformPredicate : function(value)
{
// Validate the predicate. Convert all valid types to function.
switch(typeof (value))
{
case "undefined":
// No predicate means predicate passes
return function(fsm, event)
{
return true;
};
case "boolean":
// Convert boolean predicate to a function which returns that value
return function(fsm, event)
{
return value;
};
case "function":
// Use user-provided function.
return qx.lang.Function.bind(value, this.getUserData("context"));
default:
throw new Error("Invalid transition predicate type: " +
typeof (value));
}
},
/**
* Internal transform method
*
* @param value {var} Current value
* @return {Function} the final value
*/
__transformNextState : function(value)
{
// Validate nextState. It must be a string or a number.
switch(typeof (value))
{
case "string":
return value;
case "number":
// Ensure that it's one of the possible state-change constants
switch(value)
{
case qx.util.fsm.FiniteStateMachine.StateChange.CURRENT_STATE:
case qx.util.fsm.FiniteStateMachine.StateChange.POP_STATE_STACK:
case qx.util.fsm.FiniteStateMachine.StateChange.TERMINATE:
return value;
default:
throw new Error("Invalid transition nextState value: " +
value + ": " +
"nextState must be an explicit state name, " +
"or one of the Fsm.StateChange constants");
}
break;
default:
throw new Error("Invalid transition nextState type: " +
typeof (value));
}
},
/**
* Internal transform method
*
* @param value {var} Current value
* @return {Function} the final value
*/
__transformAutoActionsBeforeOntransition : function(value)
{
return qx.util.fsm.State._commonTransformAutoActions(
"autoActionsBeforeOntransition",
value,
this.getUserData("context"));
},
/**
* Internal transform method
*
* @param value {var} Current value
* @return {Function} the final value
*/
__transformAutoActionsAfterOntransition : function(value)
{
return qx.util.fsm.State._commonTransformAutoActions(
"autoActionsAfterOntransition",
value,
this.getUserData("context"));
},
/**
* Internal transform method
*
* @param value {var} Current value
* @return {Function} the final value
*/
__transformOntransition : function(value)
{
// Validate the ontransition function. Convert undefined to function.
switch(typeof (value))
{
case "undefined":
// No provided function just means do nothing. Use a null
// function.
return function(fsm, event) {};
case "function":
// Use user-provided function.
return qx.lang.Function.bind(value, this.getUserData("context"));
default:
throw new Error("Invalid ontransition type: " + typeof (value));
}
},
/**
* If given a function, bind it to a specified context.
*
* @param f {Function|var}
* The (possibly) function to be bound to the specified context.
*
* @param context {Object}
* The context to bind the function to.
*
* @return {Function}
* If f was a function, the return value is f wrapped such that it will
* be called in the specified context. Otherwise, f is returned
* unaltered.
*/
__bindIfFunction : function(f, context)
{
// Is the first parameter a function?
if (typeof(f) == "function")
{
// Yup. Bind it to the specified context.
f = qx.lang.Function.bind(f, context);
}
return f;
}
}
});