 /**
* These javascript functions are universal in that they don't require or have any knowledge of the Instant Screening application
*/

 
/*****
    Generic Usage
*****/
 

/**
* Submits master form. Assumption of one form per page.
*/
function submitMe()
{
    document.forms[0].submit();
}

// For selected input boxes, will submit the form if enter is the key that was pressed (assumption of one master form on page)
function submitOnEnter(e)
{
    var key = getKey(e);
    
    if (key == 13)
        document.forms[0].submit();
}


// Jumps focus from one box to another based on charcount (i.e., DOB or PH# boxes where the qty of characters is known ahead of time)
// Using onkeyup works best!
function jumpBoxes(sourceBox, intCount, targetBox, e)
{
    
    var key = getKey(e);
    if (miscAllowedKeys(key)) return true; // See function: this is things like backspace, delete, and tab
    
    if (sourceBox.value.length == intCount)
    {
        targetBox.focus();
    }
    return true;
}

// For dynamically selecting a dropdown option when you know the value
function indexAtVal(dropdownObj, val)
{
    var result = -1;
    for (i = 0; i < dropdownObj.options.length; i++)
    {
        if (dropdownObj.options[i].value == val)
            result = i;
    }
    return result;
}

/**
* Get location of the cursor (caret) in an input box / text area (cross browser)
* Use onkeyup / onclick to keep rolling track i.e. in a hidden field (does not work on blur)
* 
* var txtBox: input box / text area object
* return integer (position of text caret)
*/
function getCaretLoc(txtBox) 
{
    var cPos = 0;
    if (document.selection) // MSIE
    {
        txtBox.focus();
        
        var sel = document.selection.createRange();
        if (txtBox.type == 'text')
        {
            sel.moveStart ('word', -txtBox.value.length);
            cPos = sel.text.length;
        }
        else
        {
            var sel2 = sel.duplicate();
            sel2.moveToElementText(txtBox);
            cPos = -1;
            while(sel2.inRange(sel))
            {
                sel2.moveStart('character');
                cPos++;
            }
        }
        
    }
    else if (txtBox.selectionStart || txtBox.selectionStart == '0') // Firefox
    {
        cPos = txtBox.selectionStart;
    }
    return (cPos);
}

/**
* Get highlighted text from out of a textbox / textarea. Pass the relevant element
*/
function getHighlighted(txtBox)
{
    var txt = '';
    
    if (window.getSelection) // Firefox
    {
        // Getselection doesn't apply to input elements in FF: this is the workaround
        txt = txtBox.value.substr(txtBox.selectionStart, txtBox.selectionEnd - txtBox.selectionStart); 
    }
    else if (document.getSelection)
    {
        txt = document.getSelection();
    }
    else if (document.selection)
    {
        txt = document.selection.createRange().text;
    }   
    else 
    {
        return '';
    }
    return txt;
}

/**
* Accommodates the fact that javascript single click is called whenever a double click is called.
* This method throws one or the other (never both) for the same object, based on a small time-delay.
* (Technically it's odd # of clicks vs even # of clicks, which is pretty standard)
*
* @param evtType javascript event.type
* @param strFunctionSingle The method to call on single click. Don't forget parens
* @param strFunctionDouble The method to call on double click
*/
var clicktype_timeDelay = null;
var clicktype_clickWait = 200; // How long to wait (ms) between clicks before assuming they're done
function clickType(evtType, strFunctionSingle, strFunctionDouble)
{
    switch(evtType)
    {
        case 'click':
            if (clicktype_timeDelay != null)
            {
                // Got here means three clicks = 1 click
                clearTimeout(clicktype_timeDelay); 
            }
            // Wait a short time see if more clicks are forthcoming
            clicktype_timeDelay = setTimeout(strFunctionSingle,clicktype_clickWait); 
        break;
        case 'dblclick':
            if (clicktype_timeDelay != null)
            {
                // Do not call the method called with the single (cancel)
                clearTimeout(clicktype_timeDelay); 
            }
            // Call this one instead
            clicktype_timeDelay = setTimeout(strFunctionDouble,clicktype_clickWait); 
        break;
    }
}

