// wForms - a javascript extension to web forms.
// v0.99.24 - Dec. 05 2005
// Copyright (c) 2005 Cédric Savarese <pro@4213miles.com>
// This software is licensed under the CC-GNU LGPL <http://creativecommons.org/licenses/LGPL/2.1/>
// Other Contributors: Michael Duff (fullmoondesigns.net)

// Change Log: see http://formassembly.com/blog/wforms-a-javascript-extension-to-web-forms/

// v0.99.24 Changed rules for submit button id & multipage behavior.
// v0.99.2  Fixed the refreshAllState / refreshState bug with multiple checkbox switches targeting the same element.
//          Fixed the checkOneRequired method. GetAttribute('value') didn't work in Firefox (thx to Bill Rafferty)
//          Added a SPAN element to the generated 'Repeat' and 'Delete' links to allow for CSS Image Replacement Technique
//          Fixed Repeat behavior's 'find insert node' loop to handle server-side generated text-nodes (when populating a repeated group)
//          Changed default validation behavior in Safari (now always validate, but will break if used in conjunction w/ Switch or Paging behavior)
//          Added support for 'required' on a TABLE, TR or TD
//          Fixed Inline Event Handler in the Repeat behavior (for IE)
// Known Problems:
//      Safari 1.x:     Validation will run on invisible fields (switched off or w/ paging behavior. cf. checkVisibility())
//      IE 5.2 Mac:     Validation Disabled. currentStyle.display returns an empty string in checkVisibility() causing non-visible fields to get validated.
//                      Buggy Rendering of the Repeat behavior
//                      Counter Field of Repeat Behavior not submitted (probably setting the name attribute didn't work)
//      IE 5.0 PC:      Repeat behavior doesn't work. All field are created as TEXT input ? To be checked again.


