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;
}

/**
* Convenience function to disable a sytadel date/time control
*/
function disableTsByName(theForm, controlName) {
    setStateTsByName(theForm,controlName,false);
}
/**
* Convenience function to enable a sytadel date/time control
*/
function enableTsByName(theForm, controlName) {
    setStateTsByName(theForm,controlName,true);
}
/**
 * Enables or disables a sytadel date/time control
 *
 * @param form theForm The form which contains the control.
 * @param string controlName The name of the hidden master form control.
 * @param boolean state true to enable the control, false to disable the control.
 */
function setStateTsByName (theForm, controlName, state) {
    //alert('theForm='+theForm.name+', controlName='+controlName+', state='+state+', theForm[controlName]='+theForm[controlName].name);
    // Return false if there's an error
    if (!theForm || !controlName) {
        return false;
    }
    if (!theForm[controlName]) {
        return false;
    }
    // Get the hidden master control
    var theHiddenMasterControl = theForm[controlName];
    // get the master id so we can get to the other parts of the date control
    var masterId = theHiddenMasterControl.id;
    var dateTimeType = $F(masterId+'dateTimeType');
    
    // If we're disabling the control. We need to set the master control value to an empty string
    // otherwise even though the control is disabled, it will have a value which will subsequently
    // get stored in xml data after the form is submitted.
    if (state == false) {
        theHiddenMasterControl.value = '';
    }
    // If we're re-enabling the control. We need to set the master control value 
    // back from an empty string to an actual date
    if (state == true) {
        sytadelSetIso8601ForDateTimeControl(theForm,controlName);
    }
    // Enable or disable the visible/usable parts of the date control
    if (dateTimeType.indexOf('D') != -1) {
        var day = $(masterId+'day');
        var month = $(masterId+'month');
        var year = $(masterId+'year');
        // Note that sytadelEnableInputField checks for missing elements
        sytadelEnableInputField(day, state);
        sytadelEnableInputField(month, state);
        sytadelEnableInputField(year, state);
    }
    if (dateTimeType.indexOf('T') != -1) {
        var seconds = $(masterId+'seconds');
        var minutes = $(masterId+'minutes');
        var hour = $(masterId+'hour');
        sytadelEnableInputField(seconds, state);
        sytadelEnableInputField(minutes, state);
        sytadelEnableInputField(hour, state);
    }
}

/**
 * Sets the hidden master ISO 8601 date value for the specified date/time control,
 * based on the current values in the mulipart selects for the control.
 *
 * The problem for multipart controls is that it is technically possible to have
 * multiple edits from on the one page, and these may thus have identical form
 * control names. In the case of date/time, we generate the multiple parts with
 * a page unique id, that's is able to be safely renamed in a repeater, and which
 * allows us to look up all of the other parts for the control. To get this however,
 * we need to first look up the control by form and name, and then use its id
 * as the master id for the other parts.
 *
 * @param form theForm The form which contains the control.
 * @param string controlName The name of the hidden master form control.
 * @param boolean asTimestamp Optional parameter, if set to true will store the
 * control's value as a timestamp instead of an ISO 8601. Defaults to ISO 8601.
 * Note that the control's dateTimeType hidden element may override this.
 */
function sytadelSetIso8601ForDateTimeControl (theForm,controlName,asTimestamp) {
    //alert('cn:'+controlName);
    if (!controlName) {
        return;
    }
    if (!theForm[controlName]) {
        return;
    }
    var theHiddenControl = theForm[controlName];
    // get the master id so we can get to the other parts
    var masterId = theHiddenControl.id;
    // ok, now we're safe to use ids. this works on missing elements
    var seconds = $F(masterId+'seconds');
    var minutes = $F(masterId+'minutes');
    var hour = $F(masterId+'hour');
    var day = $F(masterId+'day');
    var month = $F(masterId+'month');
    var year = $F(masterId+'year');
    // check for correct days
    var dayMax;
    if (isLeapYear(year)) {
        dayMax = dayMaxLeapYear;
    } else {
        dayMax = dayMaxStandard;
    }
    if (day > dayMax[month-1]) {
        day = dayMax[month-1];
        $(masterId+'day').value = day;
    }
    //alert('dt control from document.all = '+document.all(controlName+'dateTimeType').length);
    var dateTimeType = $F(masterId+'dateTimeType');
    var theDate = '';
    //alert('dateTimeType = '+dateTimeType);
    if (dateTimeType.indexOf('D') != -1) {
        theDate += year+'-'+pad(month,2)+'-'+pad(day,2);
    }
    if (dateTimeType.indexOf('D') != -1 && dateTimeType.indexOf('T') != -1) {
        theDate += 'T';
    }
    if (dateTimeType.indexOf('T') != -1) {
        theDate += pad(hour,2)+':'+pad(minutes,2)+':'+pad(seconds,2);
    }
    // TODO: this needs to be renamed from "E" which used to stand for the
    // incorrect term "epoch" into something related to a timestamp.
    if ((dateTimeType == 'E') ||
        ((typeof asTimestamp != 'undefined') && asTimestamp)) {
        // they're using the standard control but they want to 
        // record the time in timestamp format.
        theDate = (new Date(year, month - 1, day, hour, minutes, seconds)) / 1000;
    }
    //alert('setting value for id:'+masterId);
    $(masterId).value = theDate;
    //alert('the date = '+theDate);
}

