/* ===================================================================================================
* WARNING – This file is part of the base implementation for WebMaker, so it should not be edited or changed for any project.
* These files are replaced if a project is re-imported to the WebMaker Studio or migrated to a new version of the product.
* For guidance on ‘How do I override or clone Hyfinity webapp files such as CSS & javascript?’, please read the following relevant FAQ entry:
* http://www.hyfinity.net/faq/index.php?solution_id=1113
==================================================================================================== */

/*
 * DisplayUitls.js
 *
 * Utility functions for altering the display of HTML componenets from javascript
 *
 * Company: Hyfinity Ltd
 * Copyright (c) 2003
 */


if (typeof(dojo) != 'undefined')
{
    dojo.require("dojo.string");
    dojo.require("dojo.parser");
    dojo.require("dojo.fx");
}


/**
 * Root object to contain all hyfinity functions.
 * This should remove the chance of a name conflict with other functionality.
 *
 */
var hyf =
{
    version: '2.0',
    pageLoaded: false
};

/**
 * Attaches the specified function to be called when the specified event occurs on the given object.
 * This removes the need to worry about browser complexities from the main code.
 * For now we delegate to dojo to handle this for us if possible.
 *
 * @param object The object to attach the event to
 * @param event a string specifiying the actual event that the fucntion should be called for eg 'onclick'
 * @param func The function that should be called when this event occurs.
 */
hyf.attachEventHandler = function(object, event, func)
{
    //use dojo to attach the event.
    if (typeof(dojo) != 'undefined')
    {
        if ((object == window) && (event == 'onload'))
            dojo.addOnLoad(func)
        else
            dojo.connect(object, event, func);
    }
    else if (typeof(obj.attachEvent) != 'undefined') //IE format
        object.attachEvent(event, func);
    else if (typeof(obj.addEventListener) != 'undefined') //W3C format
        object.addEventListener(event.substring(2), func, false);
};

hyf.attachEventHandler(window, 'onload', function() {hyf.pageLoaded = true;});


/**
 * Object containing all Hyfinity utility functions
 */
hyf.util = {};


/**
 * Generic function for toggling the display of a component.
 * @param targetComponent the HTML component (or its ID) to show/hide
 * @param method An optional string specifying the required visibility of the
 *               component ('show' or 'hide')
 */
hyf.util.toggleComponent = function(targetComponent, method, animate)
{
    var component
    if (typeof(targetComponent) == 'string')
        component = document.getElementById(targetComponent);
    else
        component = targetComponent;

    if ((component != null) && (typeof(component) != 'undefined'))
    {
        if ((method == null) || (typeof(method) == 'undefined'))
        {
            if (hyf.util.getCurrentStyle(component, 'display') == 'none')
            {
                hyf.util.showComponent(component, animate);
            }
            else
            {
                hyf.util.hideComponent(component, animate);
            }
        }
        else
        {
            if (method == 'hide')
            {
                hyf.util.hideComponent(component, animate);
            }
            else
            {
                hyf.util.showComponent(component, animate);
            }
        }
    }
}

/*generic utility function for hiding a specific component*/
hyf.util.hideComponent = function(component, animate)
{
    if (component == null)
        return;

    if ((animate == true) && (typeof(dojo) != 'undefined'))
    {
        dojo.fx.wipeOut({node: component, duration: 250}).play();
    }
    else
    {
        if ((typeof(component._oldCSSDisplay) == undefined) && (hyf.util.getCurrentStyle(component, 'display') != 'none'))
            component._oldCSSDisplay = hyf.util.getCurrentStyle(component, 'display');

        component.style.visibility = 'hidden';
        component.style.display = 'none';
    }
}
/*generic utility function for showing a particular component*/
hyf.util.showComponent = function(component, animate)
{
    if (component == null)
        return;

    //If the component is being hidden by a display setting in an external
    //css file then just clearing the inline display setting, or using the dojo
    //wipeIn command does not work correctly
    //Therefore we need to remove the class that is hiding it, and switch it to using
    //inline styles to hide.
    //This assumes use of one of the specific hide classes in the provided CSS file,
    //and may not work correclty if a different class name is being used.
    if ((hyf.util.getCurrentStyle(component, 'display') == 'none') && (typeof(dojo) != 'undefined'))
    {
        dojo.removeClass(component, 'hide');
        dojo.removeClass(component, 'hidden');
        component.style.display = 'none';
    }

    if ((animate == true) && (typeof(dojo) != 'undefined') && (typeof(dojo.fx) != 'undefined'))
    {
        dojo.fx.wipeIn({node: component, duration: 250}).play();
    }
    else
    {
        component.style.visibility = 'visible';
        if (typeof(component._oldCSSDisplay) != 'undefined')
            component.style.display= component._oldCSSDisplay;
        else
            component.style.display = '';
    }
}

/* utility function for disabling a component
 * may want to extend this so children components are disabled as well, ie if a div component is passed in*/
hyf.util.disableComponent = function(component)
{
    if (component == null)
        return;

    component.disabled=true;
}

/* utility function for enabling a component*/
hyf.util.enableComponent = function(component)
{
    if (component == null)
        return;

    component.disabled=false;
}

/**
 * Returns the current value for the given style property on the given object
 */
hyf.util.getCurrentStyle = function(object, property)
{
    var currentDisplay;
    if (window.getComputedStyle)
        currentDisplay = document.defaultView.getComputedStyle(object,null).getPropertyValue(property);
    else if (object.currentStyle)
        currentDisplay = object.currentStyle[property];
    return currentDisplay;
}

