/*
 * Common.js
 *      this file contains several functions, constants, and variables that are
 *      used application wide.  It should be include in every aspx page of the
 *      application
 */
 
/*
 * MouseManager:
 *      javascript object used to allow for the stacking of mouse events.
 *      An instance of this class is instantiated at the bottom of this
 *      file via the "MouseCoords".  Use it in place of creating your own
 *      instance of this class
 * Params:
 *      none
 * Returns:
 *      true
 */
function MouseManager()
{
    // required to reference this object within the event handler.
    // It limits the number of instances of this class we can have to
    // just 1, but I don't anticipate we will ever need more than
    // one instance to begin with
    var This = this;
    
    // captures X and Y coordinates
    this.X = 0;
    this.Y = 0;
    
    this.mouseMoveHandlers = [];
    
    /*
     * onMouseMove:
     *      called whenever the mouse is moved - stores the current X and Y
     *      coordinates as well as executes any registered handlers
     * Params:
     *      a reference to mouse move event
     * Returns:
     *      true ... always
     */
    this.onMouseMove = function(evnt)
    {
        evnt = (evnt) ? evnt : window.event;
        
        if ( evnt.pageX || evnt.pageY )
        {
	        This.X = evnt.pageX;
	        This.Y = evnt.pageY;
        }
        else if (evnt.clientX || evnt.clientY)
        {
            var scroll_left = ((document.body) && (document.body.scrollLeft)) ? document.body.scrollLeft : 0;
            var scroll_top  = ((document.body) && (document.body.scrollTop)) ? document.body.scrollTop : 0;
	        This.X = evnt.clientX + scroll_left + document.documentElement.scrollLeft;
	        This.Y = evnt.clientY + scroll_top  + document.documentElement.scrollTop;
        }
        
        for (var cnt=0; cnt<This.mouseMoveHandlers.length; cnt++)
        {
            // we remove handlers from this object by nulling out the entry
            // If we find a null entry, just skip over it and move onto the
            // next one
            if ( null == This.mouseMoveHandlers[cnt] ) continue;
            
            // execute the handler
            This.mouseMoveHandlers[cnt](evnt);
        }
    	
        return true;
    }
    
    /*
     * addMouseMoveHandler:
     *      allows for the registration one or more mouse move events
     * Params:
     *      handler - a reference to the mouse move handler to be executed
     *          when a mouse move event is detected
     * Returns:
     *      true
     */
    this.addMouseMoveHandler = function(handler)
    {
        if ( handler )
        {
            var found = false;
            var idx   = -1;
            
            // only allow 1 registration per handler
            for (var cnt=0; cnt<This.mouseMoveHandlers.length; cnt++)
            {
                if (This.mouseMoveHandlers[cnt] == handler)
                {
                    found = true;
                    break;
                }
                else if ( null == This.mouseMoveHandlers[cnt] )
                {
                    idx = cnt;
                }
            }
            
            // if no prior registration of the specified handler was
            // found, add it
            if ( false == found )
            {
                if ( -1 == idx )
                {
                    This.mouseMoveHandlers.push(handler);
                }
                else
                {
                    This.mouseMoveHandlers[idx] = handler;
                }
            }
        }
        
        return true;
    }
    
    /*
     * removeMouseMoveHandler:
     *      removes a handler that was previously registered with
     *      this object
     * Params:
     *      handler - the handler to remove
     * Returns:
     *      true
     */
    this.removeMouseMoveHandler = function(handler)
    {
        if ( handler )
        {
            for (var cnt=0; cnt<This.mouseMoveHandlers.length; cnt++)
            {
                if ( This.mouseMoveHandlers[cnt] == handler )
                {
                    // there's (currently) no way to remove an item at a
                    // specified index, so we'll null it out instead of
                    // going through complicated array resize logic.
                    This.mouseMoveHandlers[cnt] = null;
                    break;
                }
            }
        }
        
        return true;
    }
    
    // capture mouse move events
    if (!document.all) document.captureEvents(Event.MOUSEMOVE);
    document.onmousemove = this.onMouseMove;
    
    return true;
}

