function checkBoxesNamePrefix (form, namePrefix, check) {
    for (var c = 0; c < form.elements.length; c++) {
        if ( (form.elements[c].type == 'checkbox') && (form.elements[c].name.substr(0,namePrefix.length) == namePrefix) ) {
            form.elements[c].checked = check;
        }
    }
}

function hideThermometer(tid) {
    var tbl = document.all ? document.all[tid+'0'] : document.getElementById(tid+'0');
    tbl.style.display = 'none';
}

function setThermometer(tid,pct,statusline) {
    var o1 = document.all ? document.all[tid+'1'] : document.getElementById(tid+'1');
    var o2 = document.all ? document.all[tid+'2'] : document.getElementById(tid+'2');
    var o3 = document.all ? document.all[tid+'3'] : document.getElementById(tid+'3');
    o3.innerHTML = statusline;
    if (pct == 0) {
        // 0% is illegal in IE DOM
        o1.width = '1';
        o2.width = '100%';
    } else {
        if (pct >= 100) {
            // 100% is illegal in IE DOM
            o2.style.background = o1.style.background;
        } else {
            o1.width = pct+'%';
            o2.width = (100-pct)+'%';
        }
    }
}

function addToThermometerLog(tid,html) {
    var o = document.all ? document.all[tid+'log'] : document.getElementById(tid+'log');
    o.innerHTML = o.innerHTML + html;
}

// SUPORT FOR SELECT CONTROLS
// IE5.5 supports the disabled attribute in option controls, but without any logic. we handle the logic here by deselecting
// any options which are selected but marked as disabled.
var selectTrackArray = new Array();

// called once a new item is selected, slam the valid value back in if this one is disabled
function checkSelect(o) {
    if (isAtLeastIE55 || isFirefox) 
        if (o.selectedIndex >= 0)
            if (o.options[o.selectedIndex].disabled == true) o.selectedIndex = selectTrackArray[o.uniqueID];
}
// called when a select is clicked on, to save a valid selected index
function saveSelect(o) {
    if (isAtLeastIE55 || isFirefox) 
        if (o.selectedIndex >= 0) selectTrackArray[o.uniqueID] = o.selectedIndex;
}
// find all disabled options and make them gray
function initSelects(oRow) {
    if (isAtLeastIE55 || isFirefox) {
        if (typeof(oRow) == "undefined") {
            optionRows = document.getElementsByTagName('option');
            for (i=0; i<optionRows.length;i++){
                if (optionRows[i].disabled) {
                    optionRows[i].style.color = 'gray';
                }
            }
        } else {
            options = oRow.getElementsByTagName('option');
            for (i=0; i<optionRows.length;i++){
                if (optionRows[i].disabled) {
                    optionRows[i].style.color = 'gray';
                }
            }
        }
    }
}

// SUPPORT FOR ISO8601 TIMESTAMPS

var dayMaxStandard   = new Array(31,28,31,30,31,30,31,31,30,31,30,31);
var dayMaxLeapYear = new Array(31,29,31,30,31,30,31,31,30,31,30,31);
function isLeapYear(year) {
    if ((year/4)   != Math.floor(year/4))   return false;
    if ((year/100) != Math.floor(year/100)) return true;
    if ((year/400) != Math.floor(year/400)) return false;
    return true;
}

function pad(number,length) {
    var str = '' + number;
    while (str.length < length)
        str = '0' + str;
    return str;
}