/** Stores the space in px to allow for the browser scrollbar. */
hyf.util.scrollbarWidth = 20;

/**
 * Restricts the vertical size of the specified table so that only the given number of rows are displayed
 * at one time, and adds scrollbars to see all the data.
 * @param row The DOM object for a row in the table
 * @param container The DOM object for the DIV containing the table (and nothing else)
 * @param body The DOM object representing the TBODY elemnt of the table.
 * @param headings The DOM object representing the heading row (TR) of the table.
 * @param numRows An integer value indicating the numebr of rows to display.
 */
hyf.util.setTableScrolling = function(row, container, body, headings, numRows)
{
    if ((row == null) || (container == null) || (body == null) || (headings == null))
    {
        //required components of the table are not present, so return
        return;
    }
    var rowHeight = hyf.util.getComputedHeight(row);
    var headingsHeight = hyf.util.getComputedHeight(headings);

    if (dojo.isIE < 8) //ie versions upto 7 support CSS expressions which makes this quite easy
    {
        container.style.overflow = 'hidden';
        container.style.overflowY = 'auto';
        var newHeight = ((rowHeight * numRows) + headingsHeight);
        if (newHeight < hyf.util.getComputedHeight(container))
        {
            var table = body.parentNode;
            table.style.marginTop = 0;
            container.style.height = newHeight + 'px';
            headings.style.setExpression('top', "document.getElementById('" + container.getAttribute('id') +"').scrollTop - 2");
            headings.style.position = 'relative';
            headings.style.left = 0;

            //make sure the scrollbar appaears next to the table (if not full width)
            if ((hyf.util.getComputedWidth(headings) + hyf.util.scrollbarWidth) < hyf.util.getViewportSize().width)
                container.style.width = hyf.util.getComputedWidth(headings) + hyf.util.scrollbarWidth;
            else
                container.style.width = hyf.util.getComputedWidth(headings);
        }
    }
    else if (dojo.isFF < 4) //firefox before version 4 supported scrollable tbody which makes this easy
    {
        var newHeight = (rowHeight * numRows);
        if (newHeight < hyf.util.getComputedHeight(body))
        {
            body.style.overflow = 'auto';
            body.style.overflowX = 'hidden';
            body.style.height = newHeight + 'px';
        }
    }
    else/* if (dojo.isWebKit || (dojo.isFF >= 4) || (dojo.isIE >= 8))*/
    {
        var newHeight = (rowHeight * numRows);
        if (newHeight < hyf.util.getComputedHeight(body))
        {
            //try and insert a new scrollable container, and position the heding row outside this
            var newContainer = document.createElement('div');
            //move existing container children into new container
            while (container.hasChildNodes())
            {
                newContainer.appendChild(container.firstChild);
            }

            container.appendChild(newContainer);

            container.style.position = 'relative';
            container.style.paddingTop = headingsHeight + 'px';
            newContainer.style.overflow = 'scroll';
            newContainer.style.overflowX = 'hidden';
            newContainer.style.height = newHeight + 'px';

            //set a specific width on each heading, as once it is moved outside, each one
            //will lose the connection to the table data
            if (dojo.isIE == 8)
            {
                for (var i = 0; i < headings.cells.length; i++)
                {
                    var headerCell = headings.cells.item(i);
                    var width = hyf.util.getComputedWidth(headerCell);

                    var dataCell = body.rows.item(0).cells.item(i);

                    headerCell.style.width = width;
                    dataCell.style.width = width;
                    headerCell.style.paddingLeft = dataCell.style.paddingLeft;
                    headerCell.style.paddingRight = dataCell.style.paddingRight;
                }
            }
            else
            {
                dojo.query('th', headings).forEach("item.style.width = hyf.util.getComputedWidth(item) + 'px';");
                dojo.query('td', row).forEach("item.style.width = hyf.util.getComputedWidth(item) + 'px';");
            }

            headings.style.borderSpacing = '0px';
            headings.style.position = 'absolute';
            headings.style.top = '0px';

            var headingWidth = hyf.util.getComputedWidth(headings);
            var table = body.parentNode;
            table.style.width = headingWidth;

            //make sure the scrollbar appaears next to the table (if not full width)
            if ((headingWidth + hyf.util.scrollbarWidth) < hyf.util.getViewportSize().width)
                container.style.width = (headingWidth + hyf.util.scrollbarWidth) + 'px';
        }
    }
    /*else
    {
        //fallback is to just scroll the container, and not keep the table headings fixed
        container.style.overflow = 'scroll';
        container.style.overflowX = 'auto';
        body.style.overflowX = 'hidden';
        var newHeight = (rowHeight * numRows) + headingsHeight;
        if (newHeight < hyf.util.getComputedHeight(container))
        {
            container.style.height = newHeight + 'px';
            container.style.width = (hyf.util.getComputedWidth(headings) + 20) + 'px';
        }
    }*/
}

/**
 * Returns the current height of the given source component
 * @param source The DOM component to return the current computed height for
 * @return a float containing the current height value (pixels)
 */
hyf.util.getComputedHeight = function(source)
{
    var compHeight
    if (dojo.isIE)
    {
        compHeight = source.offsetHeight;
    }
    else
    {
        compHeight = hyf.util.getCurrentStyle(source,"height");
    }
    return parseFloat(compHeight);
}

/**
 * Returns the current width of the given source component
 * @param source The DOM component to return the current computed width for
 * @return a float containing the current width value (pixels)
 */
