1 /* ************************************************************************
3 qooxdoo - the new era of web development
8 2006, 2007 by Derrell Lipman
11 LGPL 2.1: http://www.gnu.org/licenses/lgpl.html
14 * Derrell Lipman (derrell)
16 ************************************************************************ */
18 /* ************************************************************************
22 ************************************************************************ */
25 * A finite state machine.
27 * See {@link qx.util.finitestatemacine.State} for details on creating States,
28 * and {@link qx.util.finitestatemacine.Transitions} for details on creating
29 * transitions between states.
31 * @param machineName {String} The name of this finite state machine
34 qx.OO.defineClass("qx.util.fsm.FiniteStateMachine", qx.core.Target,
37 // Call our superclass' constructor
38 qx.core.Target.call(this);
40 // Save the machine name
41 this.setName(machineName);
43 // Initialize the states object
46 // Initialize the saved-states stack
47 this._savedStates = [ ];
49 // Initialize the pending event queue
50 this._eventQueue = [ ];
52 // Initialize the blocked events queue
53 this._blockedEvents = [ ];
55 // Create the friendlyToObject" object. Each object has as its property
56 // name, the friendly name of the object; and as its property value, the
58 this._friendlyToObject = { };
60 // Create the "friendlyToHash" object. Each object has as its property
61 // name, the friendly name of the object; and as its property value, the
62 // hash code of the object.
63 this._friendlyToHash = { };
65 // Create the "hashToFriendly" object. Each object has as its property
66 // name, the hash code of the object; and as its property value, the
67 // friendly name of the object.
68 this._hashToFriendly = { };
70 // Friendly names can be added to groups, for easy manipulation of enabling
71 // and disabling groups of widgets. Track which friendly names are in which
73 this._groupToFriendly = { };
75 // We also need to be able to map back from friendly name to the groups it
77 this._friendlyToGroups = { };
82 ---------------------------------------------------------------------------
84 ---------------------------------------------------------------------------
88 * The name of this finite state machine (for debug messages)
97 * The current state of the finite state machine.
106 * The previous state of the finite state machine, i.e. the state from which
107 * we most recently transitioned. Note that this could be the same as the
108 * current state if a successful transition brought us back to the same
113 name : "previousState",
118 * The state to which we will be transitioning. This property is valid only
119 * during a Transition's ontransition function and a State's onexit function.
120 * At all other times, it is null.
130 * The maximum number of states which may pushed onto the state-stack. It is
131 * generally a poor idea to have very many states saved on a stack. Following
132 * program logic becomes very difficult, and the code can be highly
133 * unmaintainable. The default should be more than adequate. You've been
138 name : "maxSavedStates",
144 ---------------------------------------------------------------------------
146 ---------------------------------------------------------------------------
151 ---------------------------------------------------------------------------
153 ---------------------------------------------------------------------------
158 * Add a state to the finite state machine.
160 * @param state {qx.util.fsm.State}
161 * An object of class qx.util.fsm.State representing a state
162 * which is to be a part of this finite state machine.
164 qx.Proto.addState = function(state)
166 // Ensure that we got valid state info
167 if (! state instanceof qx.util.fsm.State)
169 throw new Error("Invalid state: not an instance of " +
170 "qx.util.fsm.State");
173 // Retrieve the name of this state
174 var stateName = state.getName();
176 // Ensure that the state name doesn't already exist
177 if (stateName in this._states)
179 throw new Error("State " + state + " already exists");
182 // Add the new state object to the finite state machine
183 this._states[stateName] = state;
188 * Replace a state in the finite state machine. This is useful if initially
189 * "dummy" states are created which load the real state table for a series of
190 * operations (and possibly also load the gui associated with the new states
191 * at the same time). Having portions of the finite state machine and their
192 * associated gui pages loaded at run time can help prevent long delays at
193 * application start-up time.
195 * @param state {qx.util.fsm.State}
196 * An object of class qx.util.fsm.State representing a state
197 * which is to be a part of this finite state machine.
199 * @param bDispose {Boolean}
200 * If <i>true</i>, then dispose the old state object. If <i>false</i>, the
201 * old state object is returned for disposing by the caller.
204 * The old state object if it was not disposed; otherwise null.
206 qx.Proto.replaceState = function(state, bDispose)
208 // Ensure that we got valid state info
209 if (! state instanceof qx.util.fsm.State)
211 throw new Error("Invalid state: not an instance of " +
212 "qx.util.fsm.State");
215 // Retrieve the name of this state
216 var stateName = state.getName();
218 // Save the old state object, so we can return it to be disposed
219 var oldState = this._states[stateName];
221 // Replace the old state with the new state object.
222 this._states[stateName] = state;
224 // Did they request that the old state be disposed?
227 // Yup. Mark it to be disposed.
228 oldState._needDispose;
237 * Add an object (typically a widget) that is to be accessed during state
238 * transitions, to the finite state machine.
240 * @param friendlyName {String}
241 * The friendly name to used for access to the object being added.
243 * @param obj {Object}
244 * The object to associate with the specified friendly name
246 * @param groupNames {Array}
247 * An optional list of group names of which this object is a member.
249 qx.Proto.addObject = function(friendlyName, obj, groupNames)
251 var hash = obj.toHashCode();
252 this._friendlyToHash[friendlyName] = hash;
253 this._hashToFriendly[hash] = friendlyName;
254 this._friendlyToObject[friendlyName] = obj;
256 // If no groupNames are specified, we're done.
262 // Allow either a single group name or an array of group names. If the
263 // former, we convert it to the latter to make the subsequent code simpler.
264 if (typeof(groupNames) == "string")
266 groupNames = [ groupNames ];
269 // For each group that this friendly name is to be a member of...
270 for (var i = 0; i < groupNames.length; i++)
272 var groupName = groupNames[i];
274 // If the group name doesn't yet exist...
275 if (! this._groupToFriendly[groupName])
277 // ... then create it.
278 this._groupToFriendly[groupName] = { };
281 // Add the friendly name to the list of names in this group
282 this._groupToFriendly[groupName][friendlyName] = true;
284 // If the friendly name group mapping doesn't yet exist...
285 if (! this._friendlyToGroups[friendlyName])
287 // ... then create it.
288 this._friendlyToGroups[friendlyName] = [ ];
291 // Append this group name to the list of groups this friendly name is in
292 this._friendlyToGroups[friendlyName] =
293 this._friendlyToGroups[friendlyName].concat(groupNames);
299 * Remove an object which had previously been added by {@link #addObject}.
301 * @param friendlyName {String}
302 * The friendly name associated with an object, specifying which object is
305 qx.Proto.removeObject = function(friendlyName)
307 var hash = this._friendlyToHash[friendlyName];
309 // Delete references to any groupos this friendly name was in
310 if (this._friendlyToGroups[friendlyName])
312 for (groupName in this._friendlyToGroups[friendlyName])
314 delete this._groupToFriendly[groupName];
317 delete this._friendlyToGroups[friendlyName];
320 // Delete the friendly name
321 delete this._hashToFriendly[hash];
322 delete this._friendlyToHash[friendlyName];
323 delete this._friendlyToObject[friendlyName];
328 * Retrieve an object previously saved via {@link #addObject}, using its
331 * @param friendlyName {String}
332 * The friendly name of the object to be retrieved.
335 * The object which has the specified friendly name, or undefined if no
336 * object has been associated with that name.
338 qx.Proto.getObject = function(friendlyName)
340 return this._friendlyToObject[friendlyName];
345 * Get the friendly name of an object.
347 * @param obj {Object} The object for which the friendly name is desired
350 * If the object has been previously registered via {@link #addObject}, then
351 * the friendly name of the object is returned; otherwise, null.
353 qx.Proto.getFriendlyName = function(obj)
355 var hash = obj.toHashCode();
356 return hash ? this._hashToFriendly[hash] : null;
361 * Retrieve the list of objects which have registered, via {@link addObject} as
362 * being members of the specified group.
364 * @param groupName {String}
365 * The name of the group for which the member list is desired.
368 * An array containing the friendly names of any objects which are members
369 * of the specified group. The resultant array may be empty.
371 qx.Proto.getGroupObjects = function(groupName)
375 for (var name in this._groupToFriendly[groupName])
385 * Display all of the saved objects and their reverse mappings.
387 qx.Proto.displayAllObjects = function()
389 for (var friendlyName in this._friendlyToHash)
391 var hash = this._friendlyToHash[friendlyName];
392 var obj = this.getObject(friendlyName);
393 this.debug(friendlyName +
396 this.debug(" " + hash +
398 this._hashToFriendly[hash]);
399 this.debug(" " + friendlyName +
401 this.getObject(friendlyName));
402 this.debug(" " + this.getObject(friendlyName) +
404 this.getFriendlyName(obj));
410 * Recursively display an object (as debug messages)
412 * @param obj {Object}
413 * The object to be recursively displayed
415 qx.Proto.debugObject = function(obj, initialMessage)
419 var displayObj = function(obj, level)
422 for (var i = 0; i < level; i++)
427 if (typeof(obj) != "object")
429 thisClass.debug(indentStr, obj);
433 for (var prop in obj)
435 if (typeof(obj[prop]) == "object")
437 if (obj[prop] instanceof Array)
439 thisClass.debug(indentStr + prop + ": " + "Array");
443 thisClass.debug(indentStr + prop + ": " + "Object");
446 displayObj(obj[prop], level + 1);
450 thisClass.debug(indentStr + prop + ": " + obj[prop]);
457 this.debug(initialMessage);
466 * Start (or restart, after it has terminated) the finite state machine from
467 * the starting state. The starting state is defined as the first state added
468 * to the finite state machine.
470 qx.Proto.start = function()
474 // Set the start state to be the first state which was added to the machine
475 for (stateName in this._states)
477 this.setState(stateName);
478 this.setPreviousState(null);
479 this.setNextState(null);
485 throw new Error("Machine started with no available states");
489 (qx.Settings.getValueOfClass("qx.util.fsm.FiniteStateMachine",
491 qx.util.fsm.FiniteStateMachine.DebugFlags.FUNCTION_DETAIL);
493 // Run the actionsBeforeOnentry actions for the initial state
496 this.debug(this.getName() + "#" + stateName + "#actionsBeforeOnentry");
498 this._states[stateName].getAutoActionsBeforeOnentry()(this);
500 // Run the entry function for the new state, if one is specified
503 this.debug(this.getName() + "#" + stateName + "#entry");
505 this._states[stateName].getOnentry()(this, null);
507 // Run the actionsAfterOnentry actions for the initial state
510 this.debug(this.getName() + "#" + stateName + "#actionsAfterOnentry");
512 this._states[stateName].getAutoActionsAfterOnentry()(this);
518 * Save the current or previous state on the saved-state stack. A future
519 * transition can then provide, as its nextState value, the class constant:
521 * qx.util.fsm.FiniteStateMachine.StateChange.POP_STATE_STACK
523 * which will cause the next state to be whatever is at the top of the
524 * saved-state stack, and remove that top element from the saved-state stack.
526 * @param bCurrent {Boolean}
527 * When <i>true</i>, then push the current state onto the stack. This might
528 * be used in a transition, before the state has changed. When
529 * <i>false</i>, then push the previous state onto the stack. This might be
530 * used in an on entry function to save the previous state to return to.
532 qx.Proto.pushState = function(bCurrent)
534 // See if there's room on the state stack for a new state
535 if (this._savedStates.length >= this.getMaxSavedStates())
537 // Nope. Programmer error.
538 throw new Error("Saved-state stack is full");
543 // Push the current state onto the saved-state stack
544 this._savedStates.push(this.getState());
548 // Push the previous state onto the saved-state stack
549 this._savedStates.push(this.getPreviousState());
555 * Add the specified event to a list of events to be passed to the next state
556 * following state transition.
558 * @param event {qx.event.type.Event}
559 * The event to add to the event queue for processing after state change.
561 qx.Proto.postponeEvent = function(event)
563 // Add this event to the blocked event queue, so it will be passed to the
564 // next state upon transition.
565 this._blockedEvents.unshift(event);
572 * @param event {qx.event.type.Event}
573 * The event to be copied
575 * @return {qx.event.type.Event}
576 * The new copy of the provided event
578 qx.Proto.copyEvent = function(event)
581 for (var prop in event)
583 e[prop] = event[prop];
591 * Enqueue an event for processing
593 * @param event {qx.event.type.Event}
594 * The event to be enqueued
596 * @param bAddAtHead {Boolean}
597 * If <i>true</i>, put the event at the head of the queue for immediate
598 * processing. If <i>false</i>, place the event at the tail of the queue so
599 * that it receives in-order processing.
601 qx.Proto.enqueueEvent = function(event, bAddAtHead)
603 // Add the event to the event queue
606 // Put event at the head of the queue
607 this._eventQueue.push(event);
611 // Put event at the tail of the queue
612 this._eventQueue.unshift(event);
615 if (qx.Settings.getValueOfClass("qx.util.fsm.FiniteStateMachine",
617 qx.util.fsm.FiniteStateMachine.DebugFlags.EVENTS)
621 this.debug(this.getName() + ": Pushed event: " + event.getType());
625 this.debug(this.getName() + ": Queued event: " + event.getType());
632 * Event listener for all event types in the finite state machine
634 * @param event {qx.event.type.Event}
635 * The event that was dispatched.
637 qx.Proto.eventListener = function(event)
639 // Events are enqueued upon receipt. Some events are then processed
640 // immediately; other events get processed later. We need to allow the
641 // event dispatcher to free the source event upon our return, so we'll clone
642 // it and enqueue our clone. The source event can then be disposed upon our
644 var e = this.copyEvent(event);
646 // Enqueue the new event on the tail of the queue
647 this.enqueueEvent(e, false);
650 this._processEvents();
655 * Process all of the events on the event queue.
657 qx.Proto._processEvents = function()
659 // eventListener() can potentially be called while we're processing events
660 if (this._eventProcessingInProgress)
662 // We were processing already, so don't process concurrently.
666 // Track that we're processing events
667 this._eventProcessingInProgress = true;
669 // Process each of the events on the event queue
670 while (this._eventQueue.length > 0)
672 // Pull the next event from the pending event queue
673 var event = this._eventQueue.pop();
675 // Run the finite state machine with this event
676 var bDispose = this._run(event);
678 // If we didn't block (and re-queue) the event, dispose it.
685 // We're no longer processing events
686 this._eventProcessingInProgress = false;
690 * Run the finite state machine to process a single event.
692 * @param event {qx.event.type.Event}
693 * An event that has been dispatched. The event may be handled (if the
694 * current state handles this event type), queued (if the current state
695 * blocks this event type), or discarded (if the current state neither
696 * handles nor blocks this event type).
699 * Whether the event should be disposed. If it was blocked, we've pushed it
700 * back onto the event queue, and it should not be disposed.
702 qx.Proto._run = function(event)
704 // For use in generated functions...
707 // State name variables
712 // The current State object
715 // The transitions available in the current State
718 // Events handled by the current State
721 // The action to take place upon receipt of a particular event
724 // Get the debug flags
726 (qx.Settings.getValueOfClass("qx.util.fsm.FiniteStateMachine",
729 // Allow slightly faster access to determine if debug is enableda
731 debugFlags & qx.util.fsm.FiniteStateMachine.DebugFlags.EVENTS;
732 var debugTransitions =
733 debugFlags & qx.util.fsm.FiniteStateMachine.DebugFlags.TRANSITIONS;
735 debugFlags & qx.util.fsm.FiniteStateMachine.DebugFlags.FUNCTION_DETAIL;
736 var debugObjectNotFound =
737 debugFlags & qx.util.fsm.FiniteStateMachine.DebugFlags.OBJECT_NOT_FOUND;
741 this.debug(this.getName() + ": Process event: " + event.getType());
744 // Get the current state name
745 thisState = this.getState();
747 // Get the current State object
748 currentState = this._states[thisState];
750 // Get a list of the transitions available from this state
751 transitions = currentState.transitions;
753 // Determine how to handle this event
754 e = currentState.getEvents()[event.getType()];
756 // See if we actually found this event type
761 this.debug(this.getName() + ": Event '" + event.getType() + "'" +
762 " not handled. Ignoring.");
767 // We might have found a constant (PREDICATE or BLOCKED) or an object with
768 // each property name being the friendly name of a saved object, and the
769 // property value being one of the constants (PREDICATE or BLOCKED).
770 if (typeof(e) == "object")
772 // Individual objects are listed. Ensure target is a saved object
773 var friendly = this.getFriendlyName(event.getTarget());
776 // Nope, it doesn't seem so. Just discard it.
777 if (debugObjectNotFound)
779 this.debug(this.getName() + ": Could not find friendly name for '" +
780 event.getType() + "' on '" + event.getTarget() + "'");
785 action = e[friendly];
794 case qx.util.fsm.FiniteStateMachine.EventHandling.PREDICATE:
795 // Process this event. One of the transitions should handle it.
798 case qx.util.fsm.FiniteStateMachine.EventHandling.BLOCKED:
799 // This event is blocked. Enqueue it for later, and get outta here.
802 this.debug(this.getName() + ": Event '" + event.getType() + "'" +
803 " blocked. Re-queuing.");
805 this._blockedEvents.unshift(event);
809 // See if we've been given an explicit transition name
810 if (typeof(action) == "string")
812 // Yup! Ensure that it exists
813 if (transitions[action])
815 // Yup. Create a transitions object containing only this transition.
816 var trans = transitions[action];
818 transitions[action] = trans;
822 throw new Error("Explicit transition " + action + " does not exist");
829 // We handle the event. Try each transition in turn until we find one that
831 for (var t in transitions)
833 var trans = transitions[t];
835 // Does the predicate allow use of this transition?
836 switch(trans.getPredicate()(this, event))
839 // Transition is allowed. Proceed.
843 // Transition is not allowed. Try next transition.
847 // Transition indicates not to try further transitions
851 throw new Error("Transition " + thisState + ":" + t +
852 " returned a value other than true, false, or null.");
855 // We think we can transition to the next state. Set next state.
856 nextState = trans.getNextState();
857 if (typeof(nextState) == "string")
859 // We found a literal state name. Ensure it exists.
860 if (! nextState in this._states)
862 throw new Error("Attempt to transition to nonexistent state " +
866 // It exists. Track it being the next state.
867 this.setNextState(nextState);
871 // If it's not a string, nextState must be a StateChange constant
874 case qx.util.fsm.FiniteStateMachine.StateChange.CURRENT_STATE:
875 // They want to remain in the same state.
876 nextState = thisState;
877 this.setNextState(nextState)
880 case qx.util.fsm.FiniteStateMachine.StateChange.POP_STATE_STACK:
881 // Switch to the state at the top of the state stack.
882 if (this._savedStates.length == 0)
884 throw new Error("Attempt to transition to POP_STATE_STACK " +
885 "while state stack is empty.");
888 // Pop the state stack to retrieve the state to transition to
889 nextState = this._savedStates.pop();
890 this.setNextState(nextState);
894 throw new Error("Internal error: invalid nextState");
899 // Run the actionsBeforeOntransition actions for this transition
902 this.debug(this.getName() + "#" + thisState + "#" + t +
903 "#autoActionsBeforeOntransition");
905 trans.getAutoActionsBeforeOntransition()(this);
907 // Run the 'ontransition' function
910 this.debug(this.getName() + "#" + thisState + "#" + t + "#ontransition");
912 trans.getOntransition()(this, event);
914 // Run the autoActionsAfterOntransition actions for this transition
917 this.debug(this.getName() + "#" + thisState + "#" + t +
918 "#autoActionsAfterOntransition");
920 trans.getAutoActionsAfterOntransition()(this);
922 // Run the autoActionsBeforeOnexit actions for the old state
925 this.debug(this.getName() + "#" + thisState +
926 "#autoActionsBeforeOnexit");
928 currentState.getAutoActionsBeforeOnexit()(this);
930 // Run the exit function for the old state
933 this.debug(this.getName() + "#" + thisState + "#exit");
935 currentState.getOnexit()(this, event);
937 // Run the autoActionsAfterOnexit actions for the old state
940 this.debug(this.getName() + "#" + thisState + "#autoActionsAfterOnexit");
942 currentState.getAutoActionsAfterOnexit()(this);
944 // If this state has been replaced and we're supposed to dispose it...
945 if (currentState._needDispose)
947 // ... then dispose it now that it's no longer in use
948 currentState.dispose();
951 // Reset currentState to the new state object
952 currentState = this._states[this.getNextState()];
954 // set previousState and state, and clear nextState, for transition
955 this.setPreviousState(thisState);
956 this.setState(this.getNextState());
957 this.setNextState(null);
958 prevState = thisState;
959 thisState = nextState;
960 nextState = undefined;
962 // Run the autoActionsBeforeOnentry actions for the new state
965 this.debug(this.getName() + "#" + thisState +
966 "#autoActionsBeforeOnentry");
968 currentState.getAutoActionsBeforeOnentry()(this);
970 // Run the entry function for the new state, if one is specified
973 this.debug(this.getName() + "#" + thisState + "#entry");
975 currentState.getOnentry()(this, event);
977 // Run the autoActionsAfterOnentry actions for the new state
980 this.debug(this.getName() + "#" + thisState +
981 "#autoActionsAfterOnentry");
983 currentState.getAutoActionsAfterOnentry()(this);
985 // Add any blocked events back onto the pending event queue
987 for (var i = 0; i < this._blockedEvents.length; i++)
989 e = this._blockedEvents.pop();
990 this._eventQueue.unshift(e);
993 // Ensure that all actions have been flushed
994 qx.ui.core.Widget.flushGlobalQueues();
996 if (debugTransitions)
998 this.debug(this.getName() + "#" + prevState + " => " +
999 this.getName() + "#" + thisState);
1006 if (debugTransitions)
1008 this.debug(this.getName() + "#" + thisState +
1009 ": event '" + event.getType() + "'" +
1010 ": no transition found. No state change.");
1019 ---------------------------------------------------------------------------
1021 ---------------------------------------------------------------------------
1027 ---------------------------------------------------------------------------
1029 ---------------------------------------------------------------------------
1033 * Constants which may be values of the nextState member in the transitionInfo
1034 * parameter of the Transition constructor.
1036 qx.Class.StateChange =
1038 /** When used as a nextState value, means remain in current state */
1041 /** When used as a nextState value, means go to most-recently pushed state */
1042 POP_STATE_STACK : 2,
1044 /** When used as a nextState value, means terminate this state machine */
1050 * Constants for use in the events member of the transitionInfo parameter of
1051 * the Transition constructor.
1053 qx.Class.EventHandling =
1056 * This event is handled by this state, but the predicate of a transition
1057 * will determine whether to use that transition.
1061 /** Enqueue this event for possible use by the next state */
1066 * Debug bitmask values. Set the debug flags from the application by or-ing
1067 * together bits, akin to this:
1069 * qx.Settings.setCustomOfClass(
1070 * "qx.util.fsm.FiniteStateMachine",
1072 * (qx.util.fsm.FiniteStateMachine.DebugFlags.EVENTS |
1073 * qx.util.fsm.FiniteStateMachine.DebugFlags.TRANSITIONS |
1074 * qx.util.fsm.FiniteStateMachine.DebugFlags.FUNCTION_DETAIL |
1075 * qx.util.fsm.FiniteStateMachine.DebugFlags.OBJECT_NOT_FOUND));
1077 qx.Class.DebugFlags =
1082 /** Show transitions */
1085 /** Show individual function invocations during transitions */
1086 FUNCTION_DETAIL : 4,
1088 /** When object friendly names are referenced but not found, show message */
1089 OBJECT_NOT_FOUND : 8
1094 ---------------------------------------------------------------------------
1095 CLASS DEFAULT SETTINGS
1096 ---------------------------------------------------------------------------
1100 * Debug flags: bitmap of DebugFlags (see Class Constants).
1102 qx.Settings.setDefault(
1104 (qx.util.fsm.FiniteStateMachine.DebugFlags.EVENTS |
1105 qx.util.fsm.FiniteStateMachine.DebugFlags.TRANSITIONS |
1106 qx.util.fsm.FiniteStateMachine.DebugFlags.OBJECT_NOT_FOUND));
1110 ---------------------------------------------------------------------------
1112 ---------------------------------------------------------------------------
1116 * Common function used by {qx.util.fsm.State} and
1117 * {qx.util.fsm.Transition} for checking the value provided for
1120 * Auto-action property values passed to us look akin to:
1124 * // The name of a function.
1128 * // The parameter value(s), thus "setEnabled(true);"
1129 * "parameters" : [ true ],
1131 * // The function would be called on each object:
1132 * // this.getObject("obj1").setEnabled(true);
1133 * // this.getObject("obj2").setEnabled(true);
1134 * "objects" : [ "obj1", "obj2" ]
1136 * // And similarly for each object in each specified group.
1137 * "groups" : [ "group1", "group2" ],
1144 * "parameters" : [ "blue" ]
1145 * "groups" : [ "group3", "group4" ],
1146 * "objects" : [ "obj3", "obj4" ]
1152 * @param actionType {String}
1153 * The name of the action being validated (for debug messages)
1155 * @param propValue {Object}
1156 * The property value which is being validated
1161 qx.Class._commonCheckAutoActions = function(actionType, propValue, propData)
1163 // Validate that we received an object property value
1164 if (typeof(propValue) != "object")
1166 throw new Error("Invalid " + actionType + " value: " + typeof(propValue));
1169 // We'll create a function to do the requested actions. Initialize the
1170 // string into which we'll generate the common fragment added to the
1171 // function for each object.
1174 // Here, we'll keep the function body. Initialize a try block.
1180 var objectAndGroupList;
1182 // Retrieve the function request, e.g.
1184 for (var f in propValue)
1186 // Get the function request value object, e.g.
1190 // "parameters" : [ true ],
1191 // "objects" : [ "obj1", "obj2" ]
1192 // "groups" : [ "group1", "group2" ],
1195 var functionRequest = propValue[f];
1197 // The function request value should be an object
1198 if (! functionRequest instanceof Array)
1200 throw new Error("Invalid function request type: " +
1201 "expected array, found " + typeof(functionRequest));
1204 // For each function request...
1205 for (var i = 0; i < functionRequest.length; i++)
1207 // Retreive the object and group list object
1208 objectAndGroupList = functionRequest[i];
1210 // The object and group list should be an object, e.g.
1212 // "parameters" : [ true ],
1213 // "objects" : [ "obj1", "obj2" ]
1214 // "groups" : [ "group1", "group2" ],
1216 if (typeof(objectAndGroupList) != "object")
1218 throw new Error("Invalid function request parameter type: " +
1219 "expected object, found " +
1220 typeof(functionRequest[param]));
1223 // Retrieve the parameter list
1224 params = objectAndGroupList["parameters"];
1226 // If it didn't exist, ...
1229 // ... use an empty array.
1234 // otherwise, ensure we got an array
1235 if (! params instanceof Array)
1237 throw new Error("Invalid function parameters: " +
1238 "expected array, found " + typeof(params));
1242 // Create the function to call on each object. The object on which the
1243 // function is called will be prepended later.
1244 funcFragment = f + "(";
1246 // For each parameter...
1247 for (var j = 0; j < params.length; j++)
1249 // If this isn't the first parameter, add a separator
1252 funcFragment += ",";
1255 if (typeof(params[j]) == "function")
1257 // If the parameter is a function, arrange for it to be called
1259 funcFragment += "(" + params[j] + ")(fsm)";
1261 else if (typeof(params[j]) == "string")
1263 // If the parameter is a string, quote it.
1264 funcFragment += '"' + params[j] + '"';
1268 // Otherwise, just add the parameter's literal value
1269 funcFragment += params[j];
1273 // Complete the function call
1274 funcFragment += ")";
1276 // Get the "objects" list, e.g.
1277 // "objects" : [ "obj1", "obj2" ]
1278 var a = objectAndGroupList["objects"];
1280 // Was there an "objects" list?
1283 // Nope. Simplify code by creating an empty array.
1286 else if (! a instanceof Array)
1288 throw new Error("Invalid 'objects' list: expected array, got " +
1292 for (var j = 0; j < a.length; j++)
1294 // Ensure we got a string
1295 if (typeof(a[j]) != "string")
1297 throw new Error("Invalid friendly name in 'objects' list: " + a[j]);
1300 func += " fsm.getObject('" + a[j] + "')." + funcFragment + ";";
1303 // Get the "groups" list, e.g.
1304 // "groups" : [ "group1, "group2" ]
1305 var g = objectAndGroupList["groups"];
1307 // Was a "groups" list found?
1310 // Yup. Ensure it's an array.
1311 if (! g instanceof Array)
1313 throw new Error("Invalid 'groups' list: expected array, got " +
1317 for (var groupName in g)
1319 // Arrange to call the function on each object in each group
1321 " var groupObjects = " +
1322 " fsm.getGroupObjects('" + g[groupName] + "');" +
1323 " for (var i = 0; i < groupObjects.length; i++)" +
1325 " var objName = groupObjects[i];" +
1326 " fsm.getObject(objName)." + funcFragment + ";" +
1333 // Terminate the try block for function invocations
1341 // o = new qx.core.Object();
1342 // o.debug("Dynamically created " + actionType + "(fsm) { " + func + " }");
1344 // We've now built the entire body of a function that implements calls to
1345 // each of the requested automatic actions. Create and return the function,
1346 // which will become the property value.
1347 return new Function("fsm", func);
1353 ---------------------------------------------------------------------------
1355 ---------------------------------------------------------------------------
1358 qx.Proto.dispose = function()
1363 if (this.getDisposed()) {
1367 while (this._savedStates.length > 0)
1369 s = this._savedStates.pop();
1372 this._savedStates = null;
1374 while (this._eventQueue.length > 0)
1376 e = this._eventQueue.pop();
1380 this._eventQueue = null;
1382 while (this._blockedEvents.length > 0)
1384 e = this._blockedEvents.pop();
1389 for (var s in this._states)
1391 this._states[s].dispose();
1392 this._states[s] = null;
1393 delete this._states[s];
1395 this._states = null;
1397 return qx.core.Target.prototype.dispose.call(this);