// these functions assume that there is only one date control with the supplied name
// date controls a made up of 8 form control parts, most of which need to be manipulated
// to disable/enable the control. they all have a unique "by control" prefix however which
// is used to uniquely identify the control they belong to, and a unque suffix which indentifies
// each individual part. the name supplied to the controls, is actually the middle section of
// the control's name.
// control parts look like this:  uniquePrefixToIndentifyTheControl + "_" + xpathOrUserGivenControlName + optionalPartNameIfAppropriate
function disableTsByName(fm,xpathOrUserGivenControlName) {
    setStateTsByName(fm,xpathOrUserGivenControlName,true);
}
function enableTsByName(fm,xpathOrUserGivenControlName) {
    setStateTsByName(fm,xpathOrUserGivenControlName,false);
}
function setStateTsByName(fm,xpathOrUserGivenControlName,state) {
    //alert('setStatsTsByName: '+xpathOrUserGivenControlName+' '+state);
    //alert('form = '+fm.tagName);
    //var collection = fm.all; -- never used
    // make a pass through the form, and (en|dis)able all control parts that look like they belong
    var rootPart = null;
    var tsControlName = null;
    for (var i=0; i<fm.elements.length; i++) {
        node = fm.elements[i];
        if (node.tagName == 'SELECT') {
            if ((node.name.indexOf('_'+xpathOrUserGivenControlName) > 0) &&
               ((node.name.indexOf('seconds') == node.name.length-7) ||
                (node.name.indexOf('minutes') == node.name.length-7) ||
                (node.name.indexOf('hour') == node.name.length-4) ||
                (node.name.indexOf('day') == node.name.length-3) ||
                (node.name.indexOf('month') == node.name.length-5) ||
                (node.name.indexOf('year') == node.name.length-4))) {
                node.disabled = state;
                // use one of them to also work out the tsControlName
                if (node.name.indexOf('year') == node.name.length-4) {
                  tsControlName = node.name.substr(0,node.name.length-4);
                }else if (node.name.indexOf('hour') == node.name.length-4) {
                  tsControlName = node.name.substr(0,node.name.length-4);
                }
            }
        }
        // see if this is the root part, if so then grab it
        if ((node.tagName == 'INPUT') && (node.type == 'hidden') && (node.name.indexOf('_'+xpathOrUserGivenControlName) == (node.name.length - xpathOrUserGivenControlName.length -1))) {
            //alert('got node - '+node.tagName+' '+node.name);
            domNode = node;
        }
    }
    // now resets various parts depending on the new state
    if (state == true) {
        if (typeof(domNode) != 'undefined') {
            domNode.value = '';
        }
    } else {
      checkTs(tsControlName);
    }
}
        
// this is the base control name, which prefixes and suffixes are added to for individual parts
// control parts look like this:  uniquePrefixToIndentifyTheControl + "_" + xpathOrUserGivenControlName + optionalPartNameIfAppropriate
// tsControlName is the first three sections of the name, without the optionalPartNameIfAppropriate
function checkTs(tsControlName) {
	if (!tsControlName) {
		return;
	}
    //alert('checkTs for '+tsControlName);
    var seconds = getControlValue(document.getElementById(tsControlName+'seconds'));
    var minutes = getControlValue(document.getElementById(tsControlName+'minutes'));
    var hour = getControlValue(document.getElementById(tsControlName+'hour'));
    var day = getControlValue(document.getElementById(tsControlName+'day'));
    var month = getControlValue(document.getElementById(tsControlName+'month'));
    var year = getControlValue(document.getElementById(tsControlName+'year'));
    // check for correct days
    if (isLeapYear(year)) dayMax = dayMaxLeapYear;
    else dayMax = dayMaxStandard;
    if (day > dayMax[month-1]) {
        day = dayMax[month-1];
        setControlValue(document.getElementById(tsControlName+'day'),day);
    }
    //alert('dt control from document.all = '+document.all(tsControlName+'dateTimeType').length);
    var dateTimeType = getControlValue(document.getElementById(tsControlName+'dateTimeType'));
    var theDate = '';
    //alert('dateTimeType = '+dateTimeType);
    if ((dateTimeType == 'D' || dateTimeType == 'DT')) {
        theDate += year+'-'+pad(month,2)+'-'+pad(day,2);
    }
    if (dateTimeType == 'DT') {
        theDate += 'T';
    }
    if ((dateTimeType == 'T') || (dateTimeType == 'DT')) {
        theDate += pad(hour,2)+':'+pad(minutes,2)+':'+pad(seconds,2);
    }
    if (dateTimeType == 'E') {
        // they're using the standard control but they want to 
        // record the time in epoch format. Note that javascript
        // records month weird (Jan = 0, Feb = 1 etc) hence the -1
        theDate = (new Date(year, month - 1, day, hour, minutes, seconds)) / 1000;
    }
    //alert('setting value for '+tsControlName.substr(tsControlName.indexOf('_')+1));
    setControlValue(document.getElementById(tsControlName.substr(tsControlName.indexOf('_')+1)),theDate);
    //alert('the date = '+theDate);
}

