// wForms - a javascript extension to web forms.
// v0.99.1 - May 26 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/

// Known Problems:
// Safari 1.x:  Validation Disabled. No way of knowing if a field is visible or not (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'.

// 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.functionName_formValidation = "this.formValidation";
this.showAlertOnError = true; // set to false to not show the alert when a validation error occurs.
this.preventSubmissionOnEnter = false; // prevent 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 a row"; // repeat link
this.arrMsg[1] = "Repeats the preceding field or field group." // title attribute on the repeat link 
this.arrMsg[2] = "Remove"; // remove link
this.arrMsg[3] = "Removes the preceding field or field group." // 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) {
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]);
break;
}
}
}
}

this.refreshState = function(e) {
if(!e && arguments.length==2)
var srcE = arguments[1];
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 {
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.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++) {

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;
}
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;
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("submit-"+srcE.form.id);
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;
} // 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-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));
}
// 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.getAttribute("value");
}
} else v = n.getAttribute("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("submit-"+thisForm.id);
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 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);
actionNode.appendChild(textNode); 
}
// 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 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);
actionNode.appendChild(textNode); 
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;  
this.debug("Handler added :" + target.id + ' ' + 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 false; // effectively disable validation for Safari
// try to focus or select...
// This code not finalized (doesn't work for Safari).
try {
if(n.focus) alert(n.focus());
if(n.select) alert(n.select());
}  
catch (e) {
// focus failed. Either not a field, or not visible.
alert("not a visible field. " + e);
return false;
}
return true; // focus ok. The field is definitively visible.
}
if(!n.parentNode) { return false; } ; // should not happen, unless we 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();}  );