// TODO: remove this deprecated stub when this item no longer uses it:
// http://intranet.accc.gov.au/content/index.phtml/itemId/869045
function checkTs (tsControlName) {
    sytadelSetIso8601ForDateTimeControl(tsControlName);
}

/**
 * Sets the specified date/time control to the specified ISO 8601 string.
 *
 * We explicitly set both the hidden master value, and the values of all
 * the multipart selects.
 *
 * Note that the ISO 8601 string must be valid, as we don't perform any
 * checking against it.
 *
 * @param Form theForm The form containing the control.
 * @param string controlName The name of the control.
 * @param string iso8601String A valid ISO 8601 date/time string.
 */
function sytadelSetDateTimeControlFromIso8601 (theForm,controlName,iso8601String) {
    if (!controlName) {
        return;
    }
    if (!theForm[controlName]) {
        return;
    }
    // get the masters so we can get to the other parts
    var theHiddenControl = theForm[controlName];
    var masterId = theHiddenControl.id;
    if ($(masterId+'seconds') != 'undefined') {
        $(masterId+'seconds').selectedIndex = parseInt(iso8601String.substr(17,2));
    }
    if ($(masterId+'minutes') != 'undefined') {
        $(masterId+'minutes').selectedIndex = parseInt(iso8601String.substr(14,2));
    }
    if ($(masterId+'hour') != 'undefined') {
        $(masterId+'hour').selectedIndex = parseInt(iso8601String.substr(11,2));
    }
    if ($(masterId+'day') != 'undefined') {
        $(masterId+'day').selectedIndex = parseInt(iso8601String.substr(8,2))-1;
    }
    if ($(masterId+'month') != 'undefined') {
        $(masterId+'month').selectedIndex = parseInt(iso8601String.substr(5,2))-1;
    }
    if ($(masterId+'year') != 'undefined') {
        $(masterId+'year').value = parseInt(iso8601String.substr(0,4));
    }
    theHiddenControl.value = iso8601String; 
}

// 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";
    }
}

/**
 * Sets the character count text for a text area control, based on size
 * of the currently entered text.
 *
 * @param DOMElement textAreaElement The text area element.
 * @param string characterCountElementPrefix The prefix used to mark up the class
 * attributes inside the text string. See the UiTextArea class for more detail.
 * @param int maximum The maximum amount of character that can be stored in this text area.
 */
function sytadelSetCharacterCountStrings (textAreaElement,characterCountElementPrefix,maximum) {
    textAreaElement = $(textAreaElement);
    var currentCount = textAreaElement.value.length;
    if (currentCount > maximum) {
        textAreaElement.value = textAreaElement.value.substring(0,maximum);
    } else {
        var currentRemaining = maximum - currentCount;
        if (currentCount == maximum) {
            currentCount = 'all '+currentCount.toString();
        } else {
            if (currentCount == 0) {
                currentCount = 'no';
            }
        }
        if (currentRemaining == 0) {
            currentRemaining = 'none';
        }
        var textDiv = $$('.'+characterCountElementPrefix).first();
        textDiv.down('.'+characterCountElementPrefix+'Used').innerText = currentCount.toString();
        textDiv.down('.'+characterCountElementPrefix+'Remaining').innerText = currentRemaining.toString();
    }
}

/**
 * This function shows/hides divs based on the selections made in a group of radio or check box controls
 *
 * @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' || radios[i].type == 'checkbox') {
            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 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;
}

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');
    }
};

/**
 * Sets the HTML for a DOM element to the response text from a specific URL.
 *
 * @param string url The URL to load the HTML from.
 * @param HTMLElement domElement The DOM element to set.
 */
function sytadelSetDomElementHtmlFromUrl (url,domElement) {
    new Ajax.Request(url, {
        method: 'get',
        onSuccess: function(transport) {
            domElement.innerHTML = transport.responseText;
        }
    });
}

/**
 * Appends the response text from a specific URL to the HTML for a DOM element.
 *
 * @param string url The URL to load the HTML from.
 * @param HTMLElement domElement The DOM element to append to.
 */
function sytadelAppendDomElementHtmlFromUrl (url,domElement) {
    new Ajax.Request(url, {
        method: 'get',
        onSuccess: function(transport) {
            domElement.innerHTML += transport.responseText;
        }
    });
}

/**
 * Enables or disables an input field, styling it appropriately in IE.
 *
 * IE does not give any visual indication that a text field or textarea is disabled,
 * which is very confusing, this function will style the background of any text field
 * or text area to a light gray when it is disabled, then back to default when it is enabled.
 *
 * @param HTMLElement inputElement the DOM element for the form input to enable/disable
 * @param boolean enabled whether to enable/disable the input
 */
function sytadelEnableInputField (inputElement, enabled) {
    if (typeof(inputElement) != "object") {
        return;
    }
    if (enabled) {
        inputElement.enable();
        if (isIE && browserVersion <= 9 && (inputElement.type == 'text' || inputElement.type == 'textarea')) {
            inputElement.setStyle({backgroundColor: ""});
            inputElement.setOpacity(1);
            inputElement.setStyle("border: auto;");
        }
    } else {
        inputElement.disable();
        if (isIE && browserVersion <= 9 && (inputElement.type == 'text' || inputElement.type == 'textarea')) {
            inputElement.setStyle({backgroundColor: "#eeeeee"});
            inputElement.setOpacity(0.5);
            inputElement.setStyle("border: 1px solid #aaaaaa;");
        }
    }
}

