//$Id: DOMTool.class.js,v 1.2 2005/10/28 12:52:25 kraus Exp $
/**
 * DOMTool - Tool zur Manipulation des HTML-Document Object Models
 *
 * @author	$Author: kraus $
 * @version	$Revision: 1.2 $
 * @access	public
 */

/**
 * constructor
 *
 * sollte normalerweise nie direkt aufgerufen werden.
 * Tools bzw. ihre Methoden werden entweder statisch ?ber Tool.methode()
 * aufgerufen oder ?ber eine Singleton-Instanz mittels Tool.getInstance().methode()
 * Da es aber F?lle geben kann, in denen eine Instanz z.B. f?r das zwischenspeichern
 * von Stati ben?tigt wird, besitzt ein Tool f?r jede statische Methode eine 
 * gleichnamige Instanzmethode, die im einfachsten Fall direkt auf die statische
 * Methode gemappt ist.
 */
function DOMTool()
{
    if (DOMTool._instance == null || DOMTool._instance == undefined) {
        DOMTool._instance = this;
    }
}

// 'private' static var containing singleton instance
DOMTool._instance = null;


// Singleton
DOMTool.getInstance = function()
{
    if (DOMTool._instance == null || DOMTool._instance == undefined) {
        DOMTool._instance = new DOMTool();
    }
    return DOMTool._instance;
}


/*******************************************************************************
 * static methods                                                              *
 ******************************************************************************/
 
/**
 * setClassName() - changes the class-Attribute of an HTML-Element
 *
 * @param id        (String) id of the element
 * @param className (String) the class name of the element
 */ 
DOMTool.setClass = function(idOrEl, className)
{
    DOMTool.setAttribute(idOrEl, 'class', className);
}


DOMTool.addClass = function(idOrEl, className)
{
    var el;
    var exists = false;
    
    if (idOrEl == undefined || className == undefined) {
        throw new JSInvalidArgumentException("addClass needs two String parameters to work");
    }

    // get element
    if (typeof(idOrEl) == "string") {
        el = DOMTool.getElementById(idOrEl);
    } else {
        el = idOrEl;
    }

    var classes = el.className.split(" ");
    for (var c = 0; c < classes.length; ++c) {
        if (classes[c] == className) {
            exists = true;
            break;
        }
    }
    if (!exists) {
        el.className += " " + className;
    }
}


DOMTool.removeClass = function(idOrEl, className)
{
    var el;
    var exists = false;
    if (idOrEl == undefined || className == undefined) {
        throw new JSInvalidArgumentException("removeClass needs two String parameters to work");
    }

    // get element
    if (typeof(idOrEl) == "string") {
        el = DOMTool.getElementById(idOrEl);
    } else {
        el = idOrEl;
    }
    
    var classes = el.className.split(" ");
    var pos = 0;
    for (var c = 0; c < classes.length; ++c) {
        if (classes[c] == className) {
            exists = true;
            break;
        }
        pos += classes[c].length;
    }
    if (exists) {
        el.className = el.className.substring(0, pos+1) + el.className.substring(pos + className.length +2, el.className.length);  
    }
    el.className.replace(/[^\s+|\s+$]/, "");
}


/**
 * setStyle() - changes the style-Attribute of an HTML-Element
 * 
 * @param   id      (String)        id of the element
 * @param   style   (String/Object) if String, replaces the complete value,
 *                                  if Object, replaces only those css-properties contained in the object
 */ 
DOMTool.setStyle = function(idOrEl, style)
{
    if (idOrEl == undefined || style == undefined) {
        throw new JSInvalidArgumentException("setStyle needs two parameters (string and string or obj) to work");
    }
    if (typeof(style) == "string") {
        DOMTool.setAttribute(idOrEl, style);
    } else if (typeof(style) == "object") {
        for (var p in style) {
            try {
                DOMTool.setCSSProperty(idOrEl, p, style[p]);
            } catch (e) {
                if (e instanceof JSDoesNotSupportException) {
                    // silently ignore this - bit unsafe but so what ;-)
                } else {
                    throw e;
                }
            }
        }
    }
}