hyf.util.getComputedWidth = function(source)
{
    var compWidth
    if (dojo.isIE < 9)
    {
        compWidth = source.offsetWidth;
    }
    else
    {
        compWidth = hyf.util.getCurrentStyle(source,"width");
    }
    //special handling for opera which seems to return 0 from getCurrentStyle for the width for some reason.
    if ((parseFloat(compWidth) == 0) && (source.offsetWidth) && (source.offsetWidth > 0))
        compWidth = source.offsetWidth;

    return parseFloat(compWidth);
}


/**
 * Returns an object containing the coordinates of the mouse from the given event.
 * The returned object will have two properties, 'x' and 'y'
 */
hyf.util.getMouseCoords = function(e, base)
{
    var coords= new Object();

    var evt
    if (!e)
    {
        evt = window.event;
    }
    else //netscape
    {
        evt = e;
    }

    //get the mouse coords
    if (evt.pageX || evt.pageY)
    {
        //Gecko based
        coords.x = evt.pageX;
        coords.y = evt.pageY;
    }
    else if (evt.clientX || evt.clientY)
    {
        coords.x = evt.clientX
        coords.y = evt.clientY
        if ((document.body) && (document.body.scrollLeft || document.body.scrollTop))
        {
            //IE 4, 5, and 6 (Non standards compliant mode)
            coords.x += document.body.scrollLeft;
            coords.y += document.body.scrollTop;
        }
        else if ((document.documentElement) &&
                 ((document.documentElement.scrollLeft) ||
                 (document.documentElement.scrollTop)))
        {
            //IE 6 (Standards compliant mode)
            coords.x += document.documentElement.scrollLeft;
            coords.y += document.documentElement.scrollTop;
        }
    }

    if ((typeof(base) != 'undefined') && (typeof(base.x) != 'undefined') && (typeof(base.y) != 'undefined'))
    {
        coords.x += base.x;
        coords.y += base.y;
    }

    return coords;

}

/**
 * Returns the location of the left of the given object.
 * If the screen parameter is set to true, then the returned value will be in screen coords.
 * ie it adjusts for scrollable regions, to allow comparison with mouse coordinates
 * returned from getMouseCoords method
 */
hyf.util.getLeftPosition = function(obj, screen)
{
    var ol=obj.offsetLeft;
    while ((obj=obj.offsetParent) != null)
    {
        ol += obj.offsetLeft;
        if (screen)
        {
            if (obj.scrollLeft != 0)
            {
                ot -= obj.scrollLeft;
            }
        }
    }
    return ol;
}

/**
 * Returns the location of the right of the given object.
 * If the screen parameter is set to true, then the returned value will be in screen coords.
 * ie it adjusts for scrollable regions, to allow comparison with mouse coordinates
 * returned from getMouseCoords method
 */
hyf.util.getRightPosition = function(obj, screen)
{
    return hyf.util.getLeftPosition(obj, screen) + hyf.util.getComputedWidth(obj);
}

/**
 * Returns the location of the top of the given object.
 * If the screen parameter is set to true, then the returned value will be in screen coords.
 * ie it adjusts for scrollable regions, to allow comparison with mouse coordinates
 * returned from getMouseCoords method
 */
hyf.util.getTopPosition = function(obj, screen)
{
    var ot=obj.offsetTop;
    while ((obj=obj.offsetParent) != null)
    {
        ot += obj.offsetTop;
        if (screen)
        {
            if (obj.scrollTop != 0)
            {
                ot -= obj.scrollTop;
            }
        }
    }
    return ot;
}

/**
 * Returns the current location corrdinates of the given object.
 * The returned object will have four properties, 'x' and 'y' which give
 * the position of the top left corner, and 'width' and 'height'
 * If the screen parameter is set to true, the returned values are in 'screen coords', ie they are
 * adjusted for scrollable regions, to allow comparison with the mouse corrdinates returned from
 * the getMouseCoords method.
 */
hyf.util.getComponentPosition = function(obj, screen)
{
    var objCoords = new Object();
    objCoords.x = hyf.util.getLeftPosition(obj, screen);
    objCoords.y = hyf.util.getTopPosition(obj, screen);
    objCoords.width = hyf.util.getComputedWidth(obj);
    objCoords.height = hyf.util.getComputedHeight(obj);
    return objCoords;
}

/**
 * Returns the size of the available window viewport
 * @return an object containing two properties, width and height.
 */
hyf.util.getViewportSize = function()
{
    var size = new Object();
    if (window.innerWidth)
    {
        size.width = window.innerWidth;
        size.height = window.innerHeight;
    }
    else if (document.documentElement && document.documentElement.clientWidth)
    {
        size.width = document.documentElement.clientWidth;
        size.height = document.documentElement.clientHeight;
    }
    else if (document.body && document.body.clientWidth)
    {
        size.width = document.body.clientWidth;
        size.height = document.body.clientHeight;
    }
    return size;
}

/**
 * Returns the closest previous sibling to the given element
 * that is an element node. ie text nodes are ignored
 */
hyf.util.getPreviousElementSibling = function(elem)
{
    var prev = elem.previousSibling;
    //make sure we return an element type node, not a text node
    while (prev && prev.nodeType != 1)
    {
        prev = prev.previousSibling;
    }

    return prev;
}

/**
 * Returns the closest folowing sibling to the given element
 * that is an element node. ie text nodes are ignored
 */
hyf.util.getNextElementSibling = function(elem)
{
    var prev = elem.nextSibling;
    //make sure we return an element type node, not a text node
    while (prev && prev.nodeType != 1)
    {
        prev = prev.nextSibling;
    }

    return prev;
}