/*
 * centerDiv:
 *      centers the child div in relation to the parent
 * Params:
 *      parentElm - the parent element that the child element is to
 *          be centered in relation to
 *      child - the element to be centered
 * Returns:
 *      false
 */
function centerDiv(parentElm, child)
{
    if ( (parent) && (child) )
    {
        var parent_pos = getControlPosition(parentElm);
        var parent_mid_y = parent_pos.y + (parentElm.clientHeight / 2);
        var parent_mid_x = parent_pos.x + (parentElm.clientWidth  / 2);
        
        child.style.top  = (parent_mid_y - (child.clientHeight / 2)) + STR_PX;
        child.style.left = (parent_mid_x - (child.clientWidth  / 2)) + STR_PX;
    }
    
    return false;
}

/*
 * checkDateField:
 *      used to check the format of a specified field used to store a date
 *      value.  If this field is not set to a value, the recommended date
 *      format string is assigned to it
 * Params:
 *      dtFieldName - the id of the date field to check
 * Returns:
 *      false
 */
function checkDateField(dtFieldName) {
    if ( (dtFieldName) && (0 < dtFieldName.length) ) {
        var ctl = document.getElementById(dtFieldName);

        if (ctl) {
            if ( (0 == trim(ctl.value).length) || (DT_FORMAT == ctl.value) ) {
                ctl.value = DT_FORMAT;
                ctl.style.color = CLR_GRAY;
            }
            else {
                ctl.style.color = CLR_BLACK;
            }
        }
    }
    
    return false;
}

/*
 * checkFieldLength:
 *      checks to see if the specified control's text length exceeds
 *      the allowed limit for the control
 * Params:
 *      ctl - a reference to the control to examine
 * Returns:
 *      true if the text in the control is still within the allowable limit
 *      for the control; otherwise false
 */
function checkFieldLength(ctl) {
    var result = true;
    
    if ( ctl )
    {
        // based on the control ID, search the hashtable to retrieve the
        // max number of characters allowed by this field
        var max_length = MaxFieldLengths.find(ctl.id);
        
        if ( max_length ) {
            result = ctl.value.length < max_length;
        }
    }
    
    return result;
}

/*
 * checkValidLength:
 *      event handler that fires whenever a key is pressed in a given field.
 *      This function calls the checkFieldLength function to determine if the
 *      given control's text has exceeded the allowable limit
 * Params:
 *      evt - a reference to the event object corresponding to a keydown event
 * Returns:
 *      true if the text within the control that fired the event is still within
 *      the accepted length; otherwise false
 */
function checkValidLength(evt) {
    var result = true;
    
    // account for browser differences
    if ( !evt ) evt = window.event;
    
    var key_code = (evt.keyCode) ? evt.keyCode : evt.which;
    var target = (evt.target)? evt.target : evt.srcElement;
    
    switch (key_code) {
        // these are characters such as backspace, delete, left, right,
        // etc ... They're keys that shouldn't be blocked, regardless
        // of whether or not the text field is filled
        case 8:  case 9:  case 12:  case 13: case 14:
        case 35: case 36: case 37:  case 38: case 39:
        case 40: case 46: case 112: case 116:
            result = true;
            break;
        case 27:
            // escape character ... although the name of the function doesn't
            // explictly imply this type of behavior
            result = true;
            if ( target ) target.value = VAL_EMPTY_STRING;
            break;
        default:
            // check the element's field length
            if ( target ) result = checkFieldLength(target);
            
            // if the user has entered more than the allowable number of characters,
            // then cancel the keystroke event
            if ( false == result ) {
                evt.returnValue = false;
                evt.cancelBubble = true;
            }
    }
    
    if ( ErrorMessage ) {
        ErrorMessage.close();
        ErrorMessage = null;
    }
   
    return result;
}

/*
 * checkValidName:
 *      this function is called as the result of a keydown event.  The function
 *      checks the length of the text that is currently stored in the text field
 *      that is consuming this event as well as ensuring that we process only
 *      valid alpha characters
 * Params:
 *      a reference to an event object that contains data about the system event
 *      firing this method
 * Returns:
 *      true if the current key is valid for this field; otherwise false.  If
 *      this function returns false, the returnValue and cancelBubble properties
 *      of evt are also modified.
 */