// "Are you sure you want to check this box?"
function warningCheck(cbxObj, txtWarning)
{
    var keepIt = false;
    if (cbxObj.checked == true)
    {
        keepIt = confirm(txtWarning);
        cbxObj.checked = keepIt;
    }
}

/**
* Use for a checkbox that when checked enables other components
*/
function cbxEnableElements(cbx, containing)
{
    if (cbx.checked)
        enableAny(containing);
    else
        disableAny(containing); 
}


/**
* Use for a checkbox that, when checked, disables other elements
*/
function cbxDisableElements(cbx, containing)
{
    if (cbx.checked)
        disableAny(containing);
    else
        enableAny(containing); 
}

/**
* Disables any form element that matches the regular expression. Name grouped elements simlarly and one action can disable all
*/
function disableAny(exp)
{
    var i;
    var elem;
    for (i = 0; i < document.forms[0].elements.length; i++)
    {
        elem = document.forms[0].elements[i];  
        
        if (elem.name != null)
        {      
            if (elem.disabled != true)
            {
                var reg = new RegExp(exp);
                
                if (reg.test(elem.name))
                {
                    elem.disabled = true;
                }
            }
        }
    }
}

/**
* Enables any form element that matches the regular expression. Name grouped elements simlarly and one action can enaable all
*/
function enableAny(exp)
{
    var i;
    var elem;
    for (i = 0; i < document.forms[0].elements.length; i++)
    {
        elem = document.forms[0].elements[i]; 
        if (elem.name != null)
        {
            if (elem.disabled != false)
            {
                var reg = new RegExp(exp);
                
                if (reg.test(elem.name))
                {
                    elem.disabled = false;
                }
            }
        }
    }
}



/**
* Keeps two checkboxes (in practice likely one hidden and one visible) in parallel
* cbx1: Checkbox object, source (the one the user will click on)
* cbx2: Checkbox object, target
*/
function parallelCbx(cbx1, cbx2)
{
    cbx2.checked = cbx1.checked;
    return true;
}

/**
* Keeps two checkboxes (in practice likely one hidden and one visible) in parallel
* cbx1: Checkbox object, source (the one the user typed in)
* cbx2: Checkbox object, target
*/
function parallelFields(field1,field2)
{
    field2.value = field1.value;
}

/**
* This gets around the difficulty, in practice, that an unchecked checkbox doesn't pass at all to the next page.
* Using a hidden field and this function, the next page can watch for the hidden field 
* which will have a different value between checked / unchecked but always be passed.
*
* Usage (this will pass 1 / 0 depending (default 1), but will always pass something.)
* <input type="checkbox" checked="checked" onclick="cbxParallelField(document.forms[0].my_hidden,this.checked)" />
* <input type="hidden" name="my_hidden" value="1" />
*/
function cbxParallelField(hiddenField, checkedYN, checkedValue, uncheckedValue)
{
    if (checkedValue == null) checkedValue = '1';
    if (uncheckedValue == null) uncheckedValue = '0';
    
    if (checkedYN) hiddenField.value = checkedValue;
    else hiddenField.value = uncheckedValue;
}