/**
 * Returns a boolean value indicating whether or not the given field is currently hidden
 * @param field The HTML field to check.
 * @return true if the given field is within a hidden (display:none) component, false otherwise
 */
hyf.util.checkFieldHidden = function(field)
{
    if(hyf.util.findFieldHiddenParent(field) != null)
        return true;
    else
        return false;

}

/**
 * Searches the parents of the given field, and looks for any hidden (display:none) components.
 * This then returns the first hidden component found, or null if the field is not hidden.
 */
hyf.util.findFieldHiddenParent = function(field)
{
    if (field.nodeType == 9)  //have reached the document node
    {
        return null
    }

    var disp = hyf.util.getCurrentStyle(field,"display");

    if (disp == 'none')
    {
        return field
    }
    else if ((typeof(field.parentNode) != 'undefined') && (field.parentNode != null))
    {
        return hyf.util.findFieldHiddenParent(field.parentNode);
    }
    else
    {
        return null;
    }
}

/**
 * Searches through the parents of the given field to determine whether it is contained
 * in a tab pane.  If so, this returns the HTML component representing the tab group.
 * Otherwise this returns null.
 */
hyf.util.findFieldTabParent = function(field)
{
    if (field.nodeType == 9)  //have reached the document node
    {
        return null
    }

    if (field.getAttribute('_use') == 'tabPane')
    {
        return field
    }
    else if ((typeof(field.parentNode) != 'undefined') && (field.parentNode != null))
    {
        return hyf.util.findFieldTabParent(field.parentNode);
    }
    else
    {
        return null;
    }
}


/**
 * Checks to see if the provided parent component does actually contain the
 * child element within it a some level.
 * If both params are the same, then this will return true.
 * @param child The child element to check.
 * @param parent The HTML component to check if ti contains child.
 * @return boolean indicating whether or not parent contains child.
 */
hyf.util.isParent = function(child, parent)
{
    if (child.nodeType == 9)  //have reached the document node
    {
        return false
    }
    if (child == parent)
    {
        return true
    }
    else if ((typeof(child.parentNode) != 'undefined') && (child.parentNode != null))
    {
        return hyf.util.isParent(child.parentNode, parent);
    }
    else
    {
        return false;
    }
}



/**
 * Function to insert HTML content into the specified target container.
 * This also provides the option to evaluate any scripts present in the content
 *
 * @param target The HTML container into which the content will be inserted.
 *               Any existing content in this container will be lost.
 * @param content A string containing the HTML fragment to insert.
 * @param evalScripts an optional boolean flag to indicate whether scripts in the content should be evaluated
 *                  These will be evalauted only if true.
 * @private
 */
hyf.util.insertContent = function(target, content, evalScripts)
{

    scriptRegExp = '(?:<script.*?>)((\n|\r|.)*?)(?:<\/script>)';

    //seperate out any script tags from the content passed in.
    var match = new RegExp(scriptRegExp, 'img');
    var pureHTML = content.replace(match, '');
    var scripts  = content.match(match);

    //attempt to destory any existing widgets in the container
    hyf.util.destroyDojoWidgets(target);

    //insert the HTML into the targte location
    target.innerHTML = pureHTML;

    //check if the script fragments that we have stripped out need to be evaluated
    if (evalScripts && scripts != null)
    {
        match = new RegExp(scriptRegExp, 'im');
        setTimeout(function() {
            var scriptsToEvalLater = new Array();
            //long variable name to try and avoid conflicts with the scripts being executed.
            for (var script_counter_unique_name = 0; script_counter_unique_name < scripts.length; script_counter_unique_name++)
            {
                try
                {
                    //use the regex again (without the global flag) to remove the actual surrounding script tags
                    var scriptFragment = scripts[script_counter_unique_name].match(match)[1];
                    //check to see if the fragment contains a function definition which needs to
                    //be properly registered with this window
                    var functionStart = scriptFragment.indexOf('function ');
                    while(functionStart != -1)
                    {
                        var functionEnd = scriptFragment.indexOf('(', functionStart + 9);
                        var functionName = scriptFragment.substring(functionStart + 9, functionEnd);
                        if (functionName != '')
                            scriptFragment += '; window.' + functionName + ' = ' + functionName + ';';
                        functionStart = scriptFragment.indexOf('function ', functionEnd);
                    }

                    //check if the script fragemtn contains any calls to find the last table output,
                    //and if so try and replace this with the actual ID of the table so that this call
                    //will still work correctly.
                    var gltIndex = scriptFragment.indexOf('getLastTableOutput');
                    if (gltIndex != -1)
                    {
                        //find the location of this script fragment in the original HTML
                        var loc = content.indexOf(scripts[script_counter_unique_name]);
                        //find the id of the closest table before this point
                        var tableStart = content.lastIndexOf('<table', loc);
                        if (tableStart != -1)
                        {
                            var idStart = content.indexOf('id=', tableStart);
                            if (idStart != -1)
                            {
                                var idQuote = content.charAt(idStart + 3);
                                var idEnd = content.indexOf(idQuote, idStart + 4);
                                var idString = content.substring(idStart + 4, idEnd);
                                if (idString != '')
                                {
                                    //work out which part of the script fragment we need to replace
                                    var replaceStart = scriptFragment.lastIndexOf('hyf', gltIndex);
                                    var replaceEnd = scriptFragment.indexOf(')', gltIndex);
                                    //replace the section of the script fragment
                                    scriptFragment = scriptFragment.substring(0, replaceStart) +
                                                     "document.getElementById('" + idString + "')" +
                                                     scriptFragment.substring(replaceEnd + 1);
                                    //set this fragment to be evaluated later, as we want to make sure
                                    //that any custom attributes have been properly set first.
                                    scriptsToEvalLater.push(scriptFragment);
                                    continue;
                                }
                            }
                        }
                    }

                    eval(scriptFragment);
                }
                catch(e)
                {
                    //ignore
                }
            }
            //now evalaute any fragments that have been left to the end.
            for (var script_counter_unique_name = 0; script_counter_unique_name < scriptsToEvalLater.length; script_counter_unique_name++)
            {
                try
                {
                    eval(scriptsToEvalLater[script_counter_unique_name]);
                }
                catch (e) { } //ignore errors
            }

            //check for any dojo widgets in the inserted HTML
            if ((typeof(dojo) != 'undefined') && (typeof(dojo.parser) != 'undefined'))
                dojo.parser.parse(target);
                hyf.validation.initiateWidgets(target);
        }, 10);
    }
}


