/**
 * ADS Library from Advanced DOM Scripting
 * http://advanceddomscripting.com
 *
 * This library is not compressed and is not recommended for production use in
 * its current state. The code is excessively verbose and heavily commented
 * as it was written as a teaching tool. It is recommended you edit the code for
 * better performance and smaller file size.

 * @projectDescription ADS library from the book "AdvancED DOM Scripting" http://advanceddomscripting.com/
 * @author Jeffrey Sambells jeff@advanceddomscripting.com
 * @copyright Jeffrey Sambells 2007 unless otherwise noted
 * @version $Id: ADS-final-verbose.js 183 2007-07-17 20:23:30Z jsambells $
 * @see http://advanceddomscripting.com/source/documentation
 * @namespace ADS
 */

/**
* Missing getElementById.
* Example of creating a DOM replacement this isn't necessary because the
* library assume browsers that support it.
*/
if(document.all && !document.getElementById) {
    document.getElementById = function(id) {
         return document.all[id];
    }
}


/**
* Repeat a string 
* from Chapter 3
* Using the prototype to modify core objects
*/
if (!String.repeat) {
    String.prototype.repeat = function(l){
        return new Array(l+1).join(this);
    }
}

/** 
* Remove trailing and leading whitespace 
* from Chapter 3
*/
if (!String.trim) {
    String.prototype.trim = function() {
        return this.replace(/^\s+|\s+$/g,'');
    }
}

/**
 * ADS Namespace
 * This anonymous function acts as a namespace wrapper for the rest
 * of the methods. Methods are then assigned to the window object
 * using: window['ADS']['methodName'] = methodReference;
 * @alias ADS
 */