/**
 * setCSSProperty - changes the css-property of an HTML-Element
 *
 * @param   id      (String)    id of the element
 * @param   prop    (String)    name of the property
 * @param   value   (String)    new value
 */
DOMTool.setCSSProperty = function(idOrEl, prop, value)
{
    // check params && enforce string-type
    if (idOrEl == undefined || prop == undefined || value == undefined) {
        throw new JSInvalidArgumentException("setCSSProperty needs three String parameters to work");
    }
    
    var el;
    if (typeof(idOrEl) == "string") {
        el = DOMTool.getElementById(idOrEl);
    } else {
        el = idOrEl;
    }
    
    if (el.style == undefined) {
        throw new JSDoesNotSupportException("element does not support 'style'-attribute");
    }
    // convert css-propertyname to js equivalent
    var prparts = prop.toString().toLowerCase().split('-');
    if (prparts.length > 1) {
        prop = prparts[0];
        for (var p = 1; p < prparts.length; ++p) {
            prop += prparts[p].substr(0,1).toUpperCase() + prparts[p].substr(1, prparts[p].length);
        }
    }
    // change style if it exists
    if (el.style[prop] != undefined) {
    	try {
	        el.style[prop] = value;
        } catch (e) {
        	// ignore
    	}
    }
}


/**
 * setSrc() - changes the src-Attribute of an HTML-Element
 *
 * @param   id      (String) id of the element
 * @param   newSrc  (String) the new src-value
 */ 
DOMTool.setSrc = function(idOrEl, newSrc)
{
    var el = null;
    
    // check params
    if (idOrEl == undefined || newSrc == undefined || typeof(newSrc) != "string") {
        throw new JSInvalidArgumentException("setSrc needs two String parameters to work");
    }

    // get element
    if (typeof(idOrEl) == "string") { 
        try { 
            el = DOMTool.getElementById(idOrEl);
        } catch (e) {
            if (e instanceof JSNotImplementedException) {
                if (document.images) {
                    var images;
                    eval("images = document.images");
                    el = images[idOrEl];
                } else {
                    throw new JSRuntimeException("the browser does not support some needed functions");
                }
            } else {
            	 throw e;
			}
        }
    } else {
        el = idOrEl;
    }
    
    // set source
    if (el.src == undefined) {
        throw new JSDoesNotSupportException("the element does not support the 'src'-attribute");
    }
    el.src = newSrc;
}


/**
 * rollOver() - toggles rollover state for an HTML-Image-Element
 * 
 * @param   idOrImg     (string/element) the element to rollover
 */
DOMTool.rollOver = function(idOrImg)
{
    var el = null;
    
    if (idOrImg == undefined) {
        throw new JSInvalidArgumentException("rollOver needs a string or HTMLElement parameter to work");
    }
    
    if (typeof(idOrImg) == "string") {
        el = DOMTool.getElementById(idOrImg);
    } else {
        el = idOrImg;
    }
    if (el.src == undefined) {
        throw new JSDoesNotSupportException("element does not support 'src'-attribute");
    }
    var newSrc  = "";
    var oldSrc  = el.src;
    var path    = oldSrc.substring(0, oldSrc.lastIndexOf("/")+1);
    var ext = oldSrc.substring(oldSrc.lastIndexOf("."), oldSrc.length);
    var file = oldSrc.substring(path.length, (oldSrc.length-ext.length)-1);
    var stat = oldSrc.substring(path.length + file.length, oldSrc.length - ext.length);
    if (stat == "0") {
        newSrc = path + file + 1 + ext;
    } else if (stat == "1") {
        newSrc = path + file + 0 + ext
    }
    DOMTool.setSrc(newSrc);
}


/**
 * setAttribute() - set the given attribute of the given HTMLElement
 *
 * @params  idOrEl      (String/Object) - the element or its id
 * @params  attr        (String) the attribute
 * @params  value       (String) the value
 */