/**
 * Creates an object containing the contents of all the input fields within the specified container
 *
 * @param cont The container DOM object to process.
 * @param actionName (optional) The name of the action being called.  If present, then the associated instance
 *                              field will be included in the returned object.
 * @return The created associative array mapping param names to values for submitting in a dojo AJAX request.
 *
 * @private
 */
hyf.util.encodeContainer = function(cont, actionName)
{
    if (cont == null || typeof(cont) == 'undefined')
        return;

    var data = new Object();

    var inputs = cont.getElementsByTagName('input');

    for (var i = 0; i < inputs.length; ++i)
    {
        //look at the type of input
        switch (inputs[i].type)
        {
            case 'file'     :   //cant handle this via ajax so just ignore
                                break;
            case 'radio'    :
            case 'checkbox' :   if (inputs[i].checked)
                                {
                                    data[inputs[i].name] = inputs[i].value;
                                }
                                break;
            //QUESTION: How should we handle these types?  Should they be submitted?
            case 'image'    :   break;
            case 'button'   :   break;
            case 'reset'    :   break;
            case 'submit'   :   break;
            default         :   data[inputs[i].name] = inputs[i].value;
        }
    }

    inputs = cont.getElementsByTagName('textarea');
    for (var i = 0; i < inputs.length; ++i)
    {
        data[inputs[i].name] = inputs[i].value;
    }

    inputs = cont.getElementsByTagName('select');
    for (var i = 0; i < inputs.length; ++i)
    {
        if (inputs[i].type == 'select-multiple')
        {
            for (var j = 0; j < inputs[i].options.length; ++j)
            {
                if (inputs[i].options[j].selected)
                {
                    data[inputs[i].name] = inputs[i].options[j].value;
                }
            }
        }
        else
        {
            data[inputs[i].name] = inputs[i].value;
        }
    }

    //need to add the language value as this wont be included in the encoded group
    for (var i = 0; i < document.forms.length; ++i)
    {
        if (typeof(document.forms[i].Language) != 'undefined')
        {
            data['Language'] = document.forms[0].Language.value;
            break;
        }
    }

    //add the appropriate xform instance field if the action value has been provided
    if (typeof(actionName) != 'null')
    {
        //First check for the simple case where there is an instance field for this specific action.
        var xif = document.getElementById('xgf_xform_instance_xga_' + actionName);
        if (xif != null)
            data[xif.name] = xif.value;
        else
        {
            //if not then loop through all the fields to see if we have an instance structure to use
            var possInstances = document.getElementsByTagName('input');

            var possInstance = null;

            for (var i = 0; i < possInstances.length; ++i)
            {
                if (possInstances[i].type == 'hidden')
                {
                    if ((possInstances[i].name == 'xform_instance') && (possInstance == null))
                        possInstance = possInstances[i]
                    else if (possInstances[i].name.indexOf('xgf_xform_instance') == 0)
                    {
                        if ((possInstances[i].name + '_').indexOf('_xga_' + actionName + '_') != -1)
                        {
                            possInstance = possInstances[i];
                            break;
                        }
                    }
                }
            }
            if (possInstance != null)
                data[possInstance.name] = possInstance.value;
        }
    }

    return data;
};


/**
 * Resets all the form controls in the given container.
 * Depending on the provided mode value, this will either 'reset' their values to those shown when the
 * page was first displayed, or 'clear' the values completely.
 * @param container The HTML container whose form controls should be processed.
 * @param mode (optional) Indicates which type of reset should apply. Either 'reset' (default) or 'clear'.
 */
hyf.util.resetContainer = function(container, mode)
{
    if ((container == null) || (typeof(container) == 'undefined'))
        return;
    if ((typeof(mode) == 'undefined') || ((mode != 'reset') && (mode != 'clear')))
       mode = 'reset';

    //handle the special case of the form node being passed in, which provides a reset method.
    if ((typeof(container.reset) == 'function') && (mode == 'reset'))
        container.reset()
    else
    {
        var controls = new Array();
        var inputs = container.getElementsByTagName('input');
        for (var i = 0; i < inputs.length; ++i)
            controls[controls.length] = inputs[i];
        inputs = container.getElementsByTagName('select');
        for (var i = 0; i < inputs.length; ++i)
            controls[controls.length] = inputs[i];
        inputs = container.getElementsByTagName('textarea');
        for (var i = 0; i < inputs.length; ++i)
            controls[controls.length] = inputs[i];

        for (var i = 0; i < controls.length; ++i)
        {
            //look at the type of input
            if (mode == 'clear')
            {
                switch (controls[i].type)
                {
                    //QUESTION: How should we handle these types?
                    case 'image'    :   break;
                    case 'button'   :   break;
                    case 'reset'    :   break;
                    case 'submit'   :   break;
                    case 'file'     :   break;
                    case 'select-one':  //want select one control to default to the first value?
                                        controls[i].selectedIndex = 0;
                                        break;
                    case 'select-multiple': controls[i].selectedIndex = -1;
                                            break;
                    case 'radio'    :
                    case 'checkbox' :   controls[i].checked = false;
                                        break;
                    default         :   controls[i].value = '';
                }
            }
            else
            {
                switch (controls[i].type)
                {
                    case 'image'    :   break;
                    case 'button'   :   break;
                    case 'reset'    :   break;
                    case 'submit'   :   break;
                    case 'file'     :   break;
                    case 'select-one':
                    case 'select-multiple': for (var op = 0; op < controls[i].options.length; ++op)
                                            {
                                                controls[i].options[op].selected = controls[i].options[op].defaultSelected;
                                            }
                                            break;
                    case 'radio'    :
                    case 'checkbox' :   controls[i].checked = controls[i].defaultChecked;
                                        break;
                    default         :   controls[i].value = controls[i].defaultValue;
                }
            }
        }
    }
}