function checkValidName(evt) {
    var result = false;
    
    // first, check the current field length
    result = checkValidLength(evt);
    
    if ( true == result ) {
        if ( !evt ) evt = window.event;
        var key_code = (evt.keyCode) ? evt.keyCode : evt.which;
        
        switch (key_code) {
            // we only allow alpha characters and characters such
            // as enter, del, backspace, etc...
            case 8:  case 9:  case 12: case 13: case 14:
            case 16: case 35: case 36: case 37: case 38:
            case 39: case 40: case 46: case 65: case 66:
            case 67: case 68: case 69: case 70: case 71:
            case 72: case 73: case 74: case 75: case 76:
            case 77: case 78: case 79: case 80: case 81:
            case 82: case 83: case 84: case 85: case 86:
            case 87: case 88: case 89: case 90: case 112:
            case 116:
                result = true;
                break;
            default:
                result = false;
                evt.returnValue = false;
                evt.cancelBubble = true;
        }
    }
    
    return result;
}

/*
 * displayError:
 *      displays an error div next to the specified control
 * Params:
 *      msg - the error message to display
 *      control - the control that generated the error
 *      containingDiv - the control's containing DIV
 * Returns:
 *      nothing
 */
function displayError(msg, control, containingDiv)
{
    window.alert(msg);
    control.focus();
}

/*
 * getPosition:
 *      gets the position of the control on the screen
 * Params:
 *      control - the control to obtain the position for
 * Returns:
 *      the current x and y coordinates of the mouse
 */
function getControlPosition(control)
{
    var left = 0;
    var top  = 0;
    
    if (control)
    {
        while (control.offsetParent)
        {
            left += control.offsetLeft;
            top  += control.offsetTop;
            control = control.offsetParent;
        }
        
        left += control.offsetLeft;
        top  += control.offsetTop;
    }

    return {x:left, y:top};
}

/*
 * getRbSelected:
 *      gets the value of the currently select radio button
 *      in a radio button group
 * Params:
 *      rbGroup - reference to the radio button group to be examined
 * Returns:
 *      the value of the selected radio button or null if no radio
 *      button is selected
 */
function getRbSelected(rbGroup) {
    var result = null;
    
    if ( rbGroup ) {
        if ( rbGroup.length ) {
            for (var cnt=0; cnt<rbGroup.length; cnt++) {
                if ( true == rbGroup[cnt].checked ) {
                    result = rbGroup[cnt].value;
                    break;
                }
            }
        }
        else if ( rbGroup.value ) {
            result = rbGroup.value;
        }
    }
    
    return result;
}

/*
 * ltrim:
 *      trims leading spaces from a string
 * Params:
 *      val - the string value to trim
 * Returns:
 *      a string with the leading spaces removed
 */
function ltrim(val)
{
    return val.replace( /^\s+/g, VAL_EMPTY_STRING );
}

/*
 * onCategoryChanged:
 *      called when the parentCtl category is changed on the form.
 * Params:
 *      parentCtl - a reference to the parentCtl category control
 */