function wFORMS() { // wFORMS Class Constructor

    var wu = new wUTILITY();
    var self = this;

    // CSS class name definitions.
    this.classNamePrefix_switch         = "switch";
    this.className_switchIsOn           = "swtchIsOn"; // used to keep track of the switch state on buttons and links (where the checked attribute is not available) (added in v0.97)
    this.classNamePrefix_offState       = "offstate";
    this.classNamePrefix_onState        = "onstate";
    this.className_repeat               = "repeat";
    this.className_delete               = "removeable";
    this.className_required             = "required";
    this.className_validationError_msg  = "errMsg";
    this.className_validationError_fld  = "errFld";
    this.classNamePrefix_validation     = "validate";
    this.className_duplicateLink        = "duplicateLink";
    this.className_removeLink           = "removeLink";
    this.className_activeFieldHint      = "field-hint";
    this.className_inactiveFieldHint    = "field-hint-inactive";
    this.className_paging               = "wfPage";
    this.className_pagingCurrent        = "wfCurrentPage";
    this.className_pagingButtons        = "wfPageButton";
    this.className_hideSubmit           = "wfHideSubmit";
    // id attribute suffixes
    this.idSuffix_fieldHint             = "-H";
    this.idSuffix_fieldLabel            = "-L";
    this.idSuffix_fieldError            = "-E";
    this.idSuffix_repeatCounter         = "-RC";
    this.idSuffix_duplicateLink         = "-wfDL";              // not fully implemented yet
    this.idPrefix_pageIndex             = "wfPgIndex-";

    // Behavior configuration options
    this.preserveRadioName              = true;                 // if true, Repeat behavior will preserve name attributes for radio input.
    this.switchScopeRootTag             = "FORM";               // limit the scope of the switch behavior. You may use 'BODY'.
    this.functionName_formValidation = "this.formValidation";   // Form validation function name. May be overidden if you need to run your own validation routine (but make sure to run formValidation() in it).
    this.showAlertOnError = false;                               // sets to false to not show the alert when a validation error occurs.
    this.preventSubmissionOnEnter = false;                      // prevents submission when pressing the 'enter' key. Set to true if pagination behavior is used.

    // Error messages. This array may be overwritten in a separate js file for localization or customization purpose.
    this.arrErrorMsg = new Array();
    this.arrErrorMsg[0] = "This field is required. "; // required
    this.arrErrorMsg[1] = "The text must use alphabetic characters only (a-z, A-Z). Numbers are not allowed. ";     // validate_alpha
    this.arrErrorMsg[2] = "This does not appear to be a valid email address.";                                  // validate_email
    this.arrErrorMsg[3] = "Please enter an integer.";                                                           // validate_integer
    this.arrErrorMsg[4] = "Please enter a float (ex. 1.9).";
    this.arrErrorMsg[5] = "Unsafe password. Your password should be between 4 and 12 characters long and use a combinaison of upper-case and lower-case letters.";
    this.arrErrorMsg[6] = "Please use alpha-numeric characters only [a-z 0-9].";
    this.arrErrorMsg[7] = "This does not appear to be a valid date.";
    this.arrErrorMsg[8] = "%% error(s) detected. Your form has not been submitted yet.\nPlease check the information you provided."; // %% will be replaced by the actual number of errors.

    // Other Messages
    this.arrMsg = new Array();
    this.arrMsg[0] = "Add another response";    // repeat link
    this.arrMsg[1] = "Will duplicate this question or section." // title attribute on the repeat link
    this.arrMsg[2] = "Remove";      // remove link
    this.arrMsg[3] = "Will remove this question or section." // title attribute on the remove link
    this.arrMsg[4] = "Next Page";
    this.arrMsg[5] = "Previous Page";
    this.utilities = wu;


    // =======================================================================================
    // Switch Behavior Methods
    // =======================================================================================

    // Privileged Instance Methods
    //----------------------------
    this.refreshAllStates = function(fId) {
        wu.debug('refreshAll:'+ fId);
        var f=document.getElementById(fId);
        if(!f) return;
        // loop through the fields
        var x = wu.getElements(f);
        for (var i=0;i<x.length;i++) {
            // add switch/state behavior
            if (x[i].tagName.toUpperCase() == "SELECT" && wu.isEventHandled(x[i],'change') ) {
                this.refreshState(null,x[i]);
            }
            if (x[i].className && x[i].className.indexOf(this.classNamePrefix_switch) != -1) {
                switch(x[i].tagName.toUpperCase()) {
                    case "OPTION":
                        break;
                    default:
                        this.refreshState(null,x[i], true); // true= We only set the target on (not off)
                        break;
                }
            }
        }
    }

    this.refreshState = function(e) {

        var onStateOnly = false; // this applies when the refreshState runs as part of the refreshAllStates function.

        if(!e && arguments.length>1) {
            var srcE = arguments[1];
            if(arguments.length>2)
                onStateOnly = arguments[2];
        }
        else
            var srcE = wu.getSrcElement(e);


        switch(srcE.tagName.toUpperCase()) {
            case "SELECT":

                var selectedStateClass="";
                var localScope = switchScope(srcE);
                for(var i=0;i<srcE.options.length;i++) {
                    if(srcE.options[i].className.indexOf(self.classNamePrefix_switch) != -1 ) {
                        var s = srcE.options[i].className;
                        s = s.substr(s.indexOf(self.classNamePrefix_switch)).split(" ")[0].substr(self.classNamePrefix_switch.length);
                        var offStateClass = self.classNamePrefix_offState + s;
                        var onStateClass = self.classNamePrefix_onState + s;
                        if(i==srcE.selectedIndex) {
                            switchState(localScope, offStateClass, onStateClass);
                            selectedStateClass = onStateClass; // prevents further switching off
                        }
                        else if(onStateClass != selectedStateClass) {
                            switchState(localScope, onStateClass, offStateClass);
                        }
                    }
                }
                break;
            case "INPUT":
                if(srcE.type.toLowerCase() == 'radio') {
                    // Go through the radio group.
                    for(var i=0;i <srcE.form[srcE.name].length;i++) {
                        var r = srcE.form[srcE.name][i];
                        if(r.className && r.className.indexOf(self.classNamePrefix_switch)!=-1) {
                            var s = r.className;
                            s = s.substr(s.indexOf(self.classNamePrefix_switch)).split(" ")[0].substr(self.classNamePrefix_switch.length);
                            var offStateClass = self.classNamePrefix_offState + s;
                            var onStateClass = self.classNamePrefix_onState + s;

                            if(r.checked)
                                switchState(switchScope(r), offStateClass, onStateClass);
                            else {
                                switchState(switchScope(r), onStateClass, offStateClass);
                            }
                        }
                    }
                } else {
                    var s = srcE.className;
                    s = s.substr(s.indexOf(self.classNamePrefix_switch)).split(" ")[0].substr(self.classNamePrefix_switch.length);
                    var offStateClass = self.classNamePrefix_offState + s;
                    var onStateClass = self.classNamePrefix_onState + s;

                    if(srcE.checked ||
                      (srcE.type.toLowerCase() == 'button' && srcE != arguments[1] && srcE.className.indexOf(self.className_switchIsOn) == -1 )) { // && !srcE.defaultChecked
                        switchState(switchScope(srcE), offStateClass, onStateClass);
                        if(srcE.type.toLowerCase() == 'button') {
                            srcE.className += " " + self.className_switchIsOn;
                        }
                    }
                    else if(!onStateOnly) {
                        switchState(switchScope(srcE), onStateClass, offStateClass);
                        if(srcE.type.toLowerCase() == 'button' && srcE != arguments[1]) {
                            srcE.className = srcE.className.replace(self.className_switchIsOn,"");
                        }
                    }
                }
                break;
            default:
                var s = srcE.className;
                s = s.substr(s.indexOf(self.classNamePrefix_switch)).split(" ")[0].substr(self.classNamePrefix_switch.length);
                var offStateClass = self.classNamePrefix_offState + s;
                var onStateClass = self.classNamePrefix_onState + s;
                if(srcE != arguments[1] && srcE.className.indexOf(self.className_switchIsOn) == -1) {
                    switchState(switchScope(srcE), offStateClass, onStateClass);
                    srcE.className += " " + self.className_switchIsOn;
                }
                else if(srcE != arguments[1]) {
                    switchState(switchScope(srcE), onStateClass, offStateClass);
                    srcE.className = srcE.className.replace(self.className_switchIsOn,"");
                }
                break;
        }
    }

    // Switch Behavior Private Methods
    // -------------------------------

    // The switch scope limits the element tree on which the switch can operate.
    // Because of interference issue, a SWITCH contained in a REPEATed block
    // should not be allowed to operate outside of it.
    function switchScope(n) {
        while(n) {
             if (n.className && ( (' '+n.className+' ').indexOf(' '+self.className_repeat+' ') != -1 || (' '+n.className+' ').indexOf(' '+self.className_delete+' ') != -1))
                return n;
             if (n.tagName.toUpperCase() == self.switchScopeRootTag)
                return n;
             n = n.parentNode;
        }
        return null; // should not happen. A form should exists.
    }

    // Recursive loop within the scope to switch classes
    function switchState(n, oldStateClass, newStateClass) {
        if(n.nodeType != 1) return;
        if(n.className && n.className.indexOf(oldStateClass) != -1) {
            n.className = n.className.replace(oldStateClass, newStateClass);
            // FAT support
            // n.id not set.
            // if(Fat && n.id) Fat.fade_element(n.id, 10, 600, "#FFFFCC", "#FFFFFF");
        }
        for (var i=0;i<n.childNodes.length;i++)
            switchState(n.childNodes[i], oldStateClass, newStateClass);
    }

    // =======================================================================================
    // Repeat/Remove Behavior Methods
    // =======================================================================================

    // -------------------
    // Priviledged Methods
    // -------------------
    this.duplicateFieldGroup = function(e) {
        var srcE = wu.getSrcElement(e);
        // Get Element to duplicate.
        var sourceNode = srcE.parentNode;
        while (sourceNode && (!sourceNode.className || (' '+sourceNode.className+' ').indexOf(' '+self.className_repeat+' ') == -1)) {
            sourceNode = sourceNode.parentNode;
        }
        if (sourceNode && sourceNode.className.indexOf(self.className_repeat) != -1) {
            // Extract row counter information
            counterField = document.getElementById(sourceNode.id + self.idSuffix_repeatCounter);
            if(!counterField) return; // should not happen.
            var rowCount = parseInt(counterField.value) + 1;
            // Prepare id suffix
            var suffix = "-" + rowCount.toString()
            // duplicate node tree
            var dupTree = replicateTree(sourceNode, null, suffix);  //  sourceNode.cloneNode(true);
            // find insert point in DOM tree (after existing repeated element)
            var insertNode = sourceNode.nextSibling;
            while(insertNode &&
                  (insertNode.nodeType==3 ||       // skip text-node that can be generated server-side when populating a previously repeated group
                   ( insertNode.nodeType==1 &&
                     insertNode.className &&
                     insertNode.className.indexOf(self.className_delete) != -1))) {
                insertNode = insertNode.nextSibling;
            }

            sourceNode.parentNode.insertBefore(dupTree,insertNode);  // Buggy rendering in IE5/Mac
            // if(navigator.appVersion.indexOf("MSIE") != -1 && navigator.appVersion.indexOf("Windows") == -1)
            //

            // the copy is not duplicable, it's removeable
            dupTree.className = sourceNode.className.replace(self.className_repeat,self.className_delete);
            // Save new row count
            document.getElementById(sourceNode.id + self.idSuffix_repeatCounter).value = rowCount;
            // re-add behaviors
            if(!dupTree.id) dupTree.id = wu.randomId() + suffix;  //  createAttribute()
            wu.debug('Duplicated tree id : '+dupTree.id);
            self.addBehaviors(dupTree.id);

            // FAT support
            if(typeof Fat != 'undefined' && Fat && dupTree.id) Fat.fade_element(dupTree.id, 10, 600, "#FFFFCC", "#FFFFFF");

        }
        return wu.XBrowserPreventEventDefault(e);
    }
    this.removeFieldGroup = function(e) {
        var srcE = wu.getSrcElement(e); // Get Element to remove.
        var delNode = srcE.parentNode;

        while (delNode && (' '+delNode.className+' ').indexOf(' '+self.className_delete+' ') == -1) {
            delNode = delNode.parentNode;
        }
        delNode.parentNode.removeChild(delNode);
        return wu.XBrowserPreventEventDefault(e);
    }

    // Repeat Behavior Private Methods
    // -------------------------------
    function removeRepeatCountSuffix(str) {
        return str.replace(/-\d$/,'');
    }

    function replicateTree(srcNode,dupParentNode, idSuffix) {
        switch(srcNode.nodeType) {
            case 1: // ELEMENT-NODE
                if(srcNode.className.indexOf(self.className_duplicateLink) != -1 ||
                    srcNode.className.indexOf(self.className_removeLink) != -1  )
                    return null; // Exclude the 'duplicate/remove' links
                if(srcNode.className.indexOf(self.className_delete) != -1) {
                    return null; // Exclude duplicated elements of a nested repeat group
                }
                if(srcNode.className.indexOf(self.className_repeat) != -1 && dupParentNode!=null) {
                    // Match nested repeat group only
                    idSuffix = idSuffix.replace('-','__');
                }

                if(document.all && !window.opera) {
                    // IE Specific : see http://msdn.microsoft.com/workshop/author/dhtml/reference/properties/name_2.asp
                    var tagHtml = srcNode.tagName;

                    if(srcNode.name)
                        if (srcNode.tagName.toUpperCase()=="INPUT" && srcNode.type.toLowerCase()=="radio" && self.preserveRadioName)
                            tagHtml += " NAME='" + srcNode.name + "' ";
                        else
                            tagHtml += " NAME='" + removeRepeatCountSuffix(srcNode.name) + idSuffix + "' ";
                    if(srcNode.type) {
                        tagHtml += " TYPE='" + srcNode.type + "' ";
                    }
                    if(srcNode.selected)
                        tagHtml += " SELECTED='SELECTED' ";
                    if(srcNode.checked)
                        tagHtml += " CHECKED='CHECKED' ";

                    if(navigator.appVersion.indexOf("MSIE") != -1 && navigator.appVersion.indexOf("Windows") == -1) // IE5 Mac
                        var newNode = document.createElement(tagHtml);
                    else
                        var newNode = document.createElement("<" + tagHtml + "></"+ srcNode.tagName + ">");
                    try { newNode.type = srcNode.type; } catch(e) {}; // nail it down for IE5 ?, breaks in IE6

                }
                else
                    var newNode = document.createElement(srcNode.tagName);

                // get attributes
                for(var i=0; i< srcNode.attributes.length; i++) {
                    // Get Attribute Value. Adjust it if necessary.
                    if( srcNode.attributes[i].specified || // in IE, the attributes array contains all attributes in the DTD
                        srcNode.attributes[i].nodeName.toLowerCase() == 'value' ) { // attr.specified buggy in IE?

                        if( srcNode.attributes[i].nodeName.toLowerCase() == "id" ||
                            srcNode.attributes[i].nodeName.toLowerCase() == "name" ||
                            srcNode.attributes[i].nodeName.toLowerCase() == "for") {

                            if(srcNode.attributes[i].nodeValue.indexOf(self.idSuffix_fieldHint) != -1)  {
                                //leave the field hint suffix at the end of the id.
                                var value = srcNode.attributes[i].nodeValue;
                                value= removeRepeatCountSuffix(value.substr(0,value.indexOf(self.idSuffix_fieldHint))) + idSuffix + self.idSuffix_fieldHint;
                            }
                            else {
                                if (srcNode.tagName.toUpperCase()=="INPUT" && srcNode.getAttribute('type',false).toLowerCase()=="radio" &&
                                    srcNode.attributes[i].nodeName.toLowerCase() == "name" && self.preserveRadioName) {
                                    var value = srcNode.attributes[i].nodeValue;
                                }
                                else {
                                    // var value = removeRepeatCountSuffix(srcNode.attributes[i].nodeValue) + idSuffix;
                                    var value = srcNode.attributes[i].nodeValue + idSuffix;
                                }
                            }
                        } else {
                            // Do not copy the value attribute for text/password/file input
                            if(srcNode.attributes[i].nodeName.toLowerCase() == "value" &&
                                srcNode.tagName.toUpperCase()=='INPUT' &&
                                  (srcNode.type.toLowerCase() == 'text' || srcNode.type.toLowerCase() == 'password' || srcNode.type.toLowerCase() == 'file'))
                                var value='';
                            else
                                var value = srcNode.attributes[i].nodeValue;
                        }
                        // Create attribute and assign value
                        switch(srcNode.attributes[i].nodeName.toLowerCase()) {
                            case "class":
                                newNode.className = value;
                                break;
                            case "style": // inline style attribute (fix for IE)
                                if(srcNode.style && srcNode.style.cssText) newNode.style.cssText = srcNode.style.cssText;
                                break;
                            case "onclick": // inline event handler (fix for IE)
                                newNode.onclick = srcNode.onclick;
                                break;
                            case "onchange":
                                newNode.onchange = srcNode.onchange;
                                break;
                            case "onsubmit":
                                newNode.onsubmit = srcNode.onsubmit;
                                break;
                            case "onmouseover":
                                newNode.onmouseover = srcNode.onmouseover;
                                break;
                            case "onmouseout":
                                newNode.onmouseout = srcNode.onmouseout;
                                break;
                            case "onmousedown":
                                newNode.onmousedown = srcNode.onmousedown;
                                break;
                            case "onmouseup":
                                newNode.onmouseup = srcNode.onmouseup;
                                break;
                            case "ondblclick":
                                newNode.ondblclick = srcNode.ondblclick;
                                break;
                            case "onkeydown":
                                newNode.onkeydown = srcNode.onkeydown;
                                break;
                            case "onkeyup":
                                newNode.onkeyup = srcNode.onkeyup;
                                break;
                            case "onblur":
                                newNode.onblur = srcNode.onblur;
                                break;
                            case "onfocus":
                                newNode.onfocus = srcNode.onfocus;
                                break;
                            default:
                                newNode.setAttribute(srcNode.attributes[i].name, value, 0);//setAttribute(newNode, srcNode.attributes[i].name, value);
                        }
                    }
                }
                break;
            case 3: // TEXT-NODE (do not copy value of textareas)
                if(srcNode.parentNode.tagName.toUpperCase() != 'TEXTAREA')
                    var newNode = document.createTextNode(srcNode.data);
                break;
        }
        if(dupParentNode && newNode) dupParentNode.appendChild(newNode);
        for(var i=0; i<srcNode.childNodes.length;i++) {
            replicateTree(srcNode.childNodes[i],newNode,idSuffix);
        }
        return newNode;
    }

    // =======================================================================================
    // Field Hint Behavior Methods
    // =======================================================================================

    this.activateFieldHint = function(e) {
        var srcE = wu.getSrcElement(e);
        var fh = document.getElementById(srcE.id +  self.idSuffix_fieldHint);
        if(!fh && srcE.tagName.toUpperCase()=='INPUT' && srcE.type.toLowerCase() == 'radio') {
            fh = document.getElementById(srcE.name + self.idSuffix_fieldHint);
        }
        if(fh) fh.className = fh.className.replace(self.className_inactiveFieldHint, self.className_activeFieldHint);
    }
    this.desactivateFieldHint = function(e) {
        var srcE = wu.getSrcElement(e);
        var fh = document.getElementById(srcE.id +  self.idSuffix_fieldHint);
        if(!fh && srcE.tagName.toUpperCase()=='INPUT' && srcE.type.toLowerCase() == 'radio') {
            fh = document.getElementById(srcE.name + self.idSuffix_fieldHint);
        }
        if(fh) fh.className = fh.className.replace(self.className_activeFieldHint,self.className_inactiveFieldHint);
    }


    // =======================================================================================
    // Multi-page Forms Management
    // =======================================================================================
    this.pagingNext= function(e) {
        var srcE = wu.getSrcElement(e);
        var curPageDiv = srcE.parentNode;
        var pageIndex = parseInt(curPageDiv.id.replace(/[\D]*/,"")) + 1;
        var pageDiv = document.getElementById(self.idPrefix_pageIndex+pageIndex.toString());
        if(pageDiv) {
            if(eval(self.functionName_formValidation.replace("this","self"))(e)) {
                curPageDiv.className = curPageDiv.className.replace(self.className_pagingCurrent,"");
                pageDiv.className += ' ' + self.className_pagingCurrent;
                // show submit button if the last page of the form is reached
                pageIndex++;
                var anotherpage = document.getElementById(self.idPrefix_pageIndex+pageIndex.toString());
                if(!anotherpage) {
                    var submitButton = document.getElementById("tfa_submit");
                    if(submitButton) submitButton.className = submitButton.className.replace(self.className_hideSubmit,"");
                }
            }
        }
    }

    this.pagingPrevious = function(e) {
        var srcE = wu.getSrcElement(e);
        var curPageDiv = srcE.parentNode;
        var pageIndex = parseInt(curPageDiv.id.replace(/[\D]*/,"")) - 1;
        var pageDiv = document.getElementById(self.idPrefix_pageIndex+pageIndex.toString());
        if(pageDiv) {
            curPageDiv.className = curPageDiv.className.replace(self.className_pagingCurrent,"");
            pageDiv.className += ' ' + self.className_pagingCurrent;
            // hide submit button if necessary
            var submitButton = document.getElementById("submit-"+srcE.form.id);
            if(submitButton && submitButton.className.indexOf(self.className_hideSubmit)==-1) submitButton.className += ' ' + self.className_hideSubmit;
        }
    }

    // =======================================================================================
    // FORM VALIDATION
    // =======================================================================================
    this.formValidation = function(e) {

        var srcE = wu.getSrcElement(e);

        if(self.preventSubmissionOnEnter) {
            // prevent form submission if 'enter' key is pressed
            // (doesn't work in Opera. Further tests needed in IE and Safari)
            if(!e) e = window.event;
            if(srcE.type && srcE.type.toLowerCase()=='text')
                return wu.XBrowserPreventEventDefault(e); // works w/ Firefox.
        }

        while (srcE && srcE.tagName.toUpperCase() != 'FORM') {
            srcE = srcE.parentNode;
        }
        var x = wu.getElements(srcE); //srcE.elements;
        var nbTotalErrors = 0;
        var isVisible = false;

        for (var i=0;i<x.length;i++) {
            var nbErrors = 0;

            if ((' '+x[i].className+' ').indexOf(' '+self.className_required+' ') != -1) {
                if(wu.checkVisibility(x[i])) {
                    isVisible = true;
                    var v = true; // is Valid
                    switch(x[i].tagName.toUpperCase()) {
                        case "INPUT":
                            switch(x[i].getAttribute("type").toUpperCase()) {
                                case "CHECKBOX":
                                    v = x[i].checked;
                                    break;
                                case "RADIO":
                                    v = x[i].checked;
                                    break;
                                default:
                                    v = !self.isEmpty(x[i].value);
                            }
                            break;
                        case "SELECT":
                            v = !self.isEmpty(x[i].options[x[i].selectedIndex].value);
                            break;
                        case "TEXTAREA":
                            v = !self.isEmpty(x[i].value);
                            break;
                        case "FIELDSET":
                            v = checkOneRequired(x[i]);
                            break;
                        case "DIV":
                            v = checkOneRequired(x[i]);
                            break;
                        case "SPAN":
                            v = checkOneRequired(x[i]);
                            break;
                        case "TABLE":
                            v = checkOneRequired(x[i]);
                            break;
                        case "TR":
                            v = checkOneRequired(x[i]);
                            break;
                        case "TD":
                            v = checkOneRequired(x[i]);
                            break;
                    } // end switch
                    if(!v) {
                        // flag error
                        self.showError(x[i],self.arrErrorMsg[0]);
                        nbErrors++;
                    }  else { // remove required error flag if any.
                        var rErrClass = new RegExp(self.className_validationError_fld,"gi");
                        x[i].className = x[i].className.replace(rErrClass,"");
                        var fe = document.getElementById(x[i].id +  self.idSuffix_fieldError);
                        if(fe) fe.parentNode.removeChild(fe);
                    }
                } else { // not visible
                }
            } // end test=required

            // input validation
            if (x[i].className.indexOf(self.classNamePrefix_validation) != -1) {
                if(!isVisible) isVisible = wu.checkVisibility(x[i]);
                if(isVisible) {
                    var arrClasses = x[i].className.split(" ");
                    for (j=0;j<arrClasses.length;j++) {
                        switch(arrClasses[j]) {
                            case "validate-alpha":
                                if(!self.isAlpha(x[i].value)) {
                                    self.showError(x[i],self.arrErrorMsg[1]);
                                    nbErrors++;
                                }
                                break;
                            case "validate-alphanum":
                                if(!self.isAlphaNum(x[i].value)) {
                                    self.showError(x[i],self.arrErrorMsg[6]);
                                    nbErrors++;
                                }
                                break;
                            case "validate-date":
                                if(!self.isDate(x[i].value)) {
                                    self.showError(x[i],self.arrErrorMsg[7]);
                                    nbErrors++;
                                }
                                break;
                            case "validate-time":
                                /* NOT IMPLEMENTED */
                                break;
                            case "validate-email":
                                if(!self.isEmail(x[i].value)) {
                                    self.showError(x[i],self.arrErrorMsg[2]);
                                    nbErrors++;
                                }
                                break;
                            case "validate-integer":
                                if(!self.isInteger(x[i].value)) {
                                    self.showError(x[i],self.arrErrorMsg[3]);
                                    nbErrors++;
                                }
                                break;
                            case "validate-float":
                                if(!self.isFloat(x[i].value)) {
                                    self.showError(x[i],self.arrErrorMsg[4]);
                                    nbErrors++;
                                }
                                break;
                            case "validate-ippart":
                                if(!self.isIpPart(x[i].value)) {
                                    self.showError(x[i],self.arrErrorMsg[9]);
                                    nbErrors++;
                                }
                                break;
							case "validate-strongpassword": // NOT IMPLEMENTED
                                if(!self.isPassword(x[i].value)) {
                                    self.showError(x[i],self.arrErrorMsg[5]);
                                    nbErrors++;
                                }
                                break;
                        } // end switch
                    } // end for
                }  else { // not visible
                }
            } // end validation check

            if(nbErrors>0) {
                nbTotalErrors+= nbErrors;
            } else {
                var rErrClass = new RegExp(self.className_validationError_fld,"gi");
                x[i].className = x[i].className.replace(rErrClass,"");
                var fe = document.getElementById(x[i].id +  self.idSuffix_fieldError);
                if(fe) fe.parentNode.removeChild(fe);
            }
        }
        if (nbTotalErrors > 0) {
            if(self.showAlertOnError){  self.showAlert(nbTotalErrors); }
            return wu.XBrowserPreventEventDefault(e);
        }
        return true;
    }
    this.isEmpty = function(s) {
        var regexpWhitespace = /^\s+$/;
        return  ((s == null) || (s.length == 0) || regexpWhitespace.test(s));
    }
    this.isAlpha = function(s) {
        var regexpAlphabetic = /^[a-zA-Z]+$/; // Add ' and - ?
        return self.isEmpty(s) || regexpAlphabetic.test(s);
    }
    this.isAlphaNum = function(s) {
        var illegalChars = /\W/;
        return self.isEmpty(s) || !illegalChars.test(s);
    }
    this.isDate = function(s) {
        var testDate = new Date(s);
        return self.isEmpty(s) || !isNaN(testDate);
    }
    this.isEmail = function(s) {
        var regexpEmail = /\w{1,}[@][\w\-]{1,}([.]([\w\-]{1,})){1,3}$/;
        return self.isEmpty(s) || regexpEmail.test(s);
    }
    this.isInteger =function(s) {
        var regexp = /^[+]?\d+$/;
        return self.isEmpty(s) || regexp.test(s);
    }
    this.isFloat = function(s) {
        return self.isEmpty(s) || !isNaN(parseFloat(s));
    }
	this.isIpPart = function(s) {
		 var regexpIpPart = /^[0-9]{1,3}$/;
	     return self.isEmpty(s) || (regexpIpPart.test(s) && s<=255);
		 //(0|[1-9]{2}|1[0-9]{2}|2[0-4][0-9]|25[0,4])([.](0|[1-9]{2}|1[0-9]{2}|2[0-4][0-9]|25[0,5])){3}
    }
    // NOT IMPLEMENTED
    this.isPassword = function(s) {
        // Matches strong password : at least 1 upper case latter, one lower case letter. 4 characters minimum. 12 max.
        //var regexp = /^(?=.*[a-z])(?=.*[A-Z])(?!.*\s).{4,12}$/;  // <= breaks in IE5/Mac
        return self.isEmpty(s);
    }
    this.showError = function (n,errorMsg) {
        if(n.className.indexOf(self.className_validationError_fld)!= -1) {
            return;
        }
        if (!n.id) n.id = wu.randomId(); // we'll need an id here.
        // Add error flag to the field
        n.className += " " + self.className_validationError_fld;
        // Prepare error message
        var msgNode = document.createTextNode(" " + errorMsg);
        // Find error message placeholder.
        var fe = document.getElementById(n.id +  self.idSuffix_fieldError);
        if(!fe) { // create placeholder.
            fe = document.createElement("div");
            fe.setAttribute('id', n.id +  self.idSuffix_fieldError);
            // attach the error message after the field label if possible
            var fl = document.getElementById(n.id +  self.idSuffix_fieldLabel);
            if(fl)
                fl.parentNode.insertBefore(fe,fl.nextSibling);
            else
                // otherwise, attach it after the field tag.
                n.parentNode.insertBefore(fe,n.nextSibling);
        }
        // Finish the error message.
        fe.appendChild(msgNode);
        fe.className += " " + self.className_validationError_msg;
    }
    this.showAlert = function (nbTotalErrors) {
       alert(self.arrErrorMsg[8].replace('%%',nbTotalErrors));
    }
    // Validation Private Method
    // -------------------------
    function checkOneRequired(n) {
        var v=null;
        if(n.nodeType != 1) return false;
        if(n.tagName.toUpperCase() == "INPUT") {
            switch(n.type.toLowerCase()) {
                case "checkbox":
                    v = n.checked;
                    break;
                case "radio":
                    v = n.checked;
                    break;
                default:
                    v = n.value;
            }
        } else v = n.value;
        if(v && !self.isEmpty(v)) {
            return true;
        }
        for(var i=0; i<n.childNodes.length;i++) {
            if(checkOneRequired(n.childNodes[i])) return true;
        }
        return false;
    }

}

