// wForms - a javascript extension to web forms.
// see http://www.formassembly.com/wForms
// This software is licensed under the CC-GNU LGPL <http://creativecommons.org/licenses/LGPL/2.1/>

function WHELPERS() {}

// addEvent adapated from http://ejohn.org/projects/flexible-javascript-events/
// and  Andy Smith's (http://weblogs.asp.net/asmith/archive/2003/10/06/30744.aspx)
WHELPERS.prototype.addEvent = function(obj, type, fn) 
{
	if(!obj) { return; }
	
	if (obj.attachEvent) 
	{
		obj['e'+type+fn] = fn;
		obj[type+fn] = function(){obj['e'+type+fn]( window.event );};
		obj.attachEvent( 'on'+type, obj[type+fn] );
	}
	else if(obj.addEventListener) 
	{			
		obj.addEventListener( type, fn, false );
	} 
	else 
	{
		var originalHandler = obj["on" + type]; 
		if (originalHandler) 
		{ 
			obj["on" + type] = function(e){originalHandler(e); fn(e);}; 
		} 
		else 
		{ 
		  obj["on" + type] = fn; 
		} 
	}
};

WHELPERS.prototype.removeEvent = function(obj, type, fn) 
{
	if (obj.detachEvent) 
	{
		if(obj[type+fn]) 
		{
			obj.detachEvent( 'on'+type, obj[type+fn] );
			obj[type+fn] = null;
		}
	} 
	else if(obj.removeEventListener)
	{
		obj.removeEventListener( type, fn, false );
	}
	else 
	{
		obj["on" + type] = null;
	}
};