function checkEpoch(tsControlName) {
    //alert('checkTs for '+tsControlName);
    var seconds = getControlValue(document.getElementById(tsControlName+'seconds'));
    var minutes = getControlValue(document.getElementById(tsControlName+'minutes'));
    var hour = getControlValue(document.getElementById(tsControlName+'hour'));
    var day = getControlValue(document.getElementById(tsControlName+'day'));
    var month = getControlValue(document.getElementById(tsControlName+'month'));
    var year = getControlValue(document.getElementById(tsControlName+'year'));
    // check for correct days
    if (isLeapYear(year)) dayMax = dayMaxLeapYear;
        else dayMax = dayMaxStandard;
    if (day > dayMax[month-1]) {
        day = dayMax[month-1];
        setControlValue(document.getElementById(tsControlName+'day'),day);
    }
    // the minus one is because javascript is a knob, it counts January as 0 not 1
    var epochDate = new Date(year, month - 1, day, hour, minutes, seconds);
    //alert('setting value for '+tsControlName.substr(tsControlName.indexOf('_')+1));

    setControlValue(document.getElementById(tsControlName.substr(tsControlName.indexOf('_')+1)), (epochDate / 1000));
}

// MISCELLANEOUS CONTROL FUNCTIONS
// Returns the value of a control.
// This may be a single control, or an array, and we calculate the best way to handle it. In most cases a single string
// with the value is returned, except in the case of a multiple select where an array of strings is returned. If the control
// is a radio, or array of radios, and none are currently selected, or a checkbox is not checked, then an empty string
// is returned.
function getControlValue(control) {
     //alert('typeXXX: '+control.type+ ' '+control.tagName+' '+control.length+' '+typeof(control)+' '+control+' '+control.name);
    if (typeof(control) == 'undefined' || control == null) {
    	return null;
    }
    if (typeof(control) == "undefined") {
        return '';
    }
    // is it an array?
    if (typeof(control.length) == "undefined") {
        // no, handle as if normal control
        // if it is a radio or checkbox, see if it is actually checked
        if ((control.type == 'radio') || (control.type == 'checkbox')) {
            if (control.checked) {
                return control.value;
            } else {
                return '';
            }
        } else {
            // standard control, so just return the value
            return control.value;
        }
    } else {
        // is an array
        // see if a single select
        if (control.type == 'select-one') {
            return control.options[control.selectedIndex].value;
        }
        // see if a multiple select, and if so return an array 
        if (control.type == 'select-multiple') {
            var values = Array();
            for (var i=0; i<control.options.length; i++) {
                if (control.options[i].selected) {
                    values[values.length] = control.options[i].value;
                }
            }
            return values;
        }
        // see if it is an array of selects -- this is a bug in IE. We duplicate our date select rows in a repeat, yet for
        // some reason IE keeps a "shadow" control around for all controls which have been repeated. So if you have three date
        // controls in a repeater, asking for the first one will always return all three, even though the names are different.
        // we're only interested in the first one, the real one, so we look up the result of it alone.
        if (control[0].type == 'select-one') {
          return control[0].options[control[0].selectedIndex].value;
        }
        if (control[0].type == 'hidden') {
          return control[0].value;
        }
        // assume radio or checkbox
        for (var i=0; i<control.length; i++) {
            if (control[i].checked) {
                return control[i].value;
            }
        }
        // none were checked, so return nothing
        return '';
    }
}

function setControlValueById(id, value) {
    var o = document.getElementById(id);
    setControlValue(o,value);
}