//------------------------------------------------------------------------------------------------------------------------
// WFORMS Public Methods
//------------------------------------------------------------------------------------------------------------------------
wFORMS.prototype.onLoadHandler = function() {
    for (var i=0;i<document.forms.length;i++) {
        if(!document.forms[i].id) document.forms[i].id = this.utilities.randomId();
        this.addBehaviors(document.forms[i].id);
    }
}
wFORMS.prototype.addBehaviors = function (fId) {
    var f=document.getElementById(fId);
    if(!f) return;

    var thisForm;               // Pointer to keep track of the current form being processed.
    var wu = this.utilities;    // Utiltiy class instance
    wu.resetEventList();

    // loop through the fields
    var x = wu.getElements(f);

    for (var i=0;i<x.length;i++) {

        // add form validation behavior
        if(x[i].tagName.toUpperCase()=="FORM") {
            wu.XBrowserAddHandler(x[i],'submit',eval(this.functionName_formValidation));
            thisForm = x[i];    // Pointer to keep track of the current form being processed.
        }
        // add fieldhint behavior
        var fh = document.getElementById(x[i].id + this.idSuffix_fieldHint);
        if(!fh && x[i].tagName.toUpperCase()=='INPUT' && x[i].type.toLowerCase() == 'radio') {
            fh = document.getElementById(x[i].name + this.idSuffix_fieldHint);
        }
        if(fh) {
            wu.XBrowserAddHandler(x[i],'focus',this.activateFieldHint);
            wu.XBrowserAddHandler(x[i],'blur',this.desactivateFieldHint);
        }
        // add switch/state behavior
        if (x[i].className && x[i].className.indexOf(this.classNamePrefix_switch) != -1) {

            switch(x[i].tagName.toUpperCase()) {
                case "OPTION":
                    var sel = x[i].parentNode;  // Get to the SELECT
                    if(sel.tagName.toUpperCase() == "OPTGROUP") sel = sel.parentNode; // try again.
                    if(!wu.isEventHandled(sel,'change')) {
                        wu.XBrowserAddHandler(sel,'change',this.refreshState);
                    }
                    break;
                case "INPUT":
                    if(x[i].type && x[i].type.toLowerCase() == 'radio') {
                        // Add the onclick event on radio inputs of the same group
                        if(!thisForm) thisForm = x[i].form;
                        for (var j=0;j<thisForm[x[i].name].length;j++) {
                            if(thisForm[x[i].name][j].type == 'radio') // prevents conflicts with elements with an id = name of radio group
                                wu.XBrowserAddHandler(thisForm[x[i].name][j],'click',this.refreshState);
                        }
                    } else {
                        wu.XBrowserAddHandler(x[i],'click',this.refreshState);
                    }
                    break;
                default:
                    wu.XBrowserAddHandler(x[i],'click',this.refreshState);
                    break;
            }
        }

        // add paging behavior
        if (x[i].className && (' '+x[i].className+' ').indexOf(' '+ this.className_paging+' ') != -1) {
            var currentPageIndex = parseInt(x[i].id.replace(/[\D]*/,""));
            if(currentPageIndex > 1) {
                // add previous page button
                var actionNode = document.createElement("input");
                actionNode.setAttribute('value',this.arrMsg[5]);
                actionNode.setAttribute('type',"button");
                actionNode.className = this.className_pagingButtons;
                x[i].appendChild(actionNode);
                // Add event handler
                wu.XBrowserAddHandler(actionNode,'click',this.pagingPrevious);
            } else {
                // set current page class
                x[i].className += ' ' + this.className_pagingCurrent;
                // hide submit button until the last page of the form is reached
                if(!thisForm) {
                    thisForm = x[i].parentNode;
                    while(thisForm && thisForm.tagName.toUpperCase() != "FORM")
                        thisForm = thisForm.parentNode;
                }
                var submitButton = document.getElementById("tfa_submit");
                if(submitButton) submitButton.className = this.className_hideSubmit;
                // prevent submission of form with enter key.
                this.preventSubmissionOnEnter = true;
            }
            if(document.getElementById(this.idPrefix_pageIndex+(currentPageIndex+1).toString())) {
                // add next page button
                var actionNode = document.createElement("input");
                actionNode.setAttribute('value',this.arrMsg[4]);
                actionNode.setAttribute('type',"button");
                actionNode.className = this.className_pagingButtons;
                x[i].appendChild(actionNode);
                // Add event handler
                wu.XBrowserAddHandler(actionNode,'click',this.pagingNext);
            }
        }

        // add repeat behavior
        if (x[i].className && (' '+x[i].className+' ').indexOf(' '+this.className_repeat+' ') != -1) {
            // this element to be duplicated.
            wu.debug('adding repeat on ' + x[i].id);
            var actionNode = null;
            if(x[i].id) actionNode = document.getElementById(x[i].id + this.idSuffix_duplicateLink);
            if (!actionNode) {
                // add duplicate action
                actionNode = document.createElement("a");
                var spanNode = document.createElement("span");  // For CSS image replacement
                var textNode = document.createTextNode(this.arrMsg[0]);
                actionNode.setAttribute('href',"#");
                actionNode.className = this.className_duplicateLink;
                actionNode.setAttribute('title', this.arrMsg[1]);
                if(x[i].tagName.toUpperCase()=="TR") {
                    // find the last TD
                    var n = x[i].lastChild;
                    while(n && n.nodeType != 1)
                        n = n.previousSibling;
                    if(n && n.nodeType == 1)
                        n.appendChild(actionNode);
                    // Else Couldn't find the TD. Table row malformed ?
                } else
                    x[i].appendChild(actionNode);

                spanNode.appendChild(textNode);
                actionNode.appendChild(spanNode);
            }
            // Add hidden counter field if necessary
            var counterField = document.getElementById(x[i].id + this.idSuffix_repeatCounter);
            if (!counterField) {
                if(document.all && !window.opera) { // IE Specific :-(
                    // see http://msdn.microsoft.com/workshop/author/dhtml/reference/properties/name_2.asp
                    var counterFieldId = x[i].id + this.idSuffix_repeatCounter;
                    if(navigator.appVersion.indexOf("MSIE") != -1 && navigator.appVersion.indexOf("Windows") == -1) // IE5 Mac
                        counterField = document.createElement("INPUT NAME=\"" + counterFieldId + "\"");
                    else
                        counterField = document.createElement("<INPUT NAME=\"" + counterFieldId + "\"></INPUT>");
                    counterField.type='hidden';
                    counterField.id = counterFieldId;
                    counterField.value = "1";
                }
                else {
                    counterField = document.createElement("INPUT");
                    counterField.setAttribute('type','hidden'); // hidden
                    counterField.setAttribute('value','1');
                    counterField.setAttribute('name', x[i].id + this.idSuffix_repeatCounter);
                    counterField.setAttribute('id', x[i].id + this.idSuffix_repeatCounter);
                }

                if(!thisForm) {
                    thisForm = x[i].parentNode;
                    while(thisForm && thisForm.tagName.toUpperCase() != "FORM")
                        thisForm = thisForm.parentNode;
                }

                thisForm.appendChild(counterField);
            }
            // Add event handler
            wu.XBrowserAddHandler(actionNode,'click',this.duplicateFieldGroup);
        }
        // add remove behavior
        if (x[i].className && (' '+x[i].className+' ').indexOf(' '+this.className_delete+' ') != -1) {
            wu.debug('adding remove on ' + x[i].id);
            // this element can be removed
            // add remove action
            var actionNode = document.createElement("a");
            var spanNode = document.createElement("span");  // For CSS image replacement
            var textNode = document.createTextNode(this.arrMsg[2]);
            actionNode.setAttribute('href',"#");
            actionNode.className = this.className_removeLink;
            actionNode.setAttribute('title',this.arrMsg[3]);
            if(x[i].tagName.toUpperCase()=="TR") {
                // find the last TD
                var n = x[i].lastChild;
                while(n && n.nodeType != 1)
                    n = n.previousSibling;
                if(n && n.nodeType == 1)
                    n.appendChild(actionNode);
                // Else Couldn't find the TD. Table row malformed ?
            } else
                x[i].appendChild(actionNode);
            spanNode.appendChild(textNode);
            actionNode.appendChild(spanNode);
            wu.XBrowserAddHandler(actionNode,'click',this.removeFieldGroup);
        }
     }
     this.refreshAllStates(fId);
}


