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

/**
 * Called by UiDateTimeSelect class to set the hidden master ISO 8601 date value
 * in the form from the various date/time parts in the control with the specified name.
 *
 * @param string tsControlName The name of the hidden master form control.
 */
function sytadelSetIso8601ForDateTimeControl (tsControlName) {
    //alert('cn:'+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 cn:'+tsControlName.substr(tsControlName.indexOf('_')+1));
    setControlValue(document.getElementById(tsControlName.substr(tsControlName.indexOf('_')+1)),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);
}

/**
 * Called by PublicEvent class to set the hidden master unix timestamp value
 * in the form from the various date/time parts in the control with the specified name.
 *
 * TODO: "epoch" is the zero value for unix timestamps, and so this function is
 * incorrectly named and should be changed at some point to something like
 * sytadelSetTimestampForDateTimeControl.
 *
 * TODO: Should really fold this into sytadelSetIso8601ForDateTimeControl.
 *
 * @param string tsControlName The name of the hidden master form control.
 */
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";
    }
}

/**
 * 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 = textAreaElement.up(3).next('div');
        textDiv.down('.'+characterCountElementPrefix+'Used').innerText = currentCount.toString();
        textDiv.down('.'+characterCountElementPrefix+'Remaining').innerText = currentRemaining.toString();
    }
}

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