function onCategoryChanged(parentCtl)
{
    var is_loading = false;
    
    // parentCtl will be null if called from the document's onload event
    if ( !parentCtl )
    {
        parentCtl = document.getElementById(CTL_CATEGORY);
        is_loading = true;
    }

    // create and initialize an XMLHttpRequest object
    var xmlHttp = loadAjax();
    
    // sub_opts is the sub-option drop down list that will be populated with data, based on
    // what is selected from the top-level category drop down
    var sub_opts = document.getElementById(CTL_SUBCATEGORY);

    // make sure our input parameters are valid
    if ( (null != xmlHttp) && (null != sub_opts) && (null != parentCtl) )
    {
        // always clear out our sub-options list
        sub_opts.options.length = 0;
                    
        var selected_value = parentCtl.options[parentCtl.selectedIndex].value;
        
        if ( 0 != selected_value )
        {
            // build our request
            var url = "/servlets/ListHandlers.asmx";
            var body = " \
                <soap12:Envelope xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' xmlns:xsd='http://www.w3.org/2001/XMLSchema' xmlns:soap12='http://www.w3.org/2003/05/soap-envelope'> \
                    <soap12:Body> \
                        <GetListSubCategory xmlns='http://www.irankd.com/'> \
                            <parentId>" + selected_value + "</parentId> \
                        </GetListSubCategory> \
                    </soap12:Body> \
                </soap12:Envelope>";

            // submit the request to the server
            setRequest(xmlHttp, url, body);
            
            // make sure we succeeded
            if ( (4 == xmlHttp.readyState) && (200 == xmlHttp.status) )
            {
                // work around for an issue with IE where the responseXML attribute from
                // the xmlHttp object is not available ... this call gets a DOM that will
                // work in either IE or Mozilla
                var dom = getDom(xmlHttp);
                
                if ( null != dom )
                {
                    // add a blank option to the list
                    sub_opts.options[0] = new Option(VAL_EMPTY_STRING, 0);

                    // there will only be 1 element in this response - it's a semi-colon
                    // delimited list of options to populate the drop-down list with.  The
                    // text/value options are themselves delimited by a ":"
                    var response = dom.getElementsByTagName(ELM_SUBCAT_RESULT);
                    
                    if ( (null != response) && (0 < response.length) )
                    {
                        var options = response[0].firstChild.nodeValue.split(DEL_SEMICOLON);

                        if ( 0 < options.length )
                        {
                            var hidden_fld = document.getElementById(CTL_SUBCATHIDDEN);
                            var cnt;
                            var opt_cnt = 1;
                            var sel_idx = -1;
                            
                            if ( hidden_fld )
                            {
                                // loop through all the options and populate the secondary list
                                // with them
                                for (cnt=0; cnt<options.length; cnt++)
                                {
                                    var pair = options[cnt].split(DEL_COLON);

                                    if ( 2 == pair.length )
                                    {
                                        var opt = new Option(pair[1], pair[0]);
                                        sub_opts.options[opt_cnt++] = opt;
                                        
                                        if ( (true == is_loading) && (pair[0] == hidden_fld.value) )
                                        {
                                            sel_idx = cnt;
                                        }
                                    }
                                }
                                
                                // if a match wasn't found, sel_idx will be -1
                                if ( 0 <= sel_idx )
                                {
                                    // plus 1 to account for the null entry at the start of the list
                                    sub_opts.selectedIndex = sel_idx + 1;
                                }
                                else
                                {
                                    hidden_fld.value = VAL_EMPTY_STRING;
                                }
                            }
                            else
                            {
                                // there are some forms that use this function and which have no
                                // concept of the "hidden_fld" control.  For those pages, we're
                                // just going to load the drop-down list control and exit
                                for (cnt=0; cnt<options.length; cnt++)
                                {
                                    var pair = options[cnt].split(DEL_COLON);
                                    
                                    if ( 2 == pair.length )
                                    {
                                        var opt = new Option(pair[1], pair[0]);
                                        sub_opts.options[opt_cnt++] = opt;
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}

/*
 * onCheckExpiryDate:
 *      validates the expiry date for the list expiry date field
 * Params:
 *      control - a reference to the expiry date control
 * Returns:
 *      true if the parameter is valid, otherwise false
 */
function onCheckExpiryDate(control) {
    if ( !control ) {
        control = ExpiresField;
    }

    if ( control ) {
        if ( (0 < control.value.length) && (DT_FORMAT != control.value) ) {
            var result = onDateFieldLeave(control);
            
            if ( true == result ) {
                var today = new Date();
                var expiry = systemDateToCommonDate(control.value);
                var date_diff = (expiry - today) / ONE_DAY;
                
                if ( 0 >= date_diff ) {
                    window.alert("The expiry date for the iRANK must be set to at least 1 day in the future.");
                    result = false;
                    
                    // scwNextAction will only be assigned if we set the value from the calendar
                    // control.  In that case, set the focus to the date field.  Otherwise we set
                    // the control value via a direct edit.  Mozilla doesn't honor the focus call
                    // if the control just lost focus so we'll just blank out the value
                    if ( scwNextAction ) {
                        control.style.color = VAL_EMPTY_STRING;
                    }
                    else {
                        control.value = DT_FORMAT;
                        control.style.color = CLR_GRAY;
                    }
                }
                else if ( 0 == control.value.length ) {
                    control.style.color = CLR_GRAY;
                    control.value = DT_FORMAT;
                }
                else {
                    control.style.color = VAL_EMPTY_STRING;
                }
            }
        }
        else {
            control.style.color = CLR_GRAY;
            control.value = DT_FORMAT;
        }
    }
    
    return result;
}

/*
 * onDateFieldFocus:
 *      called when the date field receives the input focus.  This method
 *      will clear out the date format hint if the user has not entered any
 *      data into the control
 * Params:
 *      control - a reference to the page's date of birth control
 * Returns:
 *      false
 */
function onDateFieldFocus(control) {
    if ( (control) && (DT_FORMAT == control.value) ) {
        control.value = VAL_EMPTY_STRING;
        control.style.color = CLR_BLACK;
    }
    
    return false;
}

/*
 * onDateFieldLeave:
 *      called when the date of birth field loses focus, this
 *      method checks the format of the entered date and if the
 *      field contains no data, populates it with the date
 *      format mask
 * Params:
 *      control - a reference to the date control
 * Returns:
 *      true if the date control contains valid or no text; otherwise false
 */
function onDateFieldLeave(control) {
    var result = onValidateDate(control);
    
    if ( (true == result) && (0 == trim(control.value).length) ) {
        control.value = DT_FORMAT;
        control.style.color = CLR_GRAY;
    }
    
    return result;
}

/*
 * onPageLoad:
 *      called on the loading of a document.  Peforms common initialization routines
 * Params:
 *      object - a reference to any object the caller chooses to pass into this method.
 *          The parameter itself is currently not used by this function
 * Returns:
 *      nothing
 */
function onPageLoad(object)
{
    var info_data = document.getElementById(ID_INFO_DATA);
    
    // if there's a DIV with the ID "infoData", it'll be the focus of the page and the
    // default pageContent DIV will be hidden
    if ( info_data )
    {
        var page_content = document.getElementById(ID_PAGE_CONTENT);
        
        // sanity check - this DIV should always be posted back, but we don't want to
        // incurr an error if, for some reason, it isn't
        if ( page_content )
        {
            // hide the default content DIV
            page_content.className = STYLE_HIDDEN;
        }
        
        // reposition the div and make visible
        info_data.className = STYLE_HIDDEN;        
        positionContentDiv(info_data);
        info_data.className = STYLE_CONTENT;
    }
}

/*
 * onValidateDate:
 *      checks to see if the value entered in the provided
 *      control is a valid date value
 * Params:
 *      the control to validate
 * Returns:
 *      true if the control contains a valid date value; otherwise false
 */
function onValidateDate(control)
{
    var result = true;
    
    if ( control )
    {
        try
        {
            // trim leading and trailing spaces ... we're not too concerned
            // with a field full of spaces
            val = trim(control.value);
            
            if ( 0 < val.length )
            {
                // parse the date
                var date_val = Date.parse(control.value);
                
                // test to see if we got a number back
                if ( true == isNaN(date_val) )
                {
                    result = false;
                }
            }
        }
        catch (e)
        {
            result = false;
        }
    }
    
    if ( false == result )
    {
        displayError((control.value + " is not a valid date"), control);
    }
    
    return result;
}

/*
 * positionContentDiv
 *      positions a new or existing div element before the standard content DIV.
 *      This method is normally called due to an AJAX call retrieving some list
 *      data
 * Params:
 *      infoData - the div element to reposition
 * Returns:
 *      nothing
 */
function positionContentDiv(infoData)
{
    var section_div = document.getElementById(ID_SECTION);
    var page_content = document.getElementById(ID_PAGE_CONTENT);
    
    if ( (section_div) && (page_content) && (infoData) )
    {
        // clear out the div's parent before we reassign it
        if ( infoData.parentNode )
        {
            infoData.parentNode.removeChild(infoData);
        }
        
        // add the node to the section_div, right before our content div
        section_div.insertBefore(infoData, page_content);
    }
}

/*
 * rtrim:
 *      trims the trailing spaces from a string
 * Params:
 *      val - the string value to trim
 * Returns:
 *      a string with the trailing spaces removed
 */
function rtrim(val)
{
    return val.replace( /\s+$/g, VAL_EMPTY_STRING );
}

/*
 * setChildEnabledState:
 *      sets the enabled state of the control and all its children to the
 *      specified state
 * Params:
 *      control - the control to be modified
 *      disabled - true to disable the control state; otherwise false
 * Returns:
 *      false
 */
function setControlEnableState(control, disabled) {
    if ( control ) {
        if ( control.childNodes ) {
            for (var cnt=0; cnt<control.childNodes.length; cnt++) {
                setControlEnableState(control.childNodes[cnt], disabled);
            }
        }
        
        if ( (control.disabled) && (control.disabled != disabled) ) {
            contro.disabled = disabled;
        }
    }
    
    return false;
}

/*
 * systemDateToiRankDate:
 *      attempts to convert a date returned from either the DB or
 *      an iRANK date field to whatever format is being used on the
 *      system
 * Params:
 *      dateVal - a reference to a string that contains the date value
 *          to be converted
 * Returns:
 *      a system local specific date value or null if an error occurs
 *      during parsing
 */
function systemDateToCommonDate(dateVal) {
    var result = null;
    
    try {
        if ( (dateVal) && (0 < dateVal.length) ) {
            var idx = dateVal.indexOf('T');
            
            if ( -1 < idx ) {
                dateVal = dateVal.substring(0, idx);
            }
            
            dateVal = dateVal.replace(/-/g, '/');
            
            // parse the date
            var date_val = Date.parse(dateVal);
            
            // test to see if we got a number back - if we did, then this is
            // a valid date (or we can at least assume it is)
            if ( false == isNaN(date_val) ) {
                // now we have to perform some work to format the date into
                // a format the site can handle
                var parts = dateVal.split('/');
                
                if ( 3 == parts.length ) {
                    var year = null;
                    var month = null;
                    var day = null;
                    
                    // admittedly convuluted logic, but it fits based on values
                    // returned from both the DB and the system (which is probably
                    // regional)
                    for ( var cnt=0; cnt<parts.length; cnt++ ) {
                        if ( 4 == parts[cnt].length ) {
                            year = parts[cnt];
                        }
                        else if ( 0 == cnt ) {
                            if ( null == day ) {
                                day = parts[cnt];
                            }
                            else {
                                month = parts[cnt];
                            }
                        }
                        else if ( null == month ) {
                            month = parts[cnt];
                        }
                        else {
                            day = parts[cnt];
                        }
                    }
                    
                    // the next three checks are in case the logic in the loop
                    // fails and we forget to set something
                    if ( null == day ) {
                        day = parts[0];
                    }
                    
                    if ( null == month ) {
                        month = parts[1];
                    }
                    
                    if ( null == year ) {
                        year = parts[2];
                    }
                    
                    idx = month.indexOf('0');
                    
                    if ( 0 == idx ) {
                        // strip off the leading zero, if there is one
                        month = month.substring(1);
                    }
                    
                    // the month value is a 0 based index, so we have to subtract
                    // 1 from the current month value
                    month = parseInt(month);
                    month--;
                    
                    result = new Date();
                    result.setFullYear(year, month, day);
                }
            }
        }
    }
    catch (e) {
        // no-op ... if we're here, it's probably because the input
        // string isn't in a format the Date.parse() function can handle
    }
    
    return result;
}

/*
 * trim:
 *      trims leading and trailing spaces from a string
 * Params:
 *      val - the string value to trim
 * Returns:
 *      a string with the leading and trailing spaces removed
 */
function trim(val)
{
    val = ltrim(val);
    return rtrim(val);
}

/*
 * setTabColorStyle:
 *      this function is meant to be used for DIV elements that are used as
 *      containers to give the underlying control a "rounded edge" look.  The
 *      application doesn't use images to acheive this effect and instead
 *      relies on CSS.  This method is typically called when a control is "selected"
 *      and changes the color of control based on the CSS styles used to achieve
 *      the rounded edge effect.
 * Params:
 *      parentCtl - the parent element whose color is being modified
 *      currColorStyle - the current style that controls the color of the parent.
 *          This can be the element's only style or a style that exists as
 *          multiple CSS class definitions for the element.
 *      newColorStyle - a style that contains a definition for the new color style
 * Returns:
 *      true if the operation succeeded; otherwise false
 */
function setTabColorStyle(parentCtl, currColorStyle, newColorStyle)
{
    var result = true;
    
    try
    {
        // replace the color on the parent
        parentCtl.className = parentCtl.className.replace(currColorStyle, newColorStyle);
        
        var elms = parentCtl.getElementsByTagName(TYPE_B);

        // this application uses a very specific method of imbedding "<b>"
        // elements to achieve the rounded edge effect - it's this collection
        // of "b" elements that we're looking for
        if ( (elms) && (0 < elms.length) )
        {
            // loop through all the returned "<b>" elements and change their
            // color style
            for (var inner=0; inner<elms.length; inner++)
            {
                var b_elm = elms[inner];
                
                if ( (b_elm) && (b_elm.className) )
                {
                    b_elm.className = b_elm.className.replace(currColorStyle, newColorStyle);
                }
            }
        }
    }
    catch(e)
    {
        result = false;
    }
    
    return result;
}

/*
 * PageDefaultButton:
 *      Javascript class that allows for the re-targetting of the page
 *      default button (the one that is processed when the "enter" key
 *      is pressed).  The purpose of this class is to assign a new default
 *      button for a page when a specific control has the focus.  When
 *      the control loses focus, the default button for the page is reset
 *      to whatever it was before
 * Params:
 *      none
 * Returns:
 *      false ... always
 */
function PageDefaultButton() {
    var This = this;
    this.OrigDefault = document.onkeydown;
    this.NewDefault  = null;
    this.ProcessEsc  = null;
    
    /*
     * setDefaultButton:
     *      called to set a new default button for the page or reset the
     *      default button of the page to whatever it was when the page
     *      was loaded.  If newButton contains a valid control name, the
     *      default button will be set to that control; if newButton does
     *      not contain a valid control name, the page default button is
     *      reset to whatever the default button was when the page was
     *      first loaded
     * Params:
     *      newButton - the id of the button to be made the default button
     *          or null if the default button control is to be reset
     * Returns:
     *      false ... always
     */
    this.setDefaultButton = function(newButton) {
        This.NewDefault = document.getElementById(newButton);
        
        if ( null == This.NewDefault ) {
            document.onkeydown = This.OrigDefault;
        }
        else {
            document.onkeydown = function(e) {
                var result = true;
                
                if ( This.NewDefault ) {
                    e = e || window.event;
                    
                    var key_code = (e.keyCode) ? e.keyCode : e.which;
                    
                    // this function only captures the enter key ... all other
                    // input is ignored
                    if ( 13 == key_code ) {
                        e.cancelBubble = true;
                        result = false;
                        
                        if (e.preventDefault) e.preventDefault();
                        
                        This.NewDefault.click(e);
                    }
                    else if ( (27 == key_code) && (This.ProcessEsc) ) {
                        e.cancelBubble = true;
                        result = false;
            
                        if (e.preventDefault) e.preventDefault();
                        
                        This.ProcessEsc();
                    }
                }
                
                return result;
            }
        }
    }
    
    return false;
}

/*
 * removeChildNodes:
 *      recursively removes all child nodes from a parent element
 * Params:
 *      parentElm - the parent element to remove
 * Returns:
 *      false
 */
function removeChildNodes(parentElm) {
    if ( (parentElm) && (0 < parentElm.childNodes.length) ) {
        while ( parentElm.firstChild ) {
            if ( (parentElm.firstChild.childNodes) && (0 < parentElm.firstChild.childNodes.length) ) {
                removeChildNodes(parentElm.firstChild);
            }
            
            parentElm.removeChild(parentElm.firstChild);
        }
    }
    
    return false;
}

/*
 * setSelectedIndex:
 *      sets the selected index of the list to the option with
 *      the specified value
 * Params:
 *      list - the list to modify
 *      val - the value within the list to set as the default item
 * Returns:
 *      true if the list has an item with the specified value; otherwise
 *      false
 */
function setSelectedIndex(list, val)
{
    var result = false;
    
    if ( (list) && (val) )
    {
        for (var idx=0; idx<list.options.length; idx++)
        {
            if ( val == list.options[idx].value )
            {
                list.options.selectedIndex = idx;
                result = true;
                break;
            }
        }
    }
    
    return result;
}

/*
 * writeLog:
 *      if running with FireBug and running on local host, writes a message to the 
 *      console.  If none of these conditions are true, this method evals to a no-op
 * Params:
 *      msg - the message to write
 * Returns:
 *      true
 */
function writeLog(msg) {
    if ( (typeof(console) != VAL_UNDEFINED) && (null != console) && (console.log) && (true == IsDebugging) ) {
        console.log(msg);
    }
    
    return true;
}

/*
 * the following group of vars should be considered constant for the duration of
 * the page.  There's no current standard for the definition of the "const" reserved
 * keyword though, so they're declared as "var"
 */
var CLR_BLACK         = "#000000";
var CLR_GRAY          = "#AAAAAA";
var CLR_WHITE         = "white";
var CSS_BLUE          = "blueBack";
var CSS_GRAY          = "gray";
var CSS_HIDDEN        = "hidden";
var CSS_LEFT          = "left";
var CSS_LT_BLUE       = "ltBlue";
var CSS_LT_BLUE2      = "ltBlue2";
var CSS_RBLOCK_BOTTOM = "rblockbottom";
var CSS_RBLOCK_TOP    = "rblocktop";
var CSS_RIGHT         = "right";
var CSS_RND1          = "rnd1";
var CSS_RND2          = "rnd2";
var CSS_RND3          = "rnd3";
var CSS_RND4          = "rnd4";
var CTL_CATEGORY      = "ctl00_pageData_dropDown_Category";
var CTL_SRV_MESSAGE   = "serverMessage";
var CTL_SUBCATEGORY   = "dropDown_Subcategory";
var CTL_SUBCATHIDDEN  = "ctl00_pageData_hidden_Subcategory";
var CURSOR_DEFAULT    = "default";
var DEL_COLON         = ":";
var DEL_SEMICOLON     = ";";
var DT_FORMAT         = "dd/mm/yyyy";
var ELM_SUBCAT_RESULT = "GetListSubCategoryResult";
var ID_INFO_DATA      = "infoData";
var ID_PAGE_CONTENT   = "pageContent";
var ID_SECTION        = "section";
var ID_USER_FUNC      = "userFunctions";
var LINK_RANK_ITEMS   = "/RankItems.aspx?ListId=";
var ONE_DAY           = 1000*60*60*24;
var STR_PX            = "px";
var STYLE_CONTENT     = "pageContent";
var STYLE_HIDDEN      = "hidden";
var TYPE_A            = "a";
var TYPE_B            = "b";
var TYPE_BR           = "br";
var TYPE_DIV          = "div";
var TYPE_IMG          = "img";
var TYPE_INPUT        = "input";
var VAL_EMPTY_STRING  = "";
var VAL_MORE          = "...";
var VAL_TRUE          = "True";
var VAL_UNDEFINED     = "undefined";

// there are a couple of conditional Javascript include files that set this
// flag to false when the browser is IE 6 or less.  Conditional logic is
// executed based on the value of this flag
var IE7OrGreater = true;

// used to determine the coordiantes of the mouse
var MouseCoords = new MouseManager();

// used to capture and handle "enter" key events when specific
// controls have the focus.  This allows for keyboard navigation
// and input on specific sections of the form without always
// firing the page default button
var ButtonHandler = new PageDefaultButton();

// the "MaxFieldLengths" hashtable is used during the run-time of the page
// to ensure users don't input more than the maximum number of characters
// allowed by the DB field.  These fields are re-evaluated for length server
// side so the checks are more for user friendliness than security
var MaxFieldLengths = new Hashtable();

// used to turn on/off the google analytics based code when running on
// the development platform
var hostname = window.location.hostname;
var IsDebugging = ("localhost" == hostname);