function setControlValue (control, value) {
    //alert('setting length -- '+control.length);
    if (typeof(control.length) == "undefined") {
        //alert('setting type = '+control.type);
        // normal control, so set the value;
        control.value = value;
        return;
    } else {
        if (control.type == 'select-one') {
            control.value = value;
            return;
        }
        if (control.type == 'select-multiple') {
            // not yet suported
            return;
        }
        // see if it is an array of selects -- this is a bug in IE. We duplicate our date select rows in a repeat, yet for
        // some reason IE keeps a "shadow" control around for all controls which have been repeated. So if you have three date
        // controls in a repeater, asking for the first one will always return all three, even though the names are different.
        // we're only interested in the first one, the real one, so we look up the result of it alone.
        //alert('setting type = '+control[0].type);
        if (control[0].type == 'select-one') {
            control[0].value = value;
        }
        if (control[0].type == 'hidden') {
            control[0].value = value;
        }
        if (control[0].type == 'text') {
            control[0].value = value;
        }
    }
}

// GENERIC PAGE ONLOAD HANDLER
var bodyOnLoadList = new Array();
function addBodyOnLoad(codeString) {
    bodyOnLoadList[bodyOnLoadList.length] = codeString;
}
var bodyLoaded = false;
function pageBodyOnLoad() {
    bodyLoaded = true; // note this won't activate until all images are loaded
    // if you need to do work before then you will need to find an alternative
    for(onLoadIndex=0; onLoadIndex<bodyOnLoadList.length; onLoadIndex++) {
        eval(bodyOnLoadList[onLoadIndex]);
    }
}

// DYNAMIC/DELAYED HTML ADDER
function addDelayedHtml(html,markerId) {
    var marker = document.all ? document.all[markerId] : document.getElementById(markerId);
    marker.outerHTML = marker.outerHTML + html;
}

function addDelayedTableRow(html,tableId) {
    if (document.all || document.getElementById) {
        // find the table
        var theTable = document.all ? document.all[tableId] : document.getElementById(tableId);
        //reTd = /<td><\/td>|<td>|<\/td>|<tr>|<\/tr>/gi;
        reTd = new RegExp("<td|<tr>|</tr>","gi");
        var tdArray = html.split(reTd);
        var theRow = theTable.insertRow(-1);
        for (i=0; i<tdArray.length; i++) {
            var theTd = theRow.insertCell(-1);
            //alert('setting cell to <td'+tdArray[i]);
            theTd.outerHTML = '<td'+tdArray[i];
        }
    }
}

function sytadelEnableTextElementById(id,state) {
    if (document.all || document.getElementById) {
        var element = document.all ? document.all[id] : document.getElementById(id);
        if (state) {
            element.style.color = '#000000';
        } else {
            element.style.color = '#999999';
        }
    }
}

// Get a cookie from the user agent
function getCookie(name){
    var dc = document.cookie;
    if (dc.length > 0) {
        var cname = name + "=";               
        var begin = dc.indexOf(cname);       
        if (begin != -1) {           
            begin += cname.length;       
            end = dc.indexOf(";", begin);
            if (end == -1)
                end = dc.length;
            return unescape(dc.substring(begin,end));
        } 
    }
    return null;
}

// Set a cookie in the user agent
function setCookie(name, value, expires, path, domain, secure) {
    document.cookie = name + "=" + escape(value) + 
    ((expires) ? ";expires=" + expires.toGMTString() : "") +
    ((path) ? ";path=" + path : "") +
    ((domain) ? ";domain=" + domain : "") +
    ((secure) ? ";secure" : "");
}

// Delete a cookie from the user agent
//
// Note that there's no way to actually delete a cookie, you must set
// it's expiry date to something earlier than now. If you shouldn't
// delete a cookie if you're just going to set it again. setCookie
// will correctly replace the current cookie.
//
// In IE 6, the expired cookie will not actually be deleted from the user agent
// until the page has finished rendering.
function deleteCookie (name,path,domain) {
    if (getCookie(name)) {
        document.cookie = name + "=" +
        ((path == null) ? "" : "; path=" + path) +
        ((domain == null) ? "" : "; domain=" + domain) +
        "; expires=Thu, 01-Jan-1970 00:00:01 GMT";
    }
}

/*
* Count the number of letters (and printing characters) in a text area
* @param DOMObject field - the field you want to counter
* @param DOMObject cntfield -  the field where the counter information goes
* @param int maxlimit - the maximum number of characters
*/
function textCounter(field,cntfield,maxlimit) {
    if (field.value.length > maxlimit) {
        // if too long...trim it!
        field.value = field.value.substring(0, maxlimit);

    } else {
        // otherwise, update 'characters done' counter
        cntfield.value = field.value.length;
    }
}