/**
* toggle: Switches a header object and any children object defined by calling script
* strHeaderName - identifies which header (object containing clickable text, "owner" of hidden children)
* Calling script should define both strHeaderName + '_open' and + '_closed' which will contain the alternating text.
* Calling script also needs to define 2-D allChildren array of [strHeaderName] => [Array of children IDs]
*/
function toggle(strHeaderName)
{   
    var openedHeader = document.getElementById(strHeaderName + '_open');
    var closedHeader = document.getElementById(strHeaderName + '_closed');
    if (openedHeader == null || closedHeader == null || allChildren[strHeaderName] == null) return;
    
    if (closedHeader.style.display == 'none') // Object is in opened state, so close
    {
        openedHeader.style.display = 'none';
        closedHeader.style.display = '';    // Normal display (fixed FF display weirdness in table rows)
        for (childIdx in allChildren[strHeaderName])
        {
            document.getElementById(allChildren[strHeaderName][childIdx]).style.display='none';        
        }
    }
    else if (openedHeader.style.display == 'none') // Object is in closed state, so open
    {
        openedHeader.style.display = '';
        closedHeader.style.display = 'none';
        
        for (childIdx in allChildren[strHeaderName])
        {
            document.getElementById(allChildren[strHeaderName][childIdx]).style.display='block';        
        }
    }
    else
    {
        return;
    }
}

/**
* Visibility swap - simple on / off switch
* oppositeElemID is optional, will turn on when the other is off and vice-versa
*/
function vSwap(elemID, oppositeElemID)
{
    var targetElem = document.getElementById(elemID);
    var blnShowElem = false;
    
    if (targetElem.style.display == 'none')
    {
        blnShowElem = true;
        targetElem.style.display = '';
    }
    else
    {
        targetElem.style.display = 'none';
    }
    
    if (oppositeElemID != null) // Defined a second element that should be opposite of this one
    {
        var oppositeElem = document.getElementById(oppositeElemID);
        if (blnShowElem)
        {
            oppositeElem.style.display = 'none';
        }
        else
        {
            oppositeElem.style.display = '';
        }
        
    }
}

/**
* swap function takes IDs as strings and "Swaps" the visibility of those fields. 
* Calling function should handle "Seed" state of components: 
* i.e. one should already be display:none and if using third field, component should already be disabled.
* The third field (optional) makes a form component disabled while it's hidden (so it doesn't get passed to form).
*/
function swap(strItem1,strItem2,optionalFormComponent)
{
    var obj1 = document.getElementById(strItem1);
    var obj2 = document.getElementById(strItem2);
    if (optionalFormComponent != null)
    {
        document.getElementById(optionalFormComponent).disabled = (!(document.getElementById(optionalFormComponent).disabled));
    }
    
    if (obj1.style.display == 'none')
    {
        obj1.style.display = ''; // This is "Display like normal" -- Firefox has an issue with <td>s without this
        obj2.style.display = 'none';
    }
    else
    {
        obj2.style.display = '';
        obj1.style.display = 'none';
    }
}

/**
* ** Unused ** -- see exclusive function in searchdisplay class **
* This function makes two checkboxes act like radio buttons: that is, one or the other (not both)
* if forceOne is passed as true then the user cannot deselect both (also like radio buttons)
* Pass IDs (not objects) for the two checkboxes. #1 should be the one the user just clicked.
*/
function exclusiveCbx(cbxID1, cbxID2, forceOne)
{
    if (forceOne == null) forceOne = false;
    var cbx1 = document.getElementById(cbxID1);
    var cbx2 = document.getElementById(cbxID2);
    
    if (forceOne)
    {
        cbx2.checked = !cbx1.checked; // IOW if you just *unchecked* #1, #2 will check itself
    }
    else
    {
        if (cbx1.checked) cbx2.checked = false;
    }
}


/**
* Puts text into new window. This is used for Debug class mostly
*/
function popupText(strname, srttext)
{
    newwin = window.open('',strname,'width=300,height=400,scrollbars=yes,resizable=yes');
    top.newwin.document.writeln(strtext);
}