/**
 * Attempts to destory any dojo widgets that are present in the specified container
 * @param container The HTML container to destroy dojo widgets within. If not provided then the
 *              document body will be used instead, to destory any widgets on the page.
 */
hyf.util.destroyDojoWidgets = function(container)
{
    if (typeof(container) == 'undefined')
        container = document.body;

    if ((typeof(dijit) != 'undefined') && (typeof(dijit.findWidgets) == 'function'))
    {
        var widgets = dijit.findWidgets(container);
        for (var i = 0; i < widgets.length; ++i)
        {
            var widget = widgets[i];
            widget.destroyRecursive();
            delete widget;
        }
    }
}



/**
 * The hyf.textarea namespace contains all functionality relating to textarea field controls.
 */
hyf.textarea =
{
    desc : "Handles functionality relating to textareas, eg auto expanding fields.",
    offsetBuffer : (dojo.isIE) ? 2 : ((dojo.isOpera) ? 4 : 0)
};

/**
 * Handles automatically updating the size of the supplied textarea to support the current content.
 *
 * @param evt The (keypress) event object that triggered the call.
 * @param field The textarea object to adjust
 * @param min The minim number of rows to show in the textarea.
 * @param max The maximum number of rows to expand the textarea to.
 */
hyf.textarea.adjustHeight = function(evt, field, min, max)
{
    if ((evt != null) && (typeof(evt) != 'undefined'))
        var charCode = (evt.which) ? evt.which : event.keyCode;

    //ignore the arrow keys and shift/ctrl key
    if ((charCode >= 37 && charCode <= 40) || (charCode == 16) || (charCode == 17) || (evt && evt.ctrlKey))
    {
        return;
    }

    if ((max == null) || typeof(max) == 'undefined')
        max = Number.MAX_VALUE;
    if ((min == null) || typeof(min) == 'undefined')
        min = 1;


    //if the delete or backspace keys have been pressed then check whether to remove a row
    if ((field.rows > min) && ((charCode == 8) || (charCode == 46)))
    {
        if (dojo.isIE)  //does this work for other browsers???
        {
            var needToAddBack = false;
            while ((field.rows >= min ) && (!hyf.textarea.hasScroll(field)))
            {
                field.rows--;
                needToAddBack = true;
            }
            if (needToAddBack)
                field.rows++;
        }
        else
        {
            //find a guess of the number of rows of text
            var numRows = hyf.textarea.countNumTextRows(field);

            //set based on this
            field.rows = (numRows < min) ? min : ((numRows <= max) ? numRows : max);
        }
    }
    //otherwise look at adding a row
    else if ((field.rows < max ) && (hyf.textarea.hasScroll(field)))
    {
        if (dojo.isIE || dojo.isMozilla)
        {
            while ((field.rows < max ) && (hyf.textarea.hasScroll(field)))
            {
                field.rows++;
            }
        }
        else
        {
            //find a guess of the number of rows of text
            var numRows = hyf.textarea.countNumTextRows(field);

            //set based on this
            field.rows = (numRows < min) ? min : ((numRows <= max) ? numRows : max);
        }
    }
};

/**
 * Tries to determine whether the given textarea field currently has a scroll bar or not.
 * @param field The textarea object.
 * @return true or false depending on whether a scrollbar is currently required.
 *
 * @private
 */
hyf.textarea.hasScroll = function(field)
{
    //attempt to scroll to the bottom
    field.scrollTop = field.scrollHeight;
    //check if the field has been scrolled at all
    if (field.scrollTop != 0)
    {
        //field.value = content;
        return true;
    }
    else
    {
        //field.value = content;
        return false;
    }
}


/**
 * Returns an estimate of the number of rows used by the text content in the
 * provided textarea.
 * @param field The textarea object.
 *
 * @private
 */
hyf.textarea.countNumTextRows = function(field)
{
    //count number of line breaks
    var re = /\r\n|\r|\n/g;
    var numBreaks = 0;
    var lineStart = 0;
    while (re.exec(field.value))
    {
        numBreaks++;
        //for each line of text between physical breaks, try to work out how many lines it wraps to
        var newLineStart = re.lastIndex;
        var lineString = field.value.substring(lineStart, newLineStart);
        if (lineString.length > field.cols)
            numBreaks += Math.ceil(lineString.length / field.cols);

        lineStart = newLineStart;
    }

    var lineString = field.value.substring(lineStart);
    if (lineString.length == 0)
        return Number(numBreaks) + Number(1);
    else
        return Math.ceil(lineString.length / field.cols) + Number(numBreaks);

};