// *************************************************************************************************************
// UTILITY CLASS
// *************************************************************************************************************
function wUTILITY() {
    // Event Handler utility list
    this.handlerList = new Array();
}

// Cross-Browser event handler management.
// adapted from Andy Smith's (http://weblogs.asp.net/asmith/archive/2003/10/06/30744.aspx)
wUTILITY.prototype.XBrowserAddHandler = function (target,eventName,handlerName) {
    if(!target) return;
    if (target.addEventListener) {
        target.addEventListener(eventName, function(e){eval(handlerName)(e);}, false);
    } else if (target.attachEvent) {
        target.attachEvent("on" + eventName, function(e){eval(handlerName)(e);});
        } else {
        // THIS CODE NOT TESTED
        var originalHandler = target["on" + eventName];
        if (originalHandler) {
          target["on" + eventName] = function(e){originalHandler(e);eval(handlerName)(e);};
        } else {
          target["on" + eventName] = eval(handlerName);
        }
    }
    // Keep track of added handlers.
    var l = this.handlerList.length;
    this.handlerList[l] = new Array(2);
    this.handlerList[l][0] = target.id;
    this.handlerList[l][1] = eventName;
    if(target.id)
        this.debug("Handler added :" + target.id + ' ' + eventName);
    else
        this.debug("Handler added :" + eventName);
}
//
wUTILITY.prototype.isEventHandled = function(n, type) {
    for(var i=0; i < this.handlerList.length; i++) {
        if(this.handlerList[i][0]==n.id && this.handlerList[i][1]==type)
            return true;
    }
    return false;
}
wUTILITY.prototype.resetEventList = function() {
    this.handlerList = new Array();
}

