DomTaskPlayer


This is a continuation of the article Project - AutoPlayDom. The project was renamed to DomTaskPlayer, because the new name is semantically more correct, as the object plays through a series of Dom related tasks. The full source code is available at http://www.mattsnider.com/assets/js/widget/DomTaskPlayer.js. The source code has drastically changed from last week, as supporting backwards navigation through the already played DomTasks, required a massive redesign of the code.

For starters the "DomTaskPlayer.add" method has changed, to accept a collection of 'step' objects, which will contain 'task' objects, which will contain 'action' objects:

Example 1: A Step Object

var step = {
	_isValid: false, // used internally, updated after first 'next' call
	name: '', // what this step is
	tasks: [ // an array of tasks
		{ // task
			action: [
				{fx: function() {}, method: '', params: [], reverseMethod: '', reverseFx: function() {}}, // action
				...
			],
			search: [
				{fx: function() {}, method: '', params: []}, // action
				...
			],
			reverse: [] // internally managed
		},
		...
	]
};

A 'step' object is a collection of 'task' objects and a 'name' to label the step. The 'task' object is a collection of search 'action' objects (to find the node(s) to apply actions to), and a collection of action 'action' objects (actually describing how to change the nodes found when searching). There is no limit to the number of tasks per step and actions per task. When the "DomTaskPlayer.next" method is called an internal collection of 'action' objects are attached to the reverse list, explaining how to reverse the current step.

The 'next' and 'previous' methods have been simplified, delegating the work to smaller more targeted internal assertion and action functions.

Example 2: DomTaskPlayer.next

next: function() {
    // go to the next position
    if (_that.hasNext()) {
        _index += 1;

        var step = _steps[_index];

        // already validated and found nodes, no need to assert
        if (step._isValid) {
            _array_walk(step.tasks, function(task) { // iterate on the tasks
                _array_walk(task.action, _execute_action); // iterate on the actions
            });
        }
        else {
            _array_walk(step.tasks, function(task) { // iterate on the tasks
                _assert_valid_task(task);

                var tobj = {};

                _array_walk(task.search, _execute_search, tobj);

                _array_walk(task.action, function(action) { // iterate on the actions
                    _assert_valid_action(action); // validates and/or sets 'action.fx'; assures action.params is an array
                    _assert_user_reversible(action); // user fx is reversible
                    action.nodes = tobj.nodes;
                    _execute_action(action);
                });
            });

            step._isValid = true;
        }

        _that.onStepChange.fire([step, 'next']);
    }
},

This method first asserts that there is a next step available, then grabs the step from the internal step array. If the step has the internal variable '_isValid' set to true, then the method knows that it has previously already validated this step and can skip the search actions, executing the action tasks on cached DOM nodes. Otherwise, it iterates through each task and asserts that the tasks are valid. Afterwards, it searches the DOM using the search 'action' objects, producing an object with key 'nodes'. Then it iterates on the action 'action' objects and asserts they are valid and reversible. If no errors have been thrown, terminating the flow, then the method actually executes the 'action' objects. As each 'action' object is executed, the internal methods create the reverse actions, which will be used with the 'previous' method.

In addition, to supporting 'previous' and the massive revisions to 'next', there is now a build-in, public method 'initKeyListeners' that hijacks the arrow keys (and 'a' - left arrow, 's' - down arrow, 'd' - right arrow, 'w' - up arrow) to call one of the public action methods of DomTaskPlayer ('previous', 'stop', 'next', 'play'). The developer can require that any combination of 'Ctrl', 'Alt', or 'Shift' be pressed as well, so not to be obtrusive.

Example 3: DomTaskPlayer.initKeyListeners

initKeyListeners: function(forceCtrl, forceShift, forceAlt) {
    _YE.on(document, 'keydown', function(e) {
        if (forceCtrl && ! e.ctrlKey) {return;} // may trigger browser events
        if (forceShift && ! e.shiftKey) {return;}
        if (forceAlt && ! e.altKey) {return;}

        _YE.preventDefault(e); // stop browser events

        switch (_YE.getCharCode(e)) {
            case _YK.DOWN:
            case 83: // s
                _that.stop();
            break;

            case _YK.LEFT:
            case 65: // a
                _that.previous();
            break;

            case _YK.RIGHT:
            case 68: // d
                _that.next();
            break;

            case _YK.UP:
            case 87: // w
                _that.play();
            break;

            default: // do nothing
        }
    });
    _that.initKeyListeners = function() {};
},

At the end of 'initKeyListeners' the method is set to empty function, thereby preventing the developer from accidentally attaching these key listeners more than once. This method can be called anytime after the DomTaskPlayer object has been initialized and it is recommended that at least two special keys be set to true, such as 'Shift' and 'Ctrl':

Example 4: Calling initKeyListeners

yourDomTaskPlayerObject.initKeyListeners(true, true);

In Example 4, the user will need to hold 'Shift' + 'Ctrl' + ArrowKey to trigger the desired method.

Most importantly, this tool is easy to use; see Dom Task Player Test Page. Although easy to use, this tool is very complicated, please let me know if something does not work for you or are having trouble getting it working.

Lastly, please keep in mind that order of operations are very important with tasks, so if you first apply a class that changes the background, then change the background with a style directly in the same 'step' object, then the reverse operation will be confused and apply the wrong background color.