/**
 * Namespace for all the calendar related fucntionality
 * The configuration options for each calendar should be populated by script fragments in the actual page.
 */
hyf.calendar =
{
    config: {},  //associative array of field ids to their configuration options.
    activeFieldId: null,
    activeFieldRepeatId: null
};

/**
 * Gets the calendar object for the field with the given ID
 * If this object has laready been created it is just returned,
 * otherwise a new calendar object will be created using the configuration options for this field
 * @param fieldId The ID of the field to get the calendar for.  There must be an entry in the
 *                  hyf.calendar.config array for this field
 * @return The CalendarPopup object, or null if one couldn't be created.
 */
hyf.calendar.getCalendar = function(fieldId)
{
    if (typeof(hyf.calendar.config[fieldId]) == 'undefined')
    {
        return null;
    }
    else if (typeof(hyf.calendar.config[fieldId].calendar) != 'undefined')
    {
        return hyf.calendar.config[fieldId].calendar;
    }
    else
    {
        var options = hyf.calendar.config[fieldId];
        var cal;
        if (options.popup == false)
            cal = new CalendarPopup('calendarDiv');
        else
            cal = new CalendarPopup();

        switch (options.type)
        {
            case 'year_select'  :   cal.showYearNavigation();
                                    break;
            case 'year_entry'   :   cal.showYearNavigation();
                                    cal.showYearNavigationInput();
                                    break;
            case 'navigation_dropdowns' :   cal.showNavigationDropdowns();
                                    cal.setYearSelectStartOffset(5);
                                    break;
        }

        if (typeof(options.minExclusive) != 'undefined')
        {
            cal.addDisabledDates(null, convertDate(options.minExclusive, options.dataFormat, 'y-M-d'));
        }
        if (typeof(options.maxExclusive) != 'undefined')
        {
            cal.addDisabledDates(convertDate(options.maxExclusive, options.dataFormat, 'y-M-d'), null);
        }

        cal.setReturnFunction("hyf.calendar.handleCalendarSelection");

        hyf.calendar.config[fieldId].calendar = cal;
        return hyf.calendar.config[fieldId].calendar;
    }

}

/**
 * Shows the calendar popup for the given field ID.
 * @param fieldId The ID of the field to show the calendar for.
 */
hyf.calendar.showCalendar = function(fieldId, repeatId)
{
    if (typeof(repeatId) == 'undefined')
        repeatId = '';

    var cal = hyf.calendar.getCalendar(fieldId)
    if ((cal != null) && (typeof(cal) != 'undefined'))
    {
        if (hyf.calendar.config[fieldId].hasDropDowns)
        {
            var format = hyf.validation.DateValidator.getConcatDateFieldParts(repeatId + fieldId, "format", true);
            var value = hyf.validation.DateValidator.getConcatDateFieldParts(repeatId + fieldId, "values", true);
            var preset = '';
            if ((format != '') && (value != ''))
            {
                preset = convertDate(value, format, 'y-M-d');
            }
            cal.showCalendar(repeatId + fieldId + '_calendar_anchor', preset);
        }
        else
        {
            var field = document.getElementById(repeatId + fieldId);
            var preset = ''
            if (field.value != '')
            {
                var format = field.getAttribute('_display_date_format');
                if (format == null)
                    format = field.getAttribute('_data_date_format');
                preset = convertDate(field.value, format, 'y-M-d');
            }
            cal.showCalendar(repeatId + fieldId + '_calendar_anchor', preset);
        }
    }
    hyf.calendar.activeFieldId = fieldId;
    hyf.calendar.activeFieldRepeatId = repeatId;
}

/**
 * Handles selection of a value from the active calendar popup.
 * This stores the value in the relevant controls on screen.
 * @param y The year value of the selected date
 * @param m The month value of the selected date
 * @param d The day value of the selected date
 */
hyf.calendar.handleCalendarSelection = function(y, m, d)
{
    if (hyf.calendar.config[hyf.calendar.activeFieldId].hasDropDowns)
    {
        hyf.calendar.setDropdownDateValuesGeneric(y, m, d, hyf.calendar.activeFieldRepeatId + hyf.calendar.activeFieldId);
    }
    else
    {
        var field = document.getElementById(hyf.calendar.activeFieldRepeatId + hyf.calendar.activeFieldId);
        var format = field.getAttribute('_display_date_format');
        if (format == null)
            format = field.getAttribute('_data_date_format');

        field.value = formatDate(new Date(y,m-1,d,0,0,0), format);

        if (typeof(field.onchange) == 'function')
            field.onchange();
    }
}


/*
 * Function that is used when there is a dropdown date format used in conjunction with a pop-up calendar script
 */
hyf.calendar.setDropdownDateValuesGeneric = function(y, m, d, id)
{
    missCounter = 0;
    partCounter = 1;
    while(missCounter<2)
    {
        var currentField = document.getElementById(id+"_datefield_part_"+partCounter);
        if(currentField == null)
        {
            missCounter++;
        }
        else
        {
            var updated = false;
            if (currentField.type == "select-one")
            {
                if  ((currentField.getAttribute('_display_date_format') == "M") ||
                     (currentField.getAttribute('_display_date_format') == "MM") ||
                     (currentField.getAttribute('_display_date_format') == "MMM") ||
                     (currentField.getAttribute('_display_date_format') == "MMMM"))
                {
                    currentField.selectedIndex = m;
                    updated = true;
                }
                else if ((currentField.getAttribute('_display_date_format') == "d") ||
                         (currentField.getAttribute('_display_date_format') == "dd"))
                {
                    currentField.selectedIndex = d;
                    updated = true;
                }
            }

            if (!updated)
            {
                convertedValue = convertDate(y+"-"+m+"-"+d,"y-M-d",currentField.getAttribute('_display_date_format'));
                currentField.value=convertedValue;
            }

            if (typeof(currentField.onchange) == 'function')
                currentField.onchange();

            missCounter = 0;
        }
        partCounter++;
    }
}