// Returns the event's source element 
WHELPERS.prototype.getSourceElement = function(e) 
{	
    var srcE;

	if(!e) { e = window.event; }	
	
	if(e.target){ srcE = e.target; }
	else{ srcE = e.srcElement; }
	
	if(!srcE) { return null; }
	if(srcE.nodeType === 3) { srcE = srcE.parentNode; } // safari weirdness 		
	if(srcE.tagName.toUpperCase()==='LABEL' && e.type ==='click') 
	{ 
		// 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.
WHELPERS.prototype.preventEvent = function(e) 
{
	if (!e) { e = window.event; }
	
	if (e.preventDefault) {e.preventDefault();}
	else {e.returnValue = false;}
	
	return false;
};

// Cancel the propagation of the event
WHELPERS.prototype.stopPropagation = function(e) 
{
	if (!e) { e = window.event; }
	e.cancelBubble = true;
	if (e.stopPropagation) { e.stopPropagation(); }
};

// Generates a random ID
WHELPERS.prototype.randomId = function () 
{
	var seed = (new Date()).getTime();
	seed = seed.toString().substr(6);
	for (var i=0; i<6; i++)
	{	
		seed += String.fromCharCode(48 + Math.floor((Math.random()*10)));
	}
	return "id-" + seed;
};


WHELPERS.prototype.hasClass = function(element,className) 
{
	if(element && element.className) 
	{
		if((' ' + element.className + ' ').indexOf(' ' + className +' ') !== -1) 
		{
			return true;
		}
	}
	return false;
};


var wFORMS = 
{ 
	helpers  : new WHELPERS(),   
	
	className_repeat 			: "repeat",
	className_delete 			: "removeable",
	className_duplicateLink 	: "duplicateLink",
	className_removeLink 		: "removeLink",
	idSuffix_repeatCounter		: "-RC",
	idSuffix_duplicateLink		: "-wfDL",									 
		
	arrMsg 	: ["Add another ", "Will duplicate this section.", "Remove", "Will remove this section." ],
			
	evaluate: function(node) 
	{
		var n;

		// Repeatable element
		if(wFORMS.helpers.hasClass(node, wFORMS.className_repeat)) 
		{
			if(!node.id) { node.id = wFORMS.helpers.randomId(); }

			// Check if we have a 'repeat' link
			var repeatLink = document.getElementById(node.id + wFORMS.idSuffix_duplicateLink);
			if(!repeatLink) 
			{				
				// create the repeat link
				repeatLink = wFORMS.createRepeatLink(node.id);

				// find where to insert the link
				if(node.tagName.toUpperCase()==="TR") 
				{
					// find the last TD
					n = node.lastChild;	
					while(n && n.nodeType !== 1)  { n = n.previousSibling; }
					if(n && n.nodeType === 1) { n.appendChild(repeatLink); } 
					// Else Couldn't find the TD. Table row malformed ?
				} 
				else 
				{
					node.appendChild(repeatLink);
				}
			}

			// Add hidden counter field if necessary
			var counterField = document.getElementById(node.id + wFORMS.idSuffix_repeatCounter);
			if(!counterField) 
			{
				// IE Specific
				if(document.all && !window.opera) 
				{ 
					// see http://msdn.microsoft.com/workshop/author/dhtml/reference/properties/name_2.asp
					var counterFieldId = node.id + wFORMS.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', node.id + wFORMS.idSuffix_repeatCounter);
					counterField.setAttribute('id', node.id + wFORMS.idSuffix_repeatCounter); 
				}

				// get the form element						
				var form = node.parentNode;
				while(form && form.tagName.toUpperCase() !== "FORM") { form = form.parentNode; }
				form.appendChild(counterField);
			}

			// Add event handler			
			wFORMS.helpers.addEvent(repeatLink,'click',wFORMS.duplicateFieldGroup);			
		}	
		// ------------------------------------------------------------------------------------------
		// Removeable element
		if(wFORMS.helpers.hasClass(node, wFORMS.className_delete)) 
		{
			var removeLink = wFORMS.createRemoveLink();
			// find where to insert the link
			if(node.tagName.toUpperCase()==="TR") 
			{
				// find the last TD
				n = node.lastChild;	
				while(n && n.nodeType !== 1)  { n = n.previousSibling; }
				if(n && n.nodeType === 1) { n.appendChild(removeLink); }
				// Else Couldn't find the TD. Table row malformed ?
			} 
			else
			{				
				node.appendChild(removeLink);
			}
		}	
	},

	createRepeatLink: function(id) 
	{
		var repeatLink = document.createElement("a"); 
		var spanNode = document.createElement("span");   
		var textNode = document.createTextNode(wFORMS.arrMsg[0]+ id);
		repeatLink.id = id + wFORMS.idSuffix_duplicateLink;	
		repeatLink.setAttribute('href',"#");	
		repeatLink.className = wFORMS.className_duplicateLink;			
		repeatLink.setAttribute('title', wFORMS.arrMsg[1]);	
		spanNode.appendChild(textNode); 
		repeatLink.appendChild(spanNode); 
		return repeatLink;
	},

	createRemoveLink: function() 
	{
		var removeLink = document.createElement("a");
		var spanNode   = document.createElement("span");   
		var textNode   = document.createTextNode(wFORMS.arrMsg[2]);
		removeLink.setAttribute('href',"#");	
		removeLink.className = wFORMS.className_removeLink;
		removeLink.setAttribute('title',wFORMS.arrMsg[3]);	
		spanNode.appendChild(textNode); 
		removeLink.appendChild(spanNode);
		wFORMS.helpers.addEvent(removeLink,'click',wFORMS.removeFieldGroup);
		return removeLink;
	},

    // Event handler: repeatLink event
	duplicateFieldGroup: function(e) 
	{
		var element  = wFORMS.helpers.getSourceElement(e);
		if(!element) { element = e; }

		// Get Element to duplicate.				
		while (element && !wFORMS.helpers.hasClass(element,wFORMS.className_duplicateLink)) 
		{
			element = element.parentNode;
		}	
		var idOfRepeatedSection = element.id.replace(wFORMS.idSuffix_duplicateLink,"");
		element = document.getElementById(idOfRepeatedSection); 

		if (element) 
		{
			// Extract row counter information
			var counterField = document.getElementById(element.id + wFORMS.idSuffix_repeatCounter);
			if(!counterField) { return; } // should not happen.
			var rowCount = parseInt(counterField.value, 10) + 1;
			// Prepare id suffix
			var suffix = "-" + rowCount.toString();
			// duplicate node tree 
			var dupTree = wFORMS.replicateTree(element, null, suffix);   
			// find insert point in DOM tree (after existing repeated element)
			var insertNode = element.nextSibling;

			while(insertNode && 
				 (insertNode.nodeType===3 ||       // skip text-node that can be generated server-side when populating a previously repeated group 
				  wFORMS.helpers.hasClass(insertNode,wFORMS.className_delete))) 
			{						
				insertNode = insertNode.nextSibling;
			}
			element.parentNode.insertBefore(dupTree,insertNode);	 // Buggy rendering in IE5/Mac

			// the copy is not duplicable, it's removeable
			dupTree.className = element.className.replace(wFORMS.className_repeat,wFORMS.className_delete);
			// Save new row count 			
			document.getElementById(element.id + wFORMS.idSuffix_repeatCounter).value = rowCount;
			// re-add wFORMS behaviors
			wFORMS.addBehaviors(dupTree); 
		}				
		return wFORMS.helpers.preventEvent(e);
	},

	// Event handler: removeLink event
	removeFieldGroup: function(e) 
	{ 
		var element  = wFORMS.helpers.getSourceElement(e);
		if(!element) { element = e; }
		// Get Element to remove.
		element = element.parentNode;
		while (element && !wFORMS.helpers.hasClass(element,wFORMS.className_delete)) 
		{
			element = element.parentNode;
		}	
		// --- need to remove event handler from node before removing node (GS)
		element.parentNode.removeChild(element);

		// --- what about canceling event? (GS)
		return wFORMS.helpers.preventEvent(e);
	},	

	removeRepeatCountSuffix: function(str) 
	{
		return str.replace(/-\d+$/,'');
	},

	replicateTree: function(element,parentElement, idSuffix)  
	{
		var newElement, value, i;

		// Duplicating TEXT-NODE (do not copy value of textareas)
		if(element.nodeType===3) 
		{ 
			if(element.parentNode.tagName.toUpperCase() !== 'TEXTAREA')
			{
				newElement = document.createTextNode(element.data); 
			}
		} 
		// Duplicating ELEMENT-NODE
		else if(element.nodeType===1) 
		{ 
			// Do not copy repeat/remove links
			if(wFORMS.helpers.hasClass(element,wFORMS.className_duplicateLink) ||
			   wFORMS.helpers.hasClass(element,wFORMS.className_removeLink)) 							
			{	return null; }
			// Exclude duplicated elements of a nested repeat group
			if(wFORMS.helpers.hasClass(element,wFORMS.className_delete)) 
			{	return null; }
			// Adjust row suffix id if we find a nested repeat group 
			if(wFORMS.helpers.hasClass(element,wFORMS.className_repeat) && parentElement!==null)
			{	idSuffix = idSuffix.replace('-','__'); }

			if(!document.all || window.opera) 
			{ 
				// Common Branch
				newElement = document.createElement(element.tagName); 
			} 
			else 
			{
				// IE Branch 
				// see http://msdn.microsoft.com/workshop/author/dhtml/reference/properties/name_2.asp						
				var tagHtml = element.tagName;

				if(element.name)
				{ 					
					tagHtml += " NAME='" + 
						wFORMS.removeRepeatCountSuffix(element.name) +
						idSuffix + "' "; 
				}
				if(element.type) { tagHtml += " TYPE='" + element.type + "' ";	}
				//if(element.selected) { tagHtml += " SELECTED='SELECTED' "; }    !!! GS COMMENTED OUT !!! to drop-down selection not initialized
				//if(element.checked) { tagHtml += " CHECKED='CHECKED' "; }       !!! GS COMMENTED OUT !!! to keep radio buttons not initialized

				if(navigator.appVersion.indexOf("MSIE") !== -1 && navigator.appVersion.indexOf("Windows") === -1) // IE5 Mac
				{
					newElement = document.createElement(tagHtml);
				}
				else
				{
					newElement = document.createElement("<" + tagHtml + "></"+ element.tagName + ">"); 
				}
				try { newElement.type = element.type; } catch(e) {} // nail it down for IE5 ?, breaks in IE6
			}

			// duplicate attributes										
			for(i=0; i < element.attributes.length; i++) 
			{
				var attribute = element.attributes[i];
				// Get attribute value. 
				if(	attribute.specified || // in IE, the attributes array contains all attributes in the DTD
					attribute.nodeName.toLowerCase() === 'value' ) 
				{ // attr.specified buggy in IE?  
					// Add the row suffix if necessary.
					if(	attribute.nodeName.toLowerCase() === "id" || 
						attribute.nodeName.toLowerCase() === "name" ||
						attribute.nodeName.toLowerCase() === "for") 
					{
						value = attribute.nodeValue + idSuffix;
					} 
					else 
					{
						// Do not copy the value attribute for text/password/file input
						if(attribute.nodeName.toLowerCase() === "value" &&
						   element.tagName.toUpperCase()==='INPUT'      &&  
						  (element.type.toLowerCase() === 'text'     || 
						   element.type.toLowerCase() === 'password' || 
						   element.type.toLowerCase() === 'hidden' ||
						   element.type.toLowerCase() === 'file')) 
						{
							value=''; 
						}   
						// Do not copy the switch behavior's 'event handled' flag, stored in the rel attribute
						else if(attribute.nodeName.toLowerCase() === "rel" && 
								attribute.nodeValue.indexOf('wfHandled') !== -1) 
						{
							value = attribute.nodeValue.replace('wfHandled','');
						} 
						else 
						{
							value = attribute.nodeValue;
						}
					}
					// Create attribute and assign value
					switch(attribute.nodeName.toLowerCase()) 
					{
						case "class":
							newElement.className = value; 
							break;
						case "style": // inline style attribute (fix for IE)
							if(element.style && element.style.cssText) 
							{
								newElement.style.cssText = element.style.cssText;
							} 
							break;								
						case "onclick": // inline event handler (fix for IE)
							newElement.onclick     = element.onclick;							
							break;							
						case "onchange":							
							newElement.onchange    = element.onchange;							
							break;							
						case "onsubmit":
							newElement.onsubmit    = element.onsubmit;							
							break;							
						case "onmouseover":							
							newElement.onmouseover = element.onmouseover;							
							break;							
						case "onmouseout":							
							newElement.onmouseout  = element.onmouseout;							
							break;							
						case "onmousedown":
							newElement.onmousedown = element.onmousedown;							
							break;							
						case "onmouseup":
							newElement.onmouseup   = element.onmouseup;							
							break;							
						case "ondblclick":
							newElement.ondblclick  = element.ondblclick;							
							break;							
						case "onkeydown":
							newElement.onkeydown   = element.onkeydown;							
							break;							
						case "onkeyup":
							newElement.onkeyup     = element.onkeyup;							
							break;							
						case "onblur": 
							newElement.onblur      = element.onblur;							
							break;							
						case "onfocus":
							newElement.onfocus     = element.onfocus;							
							break;
						default:
						    try
						    {
							  newElement.setAttribute(attribute.name, value);  
							}
							catch(err)
							{
							   if (value === "radio" && attribute.name === "type")
							   {	
							   		newElement.type = "radio";
							   }
							   else
							   {
							   		throw new Error("undefined!");
							   }
							}
					}
				}
			}				
		}
		if(parentElement && newElement) { parentElement.appendChild(newElement); }
		for(i=0; i<element.childNodes.length; i++) 
		{
			wFORMS.replicateTree(element.childNodes[i], newElement, idSuffix); 
		}
		return newElement;
	},
   
	addBehaviors : function (node) 
	{
		// Process element nodes only
		if(node.nodeType === 1) 
		{ 
			wFORMS.evaluate(node);

			for (var i=0, l=node.childNodes.length, cn=node.childNodes; i<l; i++) 
			{
				if(cn[i].nodeType === 1){ wFORMS.addBehaviors(cn[i]); }
			}
		}
	},
    
	onLoadHandler  : function() 
	{
		// Parse document and apply wForms behavior
	    wFORMS.addBehaviors(document.forms[0]); 
	}
	
	
};
    
// Initialization 
wFORMS.helpers.addEvent(window,'load', wFORMS.onLoadHandler);