/**
* For use when order in which a series of checkboxes are clicked is important. Adds or removes this value
* cbx = checkbox; targetField = text field, usually hidden
*/
function clickOrder(cbx, targetField)
{
    if (cbx == null || targetField == null) return;
    var foundKeys;
    var newVal = cbx.value;
    // ::TODO:: See if newVal has commas, if so needs split and remove from 
    if (cbx.checked) // Add to list at the end
    {
        if (targetField.value != '')
        {
            targetField.value += ',';
        }
        targetField.value += newVal;
    }
    else // Remove from the list
    {
        if (targetField.value != '')
        {
            // This accomodates a field removing multiple entries at once (comma separated list of keys)
            if (newVal.indexOf(',') == -1)
            {
                foundKeys = Array(newVal);
            }
            else
            {
                foundKeys = newVal.split(',');
            }
            
            var currentAry = targetField.value.split(',');
            for(key in foundKeys)
            {
                var foundKey = array_search(foundKeys[key],currentAry);
                if (null != currentAry[foundKey]) 
                {
                    delete currentAry[foundKey];
                }
            }
            targetField.value = implode(',',currentAry);            
        }
    }
}


/**
* This function clears all options from a select box
*/
function clearOptions(selectBox)
{
    i = 0;
    while(selectBox.options[i] != null)
    {
        selectBox.remove(i);
    }
}

/**
* Add an option to a select Box (passed)
*/
function addOption(selectBox,strText,strValue)
{
    var opt = document.createElement('option');
    opt.text = strText;
    opt.value = strValue;
    
    try
    {
        selectBox.add(opt,null); // standards compliant
    }
    catch(ex)
    {
        selectBox.add(opt); // IE only
    }
}

/**
* Get all selected items in a select box (keys, not values)
* Returns indexed array of keys (selectedIndex(es))
* This does work for non-multi-select, will return a 1 element array
*/
function multiSelectKeys(selectBox)
{
    var aryKeys = new Array();
    while(selectBox.selectedIndex != -1)
    {
        aryKeys.push(selectBox.selectedIndex);
        selectBox.options[selectBox.selectedIndex].selected = false;
    }
    return aryKeys;
}


function cbxDisableField(cbx, fieldID)
{
    var targetfield = document.getElementById(fieldID);
    if (targetfield == null) return false;
    if (cbx.checked)
    {
        targetfield.disabled = true;
    }
    else
    {
        targetfield.disabled = false;
    }
    return true;
}

function confirmDelete(strPrompt, setField, setValue)
{
    var blnyn = confirm(strPrompt);
    if (!blnyn) return false;
    
    if (setField == null) return true; // This is acceptable use of this method
    setField.value = setValue;
    document.forms[0].submit();
    return true;
}
 
/*****
    Validation Functions
*****/

/**
* This method returns code for which keyboard key was pressed.
* Use for onKeyUp and onKeyDown events, will be 0 for onKeyPress events.
*/
function getKey(e)
{
    if (window.event)
       return window.event.keyCode;
    else if (e)
    {
        return e.keyCode;
    }
    return false;
}

/**
* This method returns code for the character that was sent to the browser.
* Use for onKeyPress events
*/
function getChar(e)
{
    if (window.event)
       return window.event.charCode;
    else if (e)
    {
        return e.charCode;
    }
    return false;
}

/**
* For methods that restrict movement based on the key pressed, this is a simplified way to ignore common harmless keystrokes
* Returns true when the key press should be allowed. False means it's some other character, up to you to validate at that point
*/
function miscAllowedKeys(key)
{
    // This is misc non-char
    if (key == 0) return true;
    // backspace and delete
    if (key == 8 || key == 9) return true;
    // tab and back-tab
    if (key == 46 || key == 16) return true;
    // Arrows and home / end
    if (key > 34 && key < 41) return true; 
    
    return false;
}

/**
* Will cancel out any keypress that is not a number (or, if passed true, including dashes)
  Usage: onkeydown="return onlyNumbers(event);" event is for Firefox compatibility
*/
function onlyNumbers(e, ohAndDashes)
{
	var keychar;
	var reg;
    var k = getKey(e);
    
    if (miscAllowedKeys(k)) return true;

    if (ohAndDashes == null)
	   ohAndDashes = false;
	
    if (false !== asNumber(k,true)) return true;
    if (ohAndDashes && (k == 189 || k == 109)) return true;
    return false;
    
}