//
// This functions hides divs based on the selections made in a radio check box group
//
// @param string container : the name of the div that holds all the divs to hide/show
// @param string control : the name of the div that holds the radio control buttons
// @param string prefix : the name of the unique prefix for these divs. Used to make sure we hide only the correct things
// @param object exceptions : a hash table of exceptions. Eg: exceptions['none'] = 'until' means that if the none div is visible then hide the 'until' div. 
//
function controlTabs (container, control, prefix, exceptions) {
    var controlElement = document.getElementById(control);
    var containerElement = document.getElementById(container);

    var radios = controlElement.getElementsByTagName('input');
    var divs = containerElement.getElementsByTagName('div');
    for (var i=0; i < radios.length;i++) {
        if (radios[i].type == 'radio') {
            if (radios[i].checked) {
                for (excep in exceptions) {
                    if (radios[i].value == excep){
                        document.getElementById(prefix + exceptions[excep]).style.display = 'none';
                    } else {
                        document.getElementById(prefix + exceptions[excep]).style.display = 'block';
                    }
                }
                document.getElementById(prefix + radios[i].value).style.display = 'block';
            } else {
                document.getElementById(prefix + radios[i].value).style.display = 'none';
            }
        }
    }
}

/**
 * Copies a (raw) form control value from one element to another.
 *
 * @param mixed fromElement Either an ID or the actual element to copy the value from.
 * @param mixed toElement Either an ID or the actual element to copy the value to.
 */
function sytadelCopyControlValue (fromElement,toElement) {
    // TODO: once prototype is integrates, these typeofs can be removed,
    // because prototype's $() accepts strings and elements.
    if (typeof fromElement == 'string') {
        fromElement = $(fromElement);
    }
    if (typeof toElement == 'string') {
        toElement = $(toElement);
    }
    toElement.value = fromElement.value;
    // TODO: prototype - $(toElement).value = $(fromElement).value;
}

// Returns an HTML #rrggbb color string from a given string.
//
// The incoming string may be in any of the following formats:
//
// #123, #abc, #aa9900, #123456, rgb(1, 2, 3), rgb(255,200,100)
//
// In most browsers, the CSS DOM returns colour values the
// same way as they were specified, however Firefox 2.0 always
// returns them as rgb(r,g,b) values. This function does the
// conversion.
function getRrggbbFromColor (colorString) {
    // see if it's already #rrggbb
    var regexp = new RegExp("^#[0-9a-fA-F]{6}$");
    if (regexp.test(colorString)) {
        return colorString;
    }
    // see if it's #rgb
    var regexp = new RegExp("^#[0-9a-fA-F]{3}$");
    if (regexp.test(colorString)) {
        return '#'+colorString.substr(1,1)+colorString.substr(1,1)+colorString.substr(2,1)+colorString.substr(2,1)+colorString.substr(3,1)+colorString.substr(3,1);
    }
    // see if it's rgb(n, n, n)
    var regexp = new RegExp("^rgb\\((\\d*),\\s*(\\d*),\\s*(\\d*)\\)$");
    if (regexp.test(colorString)) {
        var rgb = colorString.match(regexp);
        return '#'+convertDecToHex(rgb[1],2)+convertDecToHex(rgb[2],2)+convertDecToHex(rgb[3],2);
    }
    // unknown
    return colorString;
}

// converts a decimal to hexadecimal, with padding of zeroes on the left
function convertDecToHex (value,padToLength) {
    return hex = pad(Number(value).toString(16),padToLength);
}

function $() {
    var elements = new Array();
    
    for (var i = 0; i < arguments.length; i++) {
        var element = arguments[i];
        if (typeof element == 'string') {
    	   element = document.getElementById(element);
        }
        if (arguments.length == 1) {
    	   return element;
        }
        elements.push(element);
    }
    return elements;
}