DOMTool.setAttribute = function(idOrEl, attr, value)
{
    var el = null;
    if (idOrEl == undefined || attr == undefined || value == undefined) {
        throw new JSInvalidArgumentException("setAttribute needs three String parameters to work");
    }

    // get element
    if (typeof(idOrEl) == "string") {
        el = DOMTool.getElementById(idOrEl);
    } else {
        el = idOrEl;
    }
    
    // set attribute
    if (el[attr.toString()] == undefined) {
        throw new JSDoesNotSupportException("the element does not support the attribute '" + attr + "'");
    }
    el[attr] = value.toString();
}


/**
 * findParent() - tries to find a parent element to the given start element 
 *                which matches the given criteria
 *
 * @param   startEl     (String/Object) Id of an HTML-Element or the element itself
 * @param   criteria    (Object) the criteria to match
 */
DOMTool.findParent = function(startEl, criteria)
{
    var suspect = null;
    if (typeof(startEl) == "string") {
        suspect = DOMTool.getElementById(startEl);
    } else if (typeof(startEl) == "object") {
        suspect = startEl;
    } else if (startEl == undefined || criteria == undefined || typeof(criteria) != "object") {
        throw new JSInvalidArgumentException("findParent needs one string or object and one object parameter");
    }
    // check and convert criteria
    if (criteria.tagName != undefined) {
        criteria.tagName = criteria.tagName.toUpperCase();
    }

    while (suspect.parentNode != null && suspect.parentNode.tagName.toLowerCase() != "body") {
        suspect = suspect.parentNode;
        if (DOMTool.__checkCriteria(criteria, suspect)) {
            alert("findParent() hit");
            return suspect;
        }
    }
    return null;
}


/**
 * findElement() - tries to find an HTML-Element matching the given criteria
 *
 * @param   criteria        (object) criteria to match. 
 *                          the search-algorithm supports a shortcut for 
 *                          attribute values (which normally are accessed via
 *                          "element.attributes['attrname'].value" it is sufficient
 *                          to use {attributes: {'attrname' : <value>}}. 
 */
DOMTool.findElement = function(criteria)
{
    if (criteria == undefined || typeof(criteria) != "object") {
        throw new JSInvalidArgumentException("findElement needs one object parameter to work");
    }
    var suspects = new Array();

    // determine detection method & get suspects
    if (criteria.id != undefined) {
        var el = DOMTool.getElementById(criteria.id.toString());
        if (el != null) {
            suspects.push(el);
        }
    } else if (criteria.tagName != undefined) {
        criteria.tagName = criteria.tagName.toUpperCase();
        var els = document.getElementsByTagName(criteria.tagName);
        for (var e = 0; e < els.length; ++e) {
            suspects.push(els[e]);
        }
    } else {
        // @TODO implement me ;-)
        throw new JSDoesNotSupportException("sorry, the search for arbitray elements is not implemented yet. Please specify criteria.id or criteria.tagName");
    }
 
    for (var s = 0; s < suspects.length; ++s) {
        if(DOMTool.__checkCriteria(criteria, suspects[s])) {
            alert("findElement() hit");
            return suspects[s];
        }
    }
    return null;
}


/**
 * getElementById() - wrapper around document.getElementById() which checks
 *                    params and throws Exception on problems
 *
 * @param   id      (String) id of the element
 */
DOMTool.getElementById = function(id)
{
    if (document.getElementById == undefined || (typeof(document.getElementById) != "function" && typeof(document.getElementById) != "object")) {
        throw new JSDoesNotSupportException("browser does not support 'document.getElementById()'");
    }
    if (id == undefined) {
        throw new JSInvalidArgumentException("getElementById needs one String parameters to work");
    }
	if (id.id != undefined) {
		id = id.id;
	}
    var el = document.getElementById(id.toString());
    if (el == undefined || el == null) {
        throw new JSInvalidElementIdException("could not find an element with id '" + id + "'");
    }
    return el;
}


/**
 * __checkCriteria() - checks if object s matches (e.q. has same values) criteria cs
 *
 * @param   cs      (Object) criteria
 * @param   s       (Object) suspect object to check
 * @param   cn      (String) name of container - only for internal use!
 */