/**
* Will cancel out any keypress that is not a number or decimal (.)
* Usage: onkeydown="return onlyFloat(event);" event is for Firefox compatibility
*/
function onlyFloat(e, ohAndDashes)
{
	var keychar;
	var reg;
    var key = getKey(e);
    if (ohAndDashes == null) ohAndDashes = false;
    
    // Tab and backspace etc
    if (miscAllowedKeys(key)) return true;
    if (false !== asNumber(key,true)) return true;
    if (key == 190 || key == 110) return true; // .
    if (ohAndDashes && (key == 189 || key == 109)) return true; // -

    return false;
}

/**
* Get number typed by the user based on event key code. Accomodates top keys and key pad
*
* Returns boolean false when key is not a number. ::WARNING:: Use === to distinguish this result from the numeric integer 0 !!
*
* Otherwise returns integer
* blnKey - optional, defaults to true
* Note that the keypad reports different key (key up / key down) but same character (matching keypress)
*/
function asNumber(intCode, blnKey)
{
    if (null == blnKey) blnKey = true;
    // Ascii codes
    if (intCode >= 48 && intCode <= 57)
    {
        return intCode - 48;
    }
    
    if (blnKey)
    {
        if (intCode >= 96 && intCode <= 105 )
        {
            return intCode - 96;
        }
    }
    return false;
}

/**
* Enforces a date format of mm/dd/yyyy -- No non-numbers allowed, no slashes out of place allowed, adds slashes if needed
* Usage: onkeydown="dateFormat(this, event);" Ideally use with dateExit()
*
* ::NOTE:: keypress gives very strange results. It is keydown!!
* ::NOTE2:: Do not forget to pass the second param, keyword event. will work in IE without it, but will not work in Firefox!
* ::NOTE3:: This function does not validate dates: 99/99/9999 is considered "valid". Validate further in code
*/
function dateFormat(inputBox, e)
{
    var key = getKey(e);
    
    if (miscAllowedKeys(key)) return true;
    
    // This is what allows us to type over text that is highlighted
    var highlighted = getHighlighted(inputBox);
    if (highlighted != '') inputBox.value = '';
    
    if (key == 8) // Backspace
    {
        // Backspace: delete the added slash as well as the number preceding
        switch (inputBox.value.length)
        {
            case 3: 
                inputBox.value = inputBox.value.substr(0,1);
                return false;
            break;
            case 6:
                inputBox.value = inputBox.value.substr(0,4);
                return false;
            break;
            default: 
                return true;
        }
    }
    
    if (key == 191 || key == 111) // The '/' key -- only allow through in its proper spot
    {
        var strMonth;
        var strDate;
        if (inputBox.value.length == 1) // Turn 1/ into 01/
        {
            strMonth = inputBox.value.substr(0,1);
            inputBox.value = '0' + strMonth;
            return true; // The slash
        }
        if (inputBox.value.length == 4) // Turn 12/5/ into 12/05/
        {
            strMonth = inputBox.value.substr(0,2);
            strDate = inputBox.value.substr(3,1);
            inputBox.value = strMonth + '/0' + strDate;
            return true; // The slash
        }
        return (inputBox.value.length == 2 || inputBox.value.length == 5);
    }
    
    if (inputBox.value.length == 10) return false; // No more characters after 10, backspace is already accomodated
    
    // Top numbers || keypad numbers
    if (false === (num = asNumber(key,true))) return false;
    
    // Here: typed a number. 
    switch(inputBox.value.length)
    {
        case 2:
        case 5:
            inputBox.value += '/';     
        break;
    }
    return true;
}