(function(){

/**
 * The primary namespace object
 * @type {Object}
 * @alias ADS
 */
if(!window['ADS']) {
    window['ADS'] = {};
}


/********************************
* Chapter 1
*********************************/

/**
 * Checks to see if the current browser is compatible with the entire library
 */
function isCompatible(other) {
    // Use capability detection to check requirements
    if( other===false 
        || !Array.prototype.push
        || !Object.hasOwnProperty
        || !document.createElement
        || !document.getElementsByTagName
        ) {
        alert('TR- if you see this message isCompatible is failing incorrectly.');
        return false;
    }
    return true;
}
window['ADS']['isCompatible'] = isCompatible;

/**
 * document.getElementById(); replacement.
 */
function $() {
    var elements = new Array();
    
    // Find all the elements supplied as arguments
    for (var i = 0; i < arguments.length; i++) {
        var element = arguments[i];
        
        // If the argument is a string assume it's an id
        if (typeof element == 'string') {
            element = document.getElementById(element);
        }
        
        // If only one argument was supplied, return the element immediately
        if (arguments.length == 1) {
            return element;
        }
        
        // Otherwise add it to the array
        elements.push(element);
    }
    
    // Return the array of multiple requested elements
    return elements;
};
window['ADS']['$'] = $;


/**
 * Register an event listener on an element
 */
function addEvent( node, type, listener ) {
    // Check compatibility using the earlier method
    // to ensure graceful degradation
    if(!isCompatible()) { return false }
    if(!(node = $(node))) return false;
    
    if (node.addEventListener) {
        // W3C method
        node.addEventListener( type, listener, false );
        return true;
    } else if(node.attachEvent) {
        // MSIE method
        node['e'+type+listener] = listener;
        node[type+listener] = function(){node['e'+type+listener]( window.event );}
        node.attachEvent( 'on'+type, node[type+listener] );
        return true;
    }
    
    // Didn't have either so return false
    return false;
};
window['ADS']['addEvent'] = addEvent;

/**
 * Unregister an event listener on an element
 */
function removeEvent(node, type, listener ) {
    if(!(node = $(node))) return false;
    if (node.removeEventListener) {
        node.removeEventListener( type, listener, false );
        return true;
    } else if (node.detachEvent) {
        // MSIE method
        node.detachEvent( 'on'+type, node[type+listener] );
        node[type+listener] = null;
        return true;
    }
    // Didn't have either so return false
    return false;
};
window['ADS']['removeEvent'] = removeEvent;

/**
 * Retrieve an array of element base on a class name
 */
function getElementsByClassName(className, tag, parent){
    parent = parent || document;
    if(!(parent = $(parent))) return false;
    
    // Locate all the matching tags
    var allTags = (tag == "*" && parent.all) ? parent.all : parent.getElementsByTagName(tag);
    var matchingElements = new Array();
    
    // Create a regular expression to determine if the className is correct
    className = className.replace(/\-/g, "\\-");
    var regex = new RegExp("(^|\\s)" + className + "(\\s|$)");
    
    var element;
    // Check each element
    for(var i=0; i<allTags.length; i++){
        element = allTags[i];
        if(regex.test(element.className)){
            matchingElements.push(element);
        }
    }
    
    // Return any matching elements
    return matchingElements;
};
window['ADS']['getElementsByClassName'] = getElementsByClassName;

/**
 * Toggle the style display value between none and the default
 */
function toggleDisplay(node, value) {
    if(!(node = $(node))) return false;
    if ( node.style.display != 'none' ) {
        node.style.display = 'none';
    } else {
        node.style.display = value || '';
    }
    return true;
}
window['ADS']['toggleDisplay'] = toggleDisplay;

/**
 * Insert a DOM node after another DOM node
 */
function insertAfter(node, referenceNode) {
    if(!(node = $(node))) return false;
    if(!(referenceNode = $(referenceNode))) return false;
    
    return referenceNode.parentNode.insertBefore(node, referenceNode.nextSibling);
};
window['ADS']['insertAfter'] = insertAfter;

/**
 * Remove all teh child nodes from an element
 */
function removeChildren(parent) {
    if(!(parent = $(parent))) return false;
    
    // While there is a child remove it
    while (parent.firstChild) {
         parent.firstChild.parentNode.removeChild(parent.firstChild);
    }
    // Return the parent again so you can stack the methods
    return parent;
};
window['ADS']['removeChildren'] = removeChildren;

/**
 * Insert a new node as the first child.
 */
function prependChild(parent,newChild) {
    if(!(parent = $(parent))) return false;
    if(!(newChild = $(newChild))) return false;
    if(parent.firstChild) {
        // There is already a child so insert before the first one
        parent.insertBefore(newChild,parent.firstChild);    
    } else {
        // No children so just append
        parent.appendChild(newChild);
    }
    // Return the parent again so you can stack the methods
    return parent;
} 
window['ADS']['prependChild'] = prependChild;


/********************************
* Chapter 2
*********************************/

/**
 * Put the given object in teh context of the given method.
 */
function bindFunction(obj, func) {
    return function() {
        func.apply(obj,arguments);    
    };
};
window['ADS']['bindFunction'] = bindFunction;


/**
 * Retrieve the size of the browser window.
 */
function getBrowserWindowSize() {
    var de = document.documentElement;
    
    // window.innerWidth for most browsers
    // document.documentElement.clientWidth for MSIE in strict mode
    // document.body.clientWidth for MSIE in quirks mode
    
    return {
        'width':(
            window.innerWidth 
            || (de && de.clientWidth ) 
            || document.body.clientWidth),
        'height':(
            window.innerHeight 
            || (de && de.clientHeight ) 
            || document.body.clientHeight)
    }
};
window['ADS']['getBrowserWindowSize'] = getBrowserWindowSize;

/********************************
* Chapter 3
*********************************/

/**
 * Constants for note type comparison
 */
window['ADS']['node'] = {
    ELEMENT_NODE                : 1,
    ATTRIBUTE_NODE              : 2,
    TEXT_NODE                   : 3,
    CDATA_SECTION_NODE          : 4,
    ENTITY_REFERENCE_NODE       : 5,
    ENTITY_NODE                 : 6,
    PROCESSING_INSTRUCTION_NODE : 7,
    COMMENT_NODE                : 8,
    DOCUMENT_NODE               : 9,
    DOCUMENT_TYPE_NODE          : 10,
    DOCUMENT_FRAGMENT_NODE      : 11,
    NOTATION_NODE               : 12
};

/**
 * Walk the nodes in the DOM tree without maintaining parent/child relationships.
 */
function walkElementsLinear(func,node) {
    var root = node || window.document;
    var nodes = root.getElementsByTagName("*");
    for(var i = 0 ; i < nodes.length ; i++) {
        func.call(nodes[i]);
    }
};
window['ADS']['walkElementsLinear'] = walkElementsLinear;

/**
 * Walk the nodes in the DOM tree maintaining parent/child relationships.
 */
function walkTheDOMRecursive(func,node,depth,returnedFromParent) {
    var root = node || window.document;
    returnedFromParent = func.call(root,depth++,returnedFromParent);
    node = root.firstChild;
    while(node) {
        walkTheDOMRecursive(func,node,depth,returnedFromParent);
        node = node.nextSibling;
    }
};
window['ADS']['walkTheDOMRecursive'] = walkTheDOMRecursive;

/**
 * Walk the nodes in the DOM tree maintaining parent/child relationships and include the node attributes as well.
 */
function walkTheDOMWithAttributes(node,func,depth,returnedFromParent) {
    var root = node || window.document;
    returnedFromParent = func(root,depth++,returnedFromParent);
    if (root.attributes) {
        for(var i=0; i < root.attributes.length; i++) {
            walkTheDOMWithAttributes(root.attributes[i],func,depth-1,returnedFromParent);
        }
    }
    if(root.nodeType != ADS.node.ATTRIBUTE_NODE) {
        node = root.firstChild;
        while(node) {
            walkTheDOMWithAttributes(node,func,depth,returnedFromParent);
            node = node.nextSibling;
        }
    }
};
window['ADS']['walkTheDOMWithAttributes'] = walkTheDOMWithAttributes;

/**
 * Walk the DOM recursively using a callback function
 */
function walkTheDOM(node, func) {
    func(node);
    node = node.firstChild;
    while (node) {
         walkTheDOM(node, func);
         node = node.nextSibling;
    }
}
window['ADS']['walkTheDOM'] = walkTheDOM;

/**
 * Convert hyphenated word-word strings to camel case wordWord strings.
 */
function camelize(s) {
    return s.replace(/-(\w)/g, function (strMatch, p1){
        return p1.toUpperCase();
    });
}
window['ADS']['camelize'] = camelize;

/********************************
* Chapter 4
*********************************/

/**
 * Convert camel case wordWord strings to hyphenated word-word strings.
 */
function uncamelize(s, sep) {
    sep = sep || '-';
    return s.replace(/([a-z])([A-Z])/g, function (strMatch, p1, p2){
        return p1 + sep + p2.toLowerCase();
    });
}
window['ADS']['camelize'] = camelize;


/**
 * Add a load event that will run when the document finishes loading - excluding images.
 */
function addLoadEvent(loadEvent,waitForImages) {
    if(!isCompatible()) return false;
    
    // If the wait flag is true use the regular add event method
    if(waitForImages) {
        return addEvent(window, 'load', loadEvent);
    }
    
    // Otherwise use a number of different methods
    
    // Wrap the loadEvent method to assign the correct content for the
    // this keyword and ensure that the event doesn't execute twice
    var init = function() {

        if (arguments.callee.done) return;
        // Return if this function has already been called

        // Mark this function so you can verify if it was already run
        arguments.callee.done = true;

        // Run the load event in the context of the document
        loadEvent.apply(document,arguments);
    };
    
    // Register the event using the DOMContentLoaded event
    if (document.addEventListener) {
        document.addEventListener("DOMContentLoaded", init, false);
    }
    
    // For Safari, use a setInterval() to see if the document has loaded 
    if (/WebKit/i.test(navigator.userAgent)) {
        var _timer = setInterval(function() {
            if (/loaded|complete/.test(document.readyState)) {
                clearInterval(_timer);
                init();
            }
        },10);
    }
    // For Internet Explorer (using conditional comments) attach a script 
    // that is deferred to the end of the load process and then check to see
    // if it has loaded
    /*@cc_on @*/
    /*@if (@_win32)
    document.write("<script id=__ie_onload defer src=javascript:void(0)><\/script>");
    var script = document.getElementById("__ie_onload");
    script.onreadystatechange = function() {
        if (this.readyState == "complete") {
            init();
        }
    };
    /*@end @*/
    return true;
}
window['ADS']['addLoadEvent'] = addLoadEvent;

/**
 * Stop the propagation of an event
 */
function stopPropagation(eventObject) {
    eventObject = eventObject || getEventObject(eventObject);
    if(eventObject.stopPropagation) {
        eventObject.stopPropagation();
    } else {
        eventObject.cancelBubble = true;
    }
}
window['ADS']['stopPropagation'] = stopPropagation;

/**
 * Prevents the default event in the event flow (such as following the href in an anchor).
 */
function preventDefault(eventObject) {
    eventObject = eventObject || getEventObject(eventObject);
    if(eventObject.preventDefault) {
        eventObject.preventDefault();
    } else {
        eventObject.returnValue = false;
    }
}
window['ADS']['preventDefault'] = preventDefault;

/**
 * Retrieves the event object in a cross-browser way.
 */
function getEventObject(W3CEvent) {
    return W3CEvent || window.event;
}
window['ADS']['getEventObject'] = getEventObject;

/**
 * Retrieves the element targeted by the event.
 */
function getTarget(eventObject) {
    eventObject = eventObject || getEventObject(eventObject);
    // Check if the target is W3C or MSIE
    var target = eventObject.target || eventObject.scrElement;
    // Reassign the target to the parent
    // if it is a text node like in Safari
    if(target.nodeType == ADS.node.TEXT_NODE) {
        target = node.parentNode;
    }
    return target;

}
window['ADS']['getTarget'] = getTarget;

/**
 * Determine which mouse button was pressed 
 */
function getMouseButton(eventObject) {
    eventObject = eventObject || getEventObject(eventObject);
    // Initialize an object wit the appropriate properties
    var buttons = {
        'left':false,
        'middle':false,
        'right':false
    };
    // Check the toString value of the eventObject
    // W3C Dom object have a toString method and in this case it
    // should be MouseEvent
    if(eventObject.toString && eventObject.toString().indexOf('MouseEvent') != -1) {
        // W3C Method
        switch(eventObject.button) {
            case 0: buttons.left = true; break;
            case 1: buttons.middle = true; break;
            case 2: buttons.right = true; break;
            default: break;
        }
    } else if(eventObject.button) {
        // MSIE method
        switch(eventObject.button) {
            case 1: buttons.left = true; break;
            case 2: buttons.right = true; break;
            case 3:
                buttons.left = true;
                buttons.right = true;
            break;
            case 4: buttons.middle = true; break;
            case 5:
                buttons.left = true;
                buttons.middle = true;
            break;
            case 6:
                button