// Activating an Alternate Stylesheet (thx to: http://www.howtocreate.co.uk/tutorials/index.php?tut=0&part=27)
// Use this to activate a CSS Stylesheet that shouldn't be used if javascript is turned off.
// The stylesheet rel attribute should be 'alternate stylesheet'. The title attribute should be set.
wUTILITY.prototype.activateStylesheet = function(sheetref) {
    if(document.getElementsByTagName) {
        var ss=document.getElementsByTagName('link');
    } else if (document.styleSheets) {
        var ss = document.styleSheets;
    }
    for(var i=0;ss[i];i++ ) {
        if(ss[i].href.indexOf(sheetref) != -1) {
            ss[i].disabled = true;
            ss[i].disabled = false;
        }
    }
}
// Generates a random ID
wUTILITY.prototype.randomId = function () {
    var rId = "";
    for (var i=0; i<6;i++)
        rId += String.fromCharCode(97 + Math.floor((Math.random()*24)))
    return rId;
}
// returns all child elements of a node.
wUTILITY.prototype.getElements = function(n, list) {
    if(!list) list = new Array();
    if(n.nodeType==1) {
        list[list.length]= n;
        for(var i=0; i<n.childNodes.length;i++)
            this.getElements(n.childNodes[i], list);
        return list;
    }
}
// Returns the event's source element
wUTILITY.prototype.getSrcElement = function(e) {
    if(!e)
        e = window.event;
    if(e.target)
        var srcE = e.target;
    else
        var srcE = e.srcElement;
    if(srcE.nodeType == 3) srcE = srcE.parentNode; // safari weirdness
    if(srcE.tagName.toUpperCase()=='LABEL') {
        // when clicking a label, firefox fires the input onclick event
        // but the label remains the source of the event. In Opera and IE
        // the source of the event is the input element. Which is the
        // expected behavior, I suppose.
        if(srcE.getAttribute('for')) {
            srcE = document.getElementById(srcE.getAttribute('for'));
        }
    }
    return srcE;
}
// Cancel the default execution of an event.
wUTILITY.prototype.XBrowserPreventEventDefault = function(e) {
    if(!e) e = window.event;
    if (e.preventDefault) e.preventDefault();
    else e.returnValue = false;
    return false;
}
wUTILITY.prototype.checkVisibility = function(n) {
    // check if any of the element's ancestors is not visible.
    if(window.getComputedStyle) {
        var isVisible = window.getComputedStyle(n,"").getPropertyValue("display").toLowerCase()!="none";
        isVisible = isVisible && window.getComputedStyle(n,"").getPropertyValue("visibility").toLowerCase()!="hidden";
        // add visiblity!=collapse ?
    }
    else if(n.currentStyle) {
        if(n.currentStyle.display=='') return false; // effectively disable validation on IE5/Mac.
        var isVisible = n.currentStyle.display.toLowerCase() != "none";
        isVisible = isVisible && n.currentStyle.visibility.toLowerCase() !="hidden";
    }
    else {
        return true;
        // use return false to disable validation if a switch or paging behavior is used.
    }
    if(!n.parentNode) { return false; } ; // should not happen, unless we're checking some removed elements.
    if (n.parentNode.tagName.toUpperCase()=="BODY" || !isVisible)
        return isVisible;
    return this.checkVisibility(n.parentNode);
}

wUTILITY.prototype.debug = function(text) {
    var debugOutput = document.getElementById('debugOutput');
    if(debugOutput) debugOutput.innerHTML = debugOutput.innerHTML+"<br />"+text;
}

// =======================================================================================================================
// LET's GO
var wf = new wFORMS();
// Attach JS only stylesheet.
wf.utilities.activateStylesheet('wforms-jsonly.css');
// onLoad event handler
wf.utilities.XBrowserAddHandler(window,'load',function() { wf.onLoadHandler();}  );