/**
* Turns mm/dd/yy into mm/dd/yyyy (e.g. 01/01/09 -> 01/01/2009)
* Uses built in Date() methods to always accommodate centuries, in a "rolling" timespan
* Use onblur()
*/
function dateExit(txtBox)
{
    var d;
    switch(txtBox.value.length)
    {
        case 5:
            txtBox.value += '/';
        case 6:
            d = new Date();
            txtBox.value += d.getFullYear(); // Current 4 d year
        break;
        case 8: // 2 digit year
            d = new Date();
            inputYear = Number(txtBox.value.substring(6));
            currYear  = Number(d.getFullYear().toString().substring(2));
            currCentury = Number(d.getFullYear().toString().substring(0,2));
            
            if (inputYear > (currYear + 10)) // If the year entered is more than 10 years from now, they mean last century
            {
                insertCentury = currCentury - 1;
            }
            else
            {
                insertCentury = currCentury;
            }
            txtBox.value = txtBox.value.substring(0,6) + insertCentury.toString() + txtBox.value.substring(6);
        break;
        case 10: // Complete and normal
        break;
        default:
            txtBox.value = '';
        break;
    }
}

/**
* Enforces a phone format of xxx-xxx-xxxx
* Will put the dashes in for you if you just keep typing
* Usage: onkeydown="return phoneFormat(this, event);" ::NOTE:: keypress gives very strange results. It is keydown!!
*/
function phoneFormat(txtBox, e)
{
    var k = getKey(e);
    if (miscAllowedKeys(k)) return true; // Things such as tabs or backspace that should always be allowed
    
    // This is what allows us to type over text that is highlighted
    var highlighted = getHighlighted(txtBox);
    if (highlighted != '') inputBox.value = '';
    
    var strLen = txtBox.value.length;
    if (strLen == 12) return false;
    
    // The key codes for hyphen ( - )
    var hyphenTop = 189;
    var hyphenPad = 109;
    
    if ((k == hyphenTop) || (k == hyphenPad))
    {
        return ((strLen == 3) || (strLen == 7));
    }
    
    if (asNumber(k,true) === false) return false;
    
    // Here: typed a number
    if ((strLen == 3) || (strLen == 7))
    {
        txtBox.value += '-';
        return true;
    }
    return true;
}

/**
* Enforces time constraints: that is, ##:## 0-padded.
* with hours section being less than 12 and minutes section being less than 60
* Usage: onkeydown="return timeFormat(this, event);" As with dateFormat(), keydown() not keypress()!
* Don't forget to use onblur="timeExit(this);"
*/
function timeFormat(inputBox, e)
{
    var key = getKey(e);
    
    if (key == 8) // backspace
    {
        switch (inputBox.value.length)
        {
            case 3: 
                inputBox.value = inputBox.value.substr(0,1); // Remove colon
                return false;
            break;
            default: 
                return true;
            break;
        }
    }
    
    if (miscAllowedKeys(key)) return true;
    
    // colon / semicolon is 186 in IE and 59 in Firefox
    var blnColonPressed = (key == 59 || key == 186);
    if (blnColonPressed) // This is the colon (:) time separator
    {
        if (inputBox.value.length == 1) // Turn 1: into 01:
        {
            inputBox.value = '0' + inputBox.value;
            return true; // Add on :
        }
        
        return (inputBox.value.length == 2);
    }
    
    // Make sure they typed a number
    if (false === (num = asNumber(key,true))) return false;
    
    // This is what allows us to type over text that is highlighted
    var highlighted = getHighlighted(inputBox);
    if (highlighted != '') inputBox.value = '';
    
    switch(inputBox.value.length)
    {
        case 1: // Second number typed
            
            switch(inputBox.value.substr(0,1)) // Depends on the first number typed
            {
                case '0':
                    return true;
                break;
                case '1':
                    if (num <= 2)
                    {
                        return true;
                    }
                    else if (num < 6) // e.g. you can type "1 3 0" for 01:30 but "1 2 0" will be "12:0" as per above
                    {
                        inputBox.value = '01:';
                        return true;
                    }
                    
                    return false;
                break;
                default:
                    if (num < 6) // e.g. you can type "9 3 0" for 09:30
                    {
                        inputBox.value = '0' + inputBox.value + ':';
                        return true;
                    }
                    return false;
                break;
            }
        break;
        case 2:
            if (num < 6)
            {
                inputBox.value += ':';
                return true;
            }
            if (inputBox.value.substr(0,1) == '1') // This is the 1:29 problem
            {
                inputBox.value = '01:' + inputBox.value.substr(1,1);
                return true;
            }
            return false;
        break;
        case 3: // This is after the ':' divider already exists. Getting here at all means the user typed it in themselves.
            return (num < 6);
        break;
        case 5:
            return false; // No more than five chars
        break;
        default:
            return true;
    }
}

