The AutoPlayDom widget is a tool that I have been developing to perform step by step DOM actions. The primary purpose of this tool is for debugging, so that a developer can play through the classes/styles that are applied at various times throughout the life of a webpage. Then the developer can view the pages in their graded-browser support matrix and verify that the everything appears as expect. However, the tool is flexible enough to perform a serious of DOM manipulations, which have the effect of animating a page (although it does not leverage an animation library, yet…).
The project file is available at http://www.mattsnider.com/assets/js/widget/AutoPlayDom.js.
Today’s article will cover the two main (and most complicated) public methods of AutoPlayDom: add and next. The ‘add’ function allows more steps to added to the ‘_data’ array, which holds the logic for each step of DOM manipulation. The ‘next’ function performs the required tasks found in each step.
Example 1: AutoPlayDom.add()
add: function() {
// iterate on the arguments
for (var i = 0; i < arguments.length; i+= 1) {
var o = arguments[i];
if (_YL.isArray(o)) {
_that.add.apply(_that, o);
}
else if (o && o.name && o.tasks && o.tasks.length) { // validate
_data.push(o);
}
else {
alert('attempting to add invalid object');
}
}
},
The ‘add’ method in Example 1 uses the ArgumentOrArray Pattern, where the developer can pass in the tasks as: a single task object, many task objects as parameters, an array of many task objects, or many arrays containing task objects and/or task objects as parameters. The task objects will be added by their argument position first, then there position in the array. So the following:
Example 2: using AutoPlayDom.add()
var apd = Core.Widget.AutoPlayDom();
apd.add('object1');
apd.add(['object2', 'object3'], 'object4', ['object5']);
will play out in this order: ‘object1′, ‘object2′, ‘object3′, ‘object4′, ‘object5′. Lastly, the ‘add’ method validates that the task object has set ‘name’ and ‘tasks’ is an array. This will allow the ‘next’ method to run properly for this task object. Task objects should be of the form shown in Example 3.
Example 3: Task Object
{name: 'The Name of This Task', tasks: [
{search: [ // an array of search objects
{method: 'name of a method attach to YAHOO.util.Dom', params: [/*an array of parameters to pass into method*/]}
// each subquent object will use the resulting node or nodes from the previous search as their root parameter
], actionMethod: 'name of a method attach to YAHOO.util.Dom', actionParams: [ // array of actions parameters to apply to the node, using this actionMethod
[/* array of parameters to use with actionMethod */]
]},
{search: [
{method: 'getElementsByClassName', params: ['testClass', 'form', document.body]},
{method: 'getElementsBy', params: ['fieldset']}
], actionMethod: 'setStyle', actionParams: [
['height', '100px'], // use 'setStyle to apply the height to the node
['width', '300px'], // use 'setStyle to apply the width to the node
['z-index', 100] // use 'setStyle to apply the zIndex to the node
]}
]}
The comments in Example 3 explain how to use each part of the task object. Once added, the task objects can be executed using the ‘next’ method.
Example 4: AutoPlayDom.next()
next: function() {
// go to the next position
if (_that.hasNext()) {
_index += 1;
var o = _data[_index];
// iterate on the tasks
_array_walk(o.tasks, function(t) {
var isValidSearch = t.search && t.search.length,
isValidAction = t.actionMethod && t.actionParams && t.actionParams.length,
nodes = null;
if (isValidSearch && isValidAction) {
// iterate on the search methods
_array_walk(t.search, function(o) {
if (nodes) {
var new_nodes = [];
// iterate on the nodes and apply the search method
_array_walk(nodes, function(node) {
var fx = _YL.isFunction(o.method) ? o.method : _YD[o.method];
if (fx) {
var new_params = [];
if (o.params) {
new_params = _YL.isArray(o.params) ? o.params : [o.params]; // normalize params
}
// various need the node placed in differing parameter locations
if (_YL.isString(o.method)) {
switch (o.method) {
case 'getElementsByClassName': // these may need to be updated, anytime listOfMethods in loadAutoPlayHelperFunctions is changed
case 'getElementsByTagName': // this group of functions puts the node at the end
case 'getElementsBy':
new_params.push(node);
break;
default: // this group of functions puts the node at the beginning
new_params = [node].concat(new_params);
break;
}
}
else {
new_params = [node].concat(new_params);
}
var rs = fx.apply(window, new_params);
if (rs) {
if (_YL.isArray(rs)) {
new_nodes = new_nodes.concat(rs);
}
else {
new_nodes.push(rs);
}
}
else {
// this index will be removed
}
}
else {
throw('EXCEPTION: AutoPlayDom.Next - Invalid method used (' + o.method + '); no elements found');
}
});
nodes = new_nodes;
}
else {
var fx = _YL.isFunction(o.method) ? o.method : _YD[o.method];
nodes = fx.apply(window, _YL.isArray(o.params) ? o.params : [o.params]);
if (! _YL.isArray(nodes)) {nodes = [nodes];} // convert to an array so we can do same logic, when array or not array
}
});
var actionMethod = _YL.isFunction(t.actionMethod) ? t.actionMethod : _YD[t.actionMethod];
if (actionMethod) {
// iterate on the nodes and apply the action method
_array_walk(nodes, function(node) {
if (! _YL.isArray(t.actionParams)) {t.actionParams = [t.actionParams];} // convert to an array so we can do same logic, when array or not array
_array_walk(t.actionParams, function(params) {
actionMethod.apply(window, [node].concat(params));
});
});
}
else {
throw('EXCEPTION: AutoPlayDom.Next - Invalid actionMethod used (' + t.actionMethod + '); method not found on YAHOO.util.Dom');
}
}
else {
throw('EXCEPTION: AutoPlayDom.Next - Invalid task used (isValidSearch:' + isValidSearch + ', isValidAction:' + isValidAction + ')');
}
});
}
},
The ‘next’ method in Example 3 moves the DOM state from the current task object to the next. The ‘next’ method first validates that there is another task object available, then walks through each task on the task object. These tasks are checked if they contain a ’search’ array, an ‘actionMethod’, and an array of parameter arrays (’actionParams’). Next each object is ’search’ is iterated on. Each ’search’ object should contain a ‘method’ and an array of parameters (’params’). If there is only one ’search’ object, then the method moves forwarding using the node(s) found. However, when there are more than one ’search’ objects, the method iterates through nodes, calling the new ’search’ methods using the previously found node(s) as a the root node. Any method found this way will replace the values previously in the ‘nodes’ list. There is some special-case logic for various methods where the root node is the last parameter, instead of the first parameter, but the default is to use the previous node as the first parameter, when calling the ’search’ method.
Once all the ‘nodes’ are found and the ‘actionMethod’ is defined, the nodes are iterated on for DOM manipulation. The ‘actionMethod’ can be found on “YAHOO.util.Dom” by default (such as ’setStyle’ or ‘addClass’) or a function of the developers own creation. It is assumed for these methods that the ‘node’ should be the first parameter. Lastly, for each ‘node’, the ‘actionParam’ array is iterated on, to call the ‘actionMethod’ with the ‘node’ and each ‘actionParam’.
This tool is versioned ‘0.5′, because I intend to write a ‘previous’ function which will be a dynamic way to iterate backwards through DOM actions that were previously executed in the forward direction. This is going to be very tricky and I did not have time for it this week. When I finish the previous method I will create a test page so that you can experiment with AutoPlayDom.