function debugShowHide (uid) {
	var element = $('debug' + uid);
	var anchor = $('debugLink' + uid);
	if (element.style.display == "none") {
		element.style.display = "block";
		element.style.visibility = "visible";

	    anchor.innerHTML = '&#8211;';
	} else {
		element.style.display = "none";
		element.style.visibility = "hidden";

	    anchor.innerHTML = '+';
	}
}

function debugShowTables (tblId) {
    var table = $(tblId);
    if (table != 'undefined') {
        if (table.border == 3) {
            table.border = 0;
        } else {
            table.border = 3;
        }
    }
}

function internetLeftNavToggle (divId) {
    linkElement = $('link_' + divId);
    if (linkElement == null) {
        return true;
    }
    divElement = $('menu_' + divId);
    imgElement = linkElement.childNodes[0];
    if (divElement.style.display == 'none') {
        divElement.style.display = 'block';
        imgElement.src = imgElement.src.replace('Plus', 'Minus');
    } else {
        divElement.style.display = 'none';
        imgElement.src = imgElement.src.replace('Minus', 'Plus');
    }
    
}

// Expand the menu's when the user presses the left or right key
function internetLeftNavKeyDown (event, menuId, parentId) {
    var eventInfo = getEventCode(event);
    if (eventInfo.keyCode == 'right') {
        internetLeftNavToggle(menuId);
    } else if (eventInfo.keyCode == 'left') {
        // close my parent
        internetLeftNavToggle(parentId);
        $('menu_' + parentId).previousSibling.childNodes[0].focus();
    }
    return true;
}
/*
	Developed by Robert Nyman, http://www.robertnyman.com
	Code/licensing: http://code.google.com/p/getelementsbyclassname/
	Copyright (c) 2008 Robert Nyman

    Permission is hereby granted, free of charge, to any person obtaining a copy
    of this software and associated documentation files (the "Software"), to deal
    in the Software without restriction, including without limitation the rights
    to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    copies of the Software, and to permit persons to whom the Software is
    furnished to do so, subject to the following conditions:
    
    The above copyright notice and this permission notice shall be included in
    all copies or substantial portions of the Software.
    
    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    THE SOFTWARE.
*/
var getElementsByClassName = function (className, tag, elm){
	if (document.getElementsByClassName) {
		getElementsByClassName = function (className, tag, elm) {
			elm = elm || document;
			var elements = elm.getElementsByClassName(className),
				nodeName = (tag)? new RegExp("\\b" + tag + "\\b", "i") : null,
				returnElements = [],
				current;
			for(var i=0, il=elements.length; i<il; i+=1){
				current = elements[i];
				if(!nodeName || nodeName.test(current.nodeName)) {
					returnElements.push(current);
				}
			}
			return returnElements;
		};
	}
	else if (document.evaluate) {
		getElementsByClassName = function (className, tag, elm) {
			tag = tag || "*";
			elm = elm || document;
			var classes = className.split(" "),
				classesToCheck = "",
				xhtmlNamespace = "http://www.w3.org/1999/xhtml",
				namespaceResolver = (document.documentElement.namespaceURI === xhtmlNamespace)? xhtmlNamespace : null,
				returnElements = [],
				elements,
				node;
			for(var j=0, jl=classes.length; j<jl; j+=1){
				classesToCheck += "[contains(concat(' ', @class, ' '), ' " + classes[j] + " ')]";
			}
			try	{
				elements = document.evaluate(".//" + tag + classesToCheck, elm, namespaceResolver, 0, null);
			}
			catch (e) {
				elements = document.evaluate(".//" + tag + classesToCheck, elm, null, 0, null);
			}
			while ((node = elements.iterateNext())) {
				returnElements.push(node);
			}
			return returnElements;
		};
	}
	else {
		getElementsByClassName = function (className, tag, elm) {
			tag = tag || "*";
			elm = elm || document;
			var classes = className.split(" "),
				classesToCheck = [],
				elements = (tag === "*" && elm.all)? elm.all : elm.getElementsByTagName(tag),
				current,
				returnElements = [],
				match;
			for(var k=0, kl=classes.length; k<kl; k+=1){
				classesToCheck.push(new RegExp("(^|\\s)" + classes[k] + "(\\s|$)"));
			}
			for(var l=0, ll=elements.length; l<ll; l+=1){
				current = elements[l];
				match = false;
				for(var m=0, ml=classesToCheck.length; m<ml; m+=1){
					match = classesToCheck[m].test(current.className);
					if (!match) {
						break;
					}
				}
				if (match) {
					returnElements.push(current);
				}
			}
			return returnElements;
		};
	}
	return getElementsByClassName(className, tag, elm);
};