/**
* Fixes partially entered time values in a time-enforced text box
* Usage: onblur="timeExit(this);"
*/
function timeExit(txtBox)
{
    if (txtBox.value == '' || txtBox.value.length == 5) return;
    switch(txtBox.value.length)
    {
        case 1:
            txtBox.value = '0' + txtBox.value; // value of '2' becomes 02:00
        case 2:
            txtBox.value += ':00';
        break;
        case 3:
            txtBox.value += '0'; // This shouldn't be possible as the colon separator is inserted automatically
        case 4:
            if (txtBox.value.substr(0,1) == '1') 
            {
                // This is the 1:15 fix. (That is: nobody would exit with 1-1-5 and want 11:50 -- they want 01:15 but there's no interpreting that from 11:5x except on blur)
                txtBox.value = '0' + txtBox.value.substr(0,1) + ':' + txtBox.value.substr(1,1) + txtBox.value.substr(3,1);
            }
            else
            {
                txtBox.value += '0'; // No way to reliably interpret user intent here; this just maintains consistent length
            }
        break;
        default:
            txtBox.value = txtBox.value.substr(0,5); // The only way this happens is if the user pasted in to get around the front-end restrictions
    }
}


/**
* This method enforces money formatting (Numbers, decimal enforcement, two chars to the right of decimal) 
* using front-end validation (canceling non-valid key strokes)
* displayMode is boolean (not passed is false)
* In display mode this method allows commas (left of decimal).
* Usage: onkeydown="return moneyFormat(this,event,false)"
* For display mode you can combine with money_exit upon leaving the field
*/
function moneyFormat(inputBox, e, displayMode)
{
    if (displayMode == null) displayMode = false;
    var decimalKey1 = 190;
    var decimalKey2 = 110;
    var commaKey = 188;
    
    var key = getKey(e);
    if (miscAllowedKeys(key)) return true;
    
    // This is what allows us to type over text that is highlighted
    var highlighted = getHighlighted(inputBox);
    if (highlighted != '') inputBox.value = '';
    
    var decLoc = inputBox.value.indexOf('.');
    var caretLoc = getCaretLoc(inputBox);
    
    if ((decLoc == -1) || ((caretLoc - 1) < decLoc)) // Left of decimal (or no decimal)
    {
        if ((key == decimalKey1) || (key == decimalKey2)) return true; // decimal
        if (displayMode && (key == commaKey)) return true; // comma 
        if (false === (num = asNumber(key))) return false;
    }
    else // Right of decimal
    {
        if (inputBox.value.length > (decLoc + 2)) return false; // Only two chars
        if (false === (num = asNumber(key))) return false;
    }
    
    return true;
}

/**
* This method takes an input field that is supposed to be a money value and fills to a dollar length.
* i.e. 1 -> 1.00 
* Does NOT truncate or validate, always use together with money_format (above)
* Usage: onblur="moneyExit(this);"
*/
function moneyExit(txtBox)
{
    if (txtBox == null) return false;
    if (txtBox.value == '') return true; // No value
    
    var decLoc = txtBox.value.indexOf('.');
    if (decLoc == -1) txtBox.value += '.00';
    else
    {
        switch(txtBox.value.length - decLoc)
        {
            case 1:
                txtBox.value += '00';
            break;
            case 2:
                txtBox.value += '0';
            break;
            default:
        }
    }
    return true;
}