DOMTool.__checkCriteria = function(cs, s, cn)
{
    for (var c in cs) {
        var criteria = cs[c];
        var suspect = s[c];
        
        // ignore functions
        if (typeof(criteria) == "function") {
            continue;
        }
        

        // 1) check if property exists in suspect and 
        //    try to convert some known trouble-makers
        if (suspect == undefined) {
            // handle some differences in html/js-naming schemes
            if (c == "class" && s["className"] != undefined) {
                suspect = s["className"];
            } else if (cn == "style") {
                var cps = c.toString().toLowerCase().split('-');
                if (cps.length > 1) {
                    c = cps[0];
                    for (var p = 1; p < prparts.length; ++p) {
                        c += cps[p].substr(0,1).toUpperCase() + cps[p].substr(1, cps[p].length);
                    }
                }
                if (s[c] != undefined) {
                    suspect = s[c];
                } else {
                    alert("1) undefined property " + c + " even after conversion");
                    return false;
                }
            } else {
                alert("1) undefined property " + c);
                return false;
            }
        }
        
        // 2) check if type of criteria and suspect are the same
        //    if not, check if suspect.suspect is present... 
        if (typeof(criteria) != typeof(suspect)) {
            if (typeof(suspect) == "object" && suspect.value != undefined && typeof(suspect.value) == typeof(criteria)) {
                suspect = suspect.value;
            } else {
                alert("2) differing types for "  + c);
                return false;
            }
        }

        // 3) recurse sub-objects       
        if (typeof(criteria) == "object") {
            if (!DOMTool.__checkCriteria(criteria, suspect, c)) {
                alert("3) recursion for " + c + " failed");
                return false;
            }
        // 4) finally check if criteria an suspect are the same
        } else if (suspect != criteria) {
            if (criteria.indexOf("#") == 0 && suspect.indexOf("rgb(") == 0) {
                var temp = suspect.substring(suspect.indexOf('(') + 1, suspect.lastIndexOf(')')).split(", ");
                var hexa = "0123456789abcdef";
                suspect = "#";
                for (var px = 0; px < temp.length; ++px) {
                    var tmp;
                    var hex = "";
                    while (temp[px] > 15) {
                        tmp = temp[px] - (Math.floor(temp[px] / 16)) * 16;
                        hex = hexa.charAt(tmp) + hex;
                        temp[px] = Math.floor(temp[px] / 16);
                    }
                    hex = hexa.charAt(temp[px]) + hex;
                    suspect += hex;
                }
                if (suspect.toLowerCase() != criteria.toLowerCase()) {
                    alert("4) suspect differs even after conversion for " + c + ": |" + suspect + "| != |" + criteria + "|");
                    return false;
                }
            } else {
                alert("4) suspect differs for " + c + ": |" + suspect + "| != |" + criteria + "|");
                return false;
            }
        }
    }
    return true;
}

/*******************************************************************************
 * instance wrappers for static methods                                        *
 ******************************************************************************/
DOMTool.prototype.setClass = function(idOrEl, className)
{
	return DOMTool.setClass(idOrEl, className);
}

DOMTool.prototype.addClass = function(idOrEl, className)
{
	return DOMTool.addClass(idOrEl, className);
}

DOMTool.prototype.removeClass = function(idOrEl, className)
{
	return DOMTool.removeClass(idOrEl, className);
}

DOMTool.prototype.setStyle = function(idOrEl, style)
{
	return DOMTool.setStyle(idOrEl, style);
}

DOMTool.prototype.setCSSProperty = function(id, prop, value)
{
	return DOMTool.setCSSProperty(id, prop, value);
}

DOMTool.prototype.setSrc = function(idOrEl, newSrc)
{
	return DOMTool.setSrc(idOrEl, newSrc);
}

DOMTool.prototype.rollOver = function(idOrImg)
{
	return DOMTool.rollOver(idOrImg);
}

DOMTool.prototype.setAttribute = function(idOrEl, attr, value)
{
	return DOMTool.setAttribute(idOrEl, attr, value);
}

DOMTool.prototype.findParent = function(startEl, criteria)
{
	return DOMTool.findParent(startEl, criteria);
}

DOMTool.prototype.findElement = function (criteria)
{
	return DOMTool.findElement(criteria);
}

DOMTool.prototype.getElementById = function(id)
{
	return DOMTool.getElementById(id);
}