function $() {
    var elements = new Array();
    
    for (var i = 0; i < arguments.length; i++) {
        var element = arguments[i];
        if (typeof element == 'string') {
    	   element = document.getElementById(element);
        }
        if (arguments.length == 1) {
    	   return element;
        }
        elements.push(element);
    }
    return elements;
}

$$ = getElementsByClassName;

function isArray() {
    if (typeof arguments[0] == 'object') {  
        var criterion = arguments[0].constructor.toString().match(/array/i); 
        return (criterion != null);  
    }
    return false;
}

// used by getEventCode to provide shortcuts for common keys
var specialKeys = [];
specialKeys[13] = 'enter';
specialKeys[27] = 'escape';
specialKeys[8] = 'delete';
specialKeys[40] = 'down';
specialKeys[38] = 'up';
specialKeys[9] = 'tab';
specialKeys[39] = 'right';
specialKeys[37] = 'left';


// wrapper to get event information
//
// This is mainly to catch the different kinds of keyCodes
function getEventCode (event) {
    if (typeof(event) == 'undefined') {
        var event = window.event;
    }
    if (event.stopPropagation) {
        event.stopPropagation();
    }
    event.cancelBubble = true;
    var keyCode = event.keyCode ? event.keyCode : event.which;
    if (specialKeys[keyCode]) {
        keyCode = specialKeys[keyCode];
    }
    return {'keyCode':keyCode, 'type':event.type};
};

// Given a url use lazy loading to put the results into a script tag
//
// Lazy loading is where you call an external script that returns a JSON (JavaScript Object Notation)
// string and places it into a script tag. This will be loaded by the page during normal page load time
//
// url - the url to poll
// object - the javascript variable that will hold the result (otherwise script should set it's own variable)
function loadRemoteJSONObject (url, object) {
    var head = document.getElementsByTagName('head');
    var script = document.createElement('script');
    if (typeof(object) != 'undefined') {
        var urlParam = (url.indexOf('?') > 0) ? '&' : '?';
        url = url + urlParam + 'callback=' + object;
    }
    script.type="text/javascript";
    script.src = url;
    showLoadingImg();
    head[0].appendChild(script);
};

/**
 * Fixes the predicate in a supplied XPath by working out which row the element
 * is in and using the row index as the predicate.
 *
 * Note that like many Sytadel repeater functions, this will only work within a
 * single repeater, where the XPath only has one predicate.
 *
 * @param HTMLElement elementInRepeaterRow The element which specifies the row.
 * @param string xpath The XPath to fix.
 * @return string The XPath with the correct predicate for the specified row.
 */
function sytadelFixXpathPredicates (elementInRepeaterRow,xpath) {
    var regexp = new RegExp("\\(\\d+\\)","g");
    // we only need to do repeater xpaths
    if (xpath.search(regexp) < 0) {
       return xpath;
    }
    // first our row
    var rowIndex = getElementRepeaterRowIndex(elementInRepeaterRow);
    // did we find us in a repeater row?
    if (rowIndex) {
        // replace the predicate with the actual row index
        xpath = xpath.replace(regexp, '('+rowIndex+')');
        return xpath;
    }
    return xpath;
};

/**
 * Toggle the display of the sub nodes in the menu.
 *
 * @param DOMElement $linkElement The element that was clicked.
 */
function menuToggleChildren (linkElement) {
    var imgElement = linkElement.firstChild;
    var ulElement = linkElement.parentNode.lastChild;
    if (ulElement.style.display == 'none' || ulElement.style.display == '') {
        ulElement.style.display = 'block';
        imgElement.src = imgElement.src.replace('Plus', 'Minus');
    } else {
        ulElement.style.display = 'none';
        imgElement.src = imgElement.src.replace('Minus', 'Plus');
    }
};