/**
* Keeps track of how many characters used / left. Does NOT enforce any restrictions
* If the maxChars param is passed, we count back from a set number; otherwise we count up
*/
function charBox(txtField, counterID, maxChars)
{
    var counterBox = document.getElementById(counterID);
    if (counterBox == null) return;
    if (maxChars == null)
    {
        counterBox.innerHTML = txtField.value.length;
    }
    else
    {
        counterBox.innerHTML = maxChars - txtField.value.length;
    }
}

function maxlength(txtArea, e, maxChars)
{
    var key = getKey(e);
    if (miscAllowedKeys(key)) return true; // See function: this is things like backspace, delete, and tab
    
    if (maxChars)
    {
        if (txtArea.value.length >= maxChars) return false;
    }
    return true;
}


function blockExtraChars(e)
{
    var k = getKey(e);
    switch (k)
    {
        case 45: // hyphen ( - )
        case 39: // apostrophe ( ' ) 
        // You can add more here...
            return false;
        default: 
            return true;
    }   
}

/**
* Disallow spaces typed by the user in a text box
*/
function noSpaces(e)
{
    var k = getKey(e);
    switch (k)
    {
        case 32: // space bar
            return false;
        default: 
            return true;
    }   
}

/*********
    Array Functions
*********/

/**
* Duplicates the functionality of the PHP implode, pass array return string
* ::NOTE:: Explode functionality already exists as js function :: str.split(sep);
*/
function implode(strSeparator, ary)
{
    var str = '';

    for (keyvar in ary)
    {
        str = str + ary[keyvar] + strSeparator;
    }
    return str.substring(0,(str.length - strSeparator.length)); // Remove last separator at the end
}

/**
* Duplicates functionality of PHP array_search class. 
* Returns key if value found in array, otherwise returns false
*/
function array_search(needle, haystack)
{
    for (hkey in haystack)
    {
        if (needle == haystack[hkey])
        {
            return hkey;
        }
    }
    return false;
}

/**
* Duplicates functionality of PHP in_array
* Returns true if the value is found in the array, false otherwise
*/
function in_array(needle, haystack)
{
        for (hkey in haystack)
    {
        if (needle == haystack[hkey])
        {
            return true;
        }
    }
    return false;
}


/**
* Remove an element of an array and return the resulting array, still in order, preserving keys.
* Note that this does NOT re-index keys, thus .length property will give unpredictable results
*/
function removeByValue(ary, elemValue)
{
    
    var tAry = new Array();
    var k;
    if (ary.length == 0) return tAry;
    for (k in ary)
    {
        if (elemValue != ary[k])
        {
            tAry[k] = ary[k];
        }
    }
    return tAry;
    
}


/**
* Remove an element of an array (by key) and return the resulting array, still in order, preserving keys.
* Note that this does NOT re-index keys, thus .length property will give unpredictable results
*/
function removeByKey(ary, elemKey)
{
    var tAry = new Array();
    var k;
    if (ary.length == 0) return tAry;
    for (k in ary)
    {
        if (elemKey != k)
        {
            tAry[k] = ary[k];
        }
    }
    return tAry;
}

function is_array(obj) {
    return obj.constructor == Array;
}

/*
/**
* Kind of a global function (for the walkthrough) -- restricts based on char type if requested, maxChars if passed
* restrictType: 1 for numbers only, 2 for float (- and .) 0 means any chars allowed
* maxChars: max allowable (no restrictions if 0)
* strCounterBox: ID for the field to update with count
*
function charCheck(txtBox, e, restrictType, maxChars)
{
    var key = getKey(e);
    if (miscAllowedKeys(key)) return true; // See function: this is things like backspace, delete, and tab
    
    if (restrictType == 1)
    {
        if (!onlyNumbers(e)) return false;
    }
    else if (restrictType == 2)
    {
        if (!onlyFloat(e,true)) return false;
    }
    
    if (maxChars)
    {
        if (txtBox.value.length >= maxChars) return false;
    }
    return true;
}
*/