/**
 * This function allows date constraints to be set for a Date Field. This can include resticting the valid date range.
 * @param dateField Set to the Date field to be constrained.
 * @param restrictionType Set to 'Minimum' or 'Maximum' value. Default is 'Minimum'.
 * @param restrictionValue Set to 'Today', or a another Date Fieldname, a data date value. Default is 'Today'.
 */
hyf.calendar.setDateConstraint = function(dateField,restrictionType,restrictionValue)
{
    var calendarConfig = hyf.calendar.config[dateField];
    var restrictionDateValue = null;
    if ((restrictionType == '') || (typeof(restrictionType) == 'undefined'))
    {
        restrictionType = 'Minimum';
    }
    if ((restrictionValue == '') || (typeof(restrictionValue) == 'undefined'))
    {
        restrictionValue = 'Today';
    }

    if (restrictionValue == 'Today')
    {
        if (restrictionType == 'Minimum')
        {
            restrictionDateValue = formatDate(new Date( new Date().getTime() - 86400000 ), calendarConfig.dataFormat);
        }
        if (restrictionType == 'Maximum')
        {
            restrictionDateValue = formatDate(new Date( new Date().getTime() + 86400000 ), calendarConfig.dataFormat);
        }
    }
    else
    {
       var restrictionDate = document.getElementById(restrictionValue);
       if (restrictionDate != null)
       {
           restrictionDateValue = hyf.validation.ValueConverter.performDateConversion(restrictionDate, restrictionDate.value);
       }
       else
       {
           restrictionDateValue = restrictionValue;
       }
    }

    if (restrictionDateValue != null)
    {
        if (restrictionType == 'Minimum')
        {
            document.getElementById(dateField).setAttribute('_minExclusive', restrictionDateValue );
            calendarConfig.minExclusive = restrictionDateValue;
        }
        if (restrictionType == 'Maximum')
        {
            document.getElementById(dateField).setAttribute('_maxExclusive', restrictionDateValue );
            calendarConfig.maxExclusive = restrictionDateValue;
        }
        delete calendarConfig.calendar;
    }
}

/**
 * This function should be called when loading the page for any select box that
 * is being rendered as a dojo filtering select, and has an initial 'please select'
 * entry defined.
 * This function removes this option form the HTML, and ensures that the dojo placeholder
 * value will be initially displayed as needed.
 * @param field The HTML select object before it has been converted to the dojo widget.
 */
hyf.util.initFilteringSelect = function(field)
{
    //check if the intial 'please select' entry is currently selected
    //if so, we need to make sure the select has a value attribute
    //set to '' so that dojo correctly displays the placeholder value instead
    if (field.value == '')
    {
        field = hyf.util.addHtmlAttribute(field, 'value', '');
    }

    //remove the 'please select' option tag so it cant be selected
    field.removeChild(field.getElementsByTagName('option')[0]);
}

/**
 * This method attempts to add an HTML attribute to the given field,
 * rather than setting a object property.
 * For browsers that correctly support setAttribute, this is used,
 * but for older IE browsers the outerHTML string is updated directly.
 * As a result, the reference to the field object could be outdated,
 * and so the new reference is always returned
 * @param field The object representing the HTML field to add the attribute to
 * @param attribute The name of the attribute to add
 * @param value The value to give the new attribute
 * @return The updated field object
 */
hyf.util.addHtmlAttribute = function(field, attribute, value)
{
    //IE versions before 8 do not correctly support set attribute,
    //so instead we need to update the HTML string
    if (dojo.isIE < 8)
    {
        var id = field.id;
        var oh = field.outerHTML;
        var insertPoint = oh.indexOf('>');
        oh = oh.substring(0, insertPoint) + ' '+attribute+'="'+value+'"' + oh.substr(insertPoint);
        field.outerHTML = oh;
        field = document.getElementById(id);
    }
    else
    {
        field.setAttribute(attribute, value);
    }
    return field;
}

/**
 * This method attempts to remove an HTML attribute from the given field.
 * For browsers that correctly support removeAttribute, this is used,
 * but for older IE browsers the outerHTML string is updated directly.
 * As a result, the reference to the field object could be outdated,
 * and so the new reference is always returned
 * @param field The object representing the HTML field to remove the attribute from
 * @param attribute The name of the attribute to remove
 * @return The updated field object
 */
hyf.util.removeHtmlAttribute = function(field, attribute)
{
    //IE versions before 8 do not correctly support set attribute,
    //so instead we need to update the HTML string
    if (dojo.isIE < 8)
    {
        var id = field.id;
        var oh = field.outerHTML;
        var elementEnd = oh.indexOf('>');
        var attLoc = oh.indexOf(attribute + '=');
        if ((attLoc == -1) || (attLoc > elementEnd))
            return field;

        var attEnd = oh.indexOf('"', attLoc + attribute.length + 2);
        oh = oh.substring(0, attLoc) + oh.substr(attEnd + 1);
        field.outerHTML = oh;
        field = document.getElementById(id);
    }
    else
    {
        field.removeAttribute(attribute);
    }
    return field;
}
