/*
 * Copyright (c) 1997-2009, Ron Lussier. All rights reserved.
 *
 * This software is an unpublished work subject to a confidentiality agreement
 * and protected by copyright and trade secret law. Unauthorized copying,
 * redistribution or other use of this work is prohibited. All copies must
 * retain this copyright notice. Any use or exploitation of this work without
 * authorization could subject the perpetrator to criminal and civil liability.
 *
 * The above copyright notice does not indicate actual or intended publication
 * of this source code.
 */

/*---------------------------------------------------------------------------------------------------------
 * This file contains code used by Earthenberm applications for input fields and forms.
 *
 * This file requires the following Earthenberm files to be included:
 *		eb-common.js
 *		eb-dom.js
 *---------------------------------------------------------------------------------------------------------*/

/* The following specifies JSLint options */
/*global EB, id */

if (typeof EB == "undefined") {
	/**
	 * The EARTHENBERM global namespace object.
	 */
	var EB = {};
	}

if (typeof EB.forms == "undefined") {
	/**
	 * The EARTHENBERM 'forms' namespace object.
	 */
	EB.forms = {};
	}


/*--------------------------------------------------------------------------------
 * Auto-grow for text area fields
 *--------------------------------------------------------------------------------*/

/**
 * Makes a text area field auto-grow, so that it grows to fit its contents.
 */
EB.forms.setAutoGrow = function( textarea ) {
	textarea = id(textarea);

	if ((textarea.nodeName != "TEXTAREA") && (textarea.nodeName != "textarea")) {
		alert(textarea.nodeName + ' is not a TEXTAREA node.');
		return;
	}

	if (typeof textarea.rows === 'number') {
		textarea.gdMinRows = textarea.rows;
	}
	
	var		shadowNode = document.createElement('div');

	if (shadowNode) {
		textarea.style.overflow = 'hidden';

		shadowNode.className = 'textareaShadow';

		shadowNode.style.position = 'absolute';
		shadowNode.style.zIndex = 10000;
		shadowNode.style.top = '-10000px';
		shadowNode.style.left = '-10000px';
		shadowNode.style.border = '1px dotted red';
		shadowNode.style.overflow = 'scroll';
		shadowNode.style.width = (textarea.offsetWidth + EB.dom.getScrollerWidth()) + 'px';
		shadowNode.style.height = textarea.offsetHeight + 'px';
		
		shadowNode.style.fontFamily = EB.dom.getStyle(textarea, 'fontFamily');
		shadowNode.style.fontSize = EB.dom.getStyle(textarea, 'fontSize');
		shadowNode.style.fontStyle = EB.dom.getStyle(textarea, 'fontStyle');
		shadowNode.style.fontWeight = EB.dom.getStyle(textarea, 'fontWeight');
		shadowNode.style.lineHeight = EB.dom.getStyle(textarea, 'lineHeight');
		
		shadowNode.innerHTML = textarea.value;

		textarea.shadowNode = shadowNode;
		document.body.insertBefore( shadowNode, null );

		/*
		 * Add a listener that waits for 1/3 second, then adjusts the size of the edit field.  The wait
		 * ensures that paste events occur before the size is recalculated.
		 */
		EB.dom.attachEventListener( textarea, "keydown",
									function(event) { setTimeout( function(event) { EB.effects._autoGrowResize(textarea); }, 10 ); } );

		EB.forms._autoGrowResize(textarea);
	}

	return;
}

/**
 * Handles key strokes for auto-grow text fields.
 */
EB.forms._autoGrowResize = function(textarea) {
	var		shadowNode = textarea.shadowNode;
	var		desiredHeight;
	var		lineHeight = 30;
	var		textContents = textarea.value;

	textContents = textContents.replace(/\r\n/g, '<br/>');
	textContents = textContents.replace(/\n/g, '<br/>');
	textContents = textContents.replace(/\r/g, '<br/>');

	shadowNode.innerHTML = textContents;

	if (EB.isIE()) {
		shadowNode.scrollHeight;		// for some reason completely unexplainable by me, this is required...
	}
	desiredHeight = (shadowNode.scrollHeight + lineHeight);

	textarea.style.height = desiredHeight + "px";

	return true;
}


/*--------------------------------------------------------------------------------
 * Construct a tag editor field
 *--------------------------------------------------------------------------------*/

EB.forms.tagsMaxTagLength = 50;

/*
 * 'constants' used as postfixes to the base tag editor ID for parts of the tag editor.
 */
EB.forms.tagsEditAreaPostfix = '_Edit';
EB.forms.tagsEditFieldPostfix = '_EditField';
EB.forms.tagsFieldValuePostfix = '_Value';
EB.forms.tagsHelpAreaPostfix = '_Help';

/*
 * 'constants' for class names used within the tag editor.
 */
EB.forms.classNameTagEditor = 'tagEditor';
EB.forms.classNameTagEditorEditArea = 'workingArea';
EB.forms.classNameTagEditorEditField = 'editField';
EB.forms.classNameTagEditorHelpArea = 'helpArea';
EB.forms.classNameTagEditorEnteredTag = 'enteredTag';
EB.forms.classNameTagEditorEnteredTagHilited = 'enteredTagHilited';
EB.forms.classNameTagEditorEnteredTagText = 'enteredTagText';	// text portion of an entered tag
EB.forms.classNameTagEditorEnteredTagClose = 'enteredTagClose';	// close box of an entered tag

/**
 * Initialize a div to be a tag input field.
 *
 * @param container			An empty div element that will be used as the container for the tag input field.
 * @param tagEditorId			The identifier to use for the tag input field.  This value is required.
 * @param name				The name to assign to the tag input field.  This is like the 'name' attribute for
 *							an <input> tag, and is required.
 * @param value				The initial value of the tag field, consisting of a list of tags separated by
 *							commas.  This parameter is optional.
 * @param suggestedTags		A list of suggested tags, separated by commas.  This parameter is optional.
 */
EB.forms.createTagField = function(containerId, tagEditorId, name, value, suggestedTags) {
	var		container = id(containerId);

	if (!container) {
		alert("EB.forms.addTagField called without valid container: '" + containerId + "'");
		return;
	}
	
	if (typeof tagEditorId !== 'string') {
		alert('EB.forms.addTagField called without valid tagEditorId.');
		return;
	}
	tagEditorId = tagEditorId.trim();

	if (tagEditorId.length <= 0) {
		alert('EB.forms.addTagField called with an empty tagEditorId.');
		return;
	}

	if (container.nodeName.toLowerCase() != 'div') {
		alert("tag field should be applied to DIV element.  You tried to apply it to a '" +
				container.nodeName.toLowerCase() + "' element.");
		return;
	}

	if (typeof name !== 'string') {
		alert('tag field name is required.');
		return;
	}
	name = name.trim();

	if (typeof value !== 'string') {
		value = '';
	}
	value = value.trim();
	
	if (typeof suggestedTags !== 'string') {
		suggestedTags = '';
	}
	suggestedTags = suggestedTags.trim();

	var		tagEditor = document.createElement('div');
	
	tagEditor.id = tagEditorId;
	tagEditor.className = EB.forms.classNameTagEditor;
	tagEditor.onclick = function(event) {
								/*
								 * A local closure to hold the tag that we want to delete.
								 */
								var		editorId = tagEditorId;
	
								function tagEditorClick(event) {
												EB.forms._onTagEditorClick(event, editorId);
											}

								tagEditorClick(event);
							};

	EB.forms._addTagEditAreaNode(tagEditorId, tagEditor, name, value);
	EB.forms._addTagHelpNode(tagEditorId, tagEditor, suggestedTags);
	
	container.appendChild(tagEditor);
}

/*
 * @param tags		a comma-delimited list of tags to be added.
 */
EB.forms.addTags = function(tagEditorId, tags) {
	var		tagEditorEditAreaNode = id(tagEditorId + EB.forms.tagsEditAreaPostfix);
	var		tagEditorValueNode = id(tagEditorId + EB.forms.tagsFieldValuePostfix);

	EB.forms._addTags(tagEditorId, tagEditorEditAreaNode, tagEditorValueNode, tags);
}

/*
 * @param tags		a comma-delimited list of tags to be removed.
 */
EB.forms.removeTags = function(tagEditorId, tags) {
	if ((typeof tagEditorId !== 'string') || (typeof tags !== 'string')) {
		return;		// Invalid tag editor ID or tag name
	}

	var		tagEditorEditAreaId = tagEditorId + EB.forms.tagsEditAreaPostfix;
	var		tagEditorEditArea = id(tagEditorEditAreaId);
	
	tags = tags.trim();
	if (tags.length <= 0) {
		return;		// empty tag name
	}

	var		enteredTagTextList = EB.dom.getElementsByClass(EB.forms.classNameTagEditorEnteredTagText,
															tagEditorEditArea, 'span');
	var		tagsToRemove = tags.split(',');

	/*
	 * Remove any tag nodes found.
	 */
	for (var removeIndex = 0; removeIndex < tagsToRemove.length; removeIndex++) {
		var		tagToRemove = tagsToRemove[removeIndex];
		
		tagToRemove = tagToRemove.trim();

		for (var loop = 0; loop < enteredTagTextList.length; loop++) {
			var		textNode = enteredTagTextList[loop];
			
			if (textNode.firstChild) {
				var		text = textNode.firstChild.nodeValue;
		
				if (text === tagToRemove) {
					// Found it!  Remove it from the list of children.
					var		enteredTagNode = textNode.parentNode;

					tagEditorEditArea.removeChild(enteredTagNode);
					enteredTagNode = null;
				}
			}
		}
	}

	/*
	 * Also remove these from the value list.
	 */
	EB.forms._removeTagValues(tagEditorId, tags);
}


/*
 * Tag editor internal functions.
 */

/**
 * [internal] Add new tags to the specified tag edit field.
 *
 * @param tagEditorEditArea		The field editor node.
 * @param tagEditorValueNode		The node representing the hidden input field containing the tag
 *									editor value.
 * @param tags						A tag or comma-separated list of tags to be added to this editor.
 */
EB.forms._addTags = function(tagEditorId, tagEditorEditArea, tagEditorValueNode, tags) {
	if (typeof tags !== 'string') {
		return;		// nothing to add.
	}
	
	var		existingValue = tagEditorValueNode.value;
	var		existingTags;

	if (existingValue) {
		existingValue = existingValue.trim();

		if (existingValue.length > 0) {
			existingTags = existingValue.split(',');
		}
	}

	if (!existingTags) {
		existingTags = new Array();
	}

	tags = tags.split(',');

	for (var loop = 0; loop < tags.length; loop += 1) {
		var		thisTag = tags[loop].trim();

		if (thisTag.length > 0) {

			if (!existingTags.contains(thisTag)) {
				/*
				 * Finally, add the tag!
				 */
				existingTags.push(thisTag);

				var		enteredTag = EB.forms._createEnteredTagNode(tagEditorId, thisTag);
				var		lastEditAreaChild = null;
	
				if (tagEditorEditArea.childNodes.length > 0) {
					lastEditAreaChild = tagEditorEditArea.childNodes[tagEditorEditArea.childNodes.length - 1];
				}
	
				tagEditorEditArea.insertBefore(enteredTag, lastEditAreaChild);
			}
		}
	}

	existingTags.sort();
	tagEditorValueNode.value = existingTags.join();
}

/**
 * [internal] Remove the specified tag from the value list of the tag editor.
 *
 * @param tag						The tag to be removed.  This may be a string or array of strings.
 */
EB.forms._removeTagValues = function(tagEditorId, tags) {
	var		tagEditor = id(tagEditorId);

	if (!tagEditor || !tags) {
		return;
	}

	if (typeof tags === 'string') {
		tags = tags.trim();

		if (tags.length > 0) {
			tags = tags.split(',');
		}
		else {
			tags = new Array();
		}	
	}

	if (!tags.length || (tags.length <= 0)) {
		return;		// Nothing to remove
	}

	var		valueNode = id(tagEditorId + EB.forms.tagsFieldValuePostfix);

	if (valueNode) {
		var		nodeList = valueNode.value.trim();
		var		newNodeList = new Array();
		
		if (nodeList.length > 0) {
			nodeList = nodeList.split(',');
		}
		else {
			nodeList = new Array();
		}

		for (var loop = 0; loop < nodeList.length; loop++) {
			var		currentTag = nodeList[loop];
			var		okayToKeep = true;

			for (var removeIndex = 0; (removeIndex < tags.length) && okayToKeep; removeIndex += 1) {
				var		tagToRemove = tags[removeIndex].toLowerCase();

				if (tagToRemove === currentTag) {
					okayToKeep = false;
				}
			}

			if (okayToKeep) {
				newNodeList.push(currentTag);
			}
		}

		if (newNodeList.length > 0) {
			valueNode.value = newNodeList.join();
		}
		else {
			valueNode.value = '';
		}
	}
}

/**
 * Initial construction of the tag editor 'edit area'.  This includes the editor node and the hidden
 * 'value' field.
 *
 * @param tagEditor		The tag editor top-level node to which this edit field will be added.
 * @param tagEditorId	The overall ID for the tag editor.
 * @param name			The name of the value field.
 * @param value			Initial tags to be added to the edit portion.
 */
EB.forms._addTagEditAreaNode = function(tagEditorId, tagEditor, name, value) {
	var		tagEditorEditArea = document.createElement('div');
	
	tagEditorEditArea.id = tagEditorId + EB.forms.tagsEditAreaPostfix;
	tagEditorEditArea.className = EB.forms.classNameTagEditorEditArea + ' clearfix';

	tagEditor.appendChild(tagEditorEditArea);

	var		tagEditorEditNode = document.createElement('input');

	tagEditorEditNode.id = tagEditorId + EB.forms.tagsEditFieldPostfix;
	tagEditorEditNode.name = name;
	tagEditorEditNode.type = 'text';
	tagEditorEditNode.maxLength = EB.forms.tagsMaxTagLength;
	tagEditorEditNode.className = EB.forms.classNameTagEditorEditField;
	tagEditorEditNode.onkeypress =
		tagEditorEditNode.onkeydown =
							function(event) {
								/*
								 * A local closure to hold the tag that we want to delete.
								 */
								var		editorId = tagEditorId;
	
								function tagEditorKeyPress(event) {
												return EB.forms._onTagEditorKeyPress(event, editorId);
											}

								return tagEditorKeyPress(event);
							};

	tagEditorEditNode.onblur =
							function(event) {
								/*
								 * A local closure to hold the tag that we want to delete.
								 */
								var		editorId = tagEditorId;
	
								function tagEditorKeyPress(event) {
												return EB.forms._onTagEditorBlur(event, editorId);
											}

								return tagEditorKeyPress(event);
							};

	tagEditorEditArea.appendChild(tagEditorEditNode);

	var		tagEditorValueNode = document.createElement('input');

	tagEditorValueNode.id = tagEditorId + EB.forms.tagsFieldValuePostfix;
	tagEditorValueNode.name = name;
	tagEditorValueNode.type = 'hidden';

	tagEditor.appendChild(tagEditorValueNode);

	EB.forms._addTags(tagEditorId, tagEditorEditArea, tagEditorValueNode, value);
}

/**
 * Initial construction of the tag editor 'help' portion.  This includes some help text and
 * tag suggestions.
 *
 * @param tagEditorId	The overall ID for the tag editor.
 * @param tagEditor		The tag editor top-level node to which this edit field will be added.
 * @param name			The name of the value field.
 * @param value			Initial tags to be added to the edit portion.
 */
EB.forms._addTagHelpNode = function(tagEditorId, tagEditor, suggestedTags) {
	var		tagEditorHelpNode = document.createElement('div');

	tagEditorHelpNode.id = tagEditorId + EB.forms.tagsHelpAreaPostfix;
	tagEditorHelpNode.className = EB.forms.classNameTagEditorHelpArea + ' minorText';

	if (!suggestedTags) {
		suggestedTags = new Array();
	}
	else if (typeof suggestedTags === 'string') {
		suggestedTags = suggestedTags.trim();
		suggestedTags = suggestedTags.split(',');
	}

	var		helpHTML = '';

	helpHTML += 'Separate each tag with a comma. ' +
				'(Each tag may be up to ' + EB.forms.tagsMaxTagLength + ' characters long.)';

	if (suggestedTags.length > 0) {
		helpHTML += '<br/>';
		helpHTML += '<strong>Suggested Tags:</strong> ';
		
		for (var loop = 0; loop < suggestedTags.length; loop++) {
			var		tag = suggestedTags[loop];

			if (loop > 0) {
				helpHTML += ", ";
			}

			var		escapedTag = tag.replace("'", "&#39;");
			var		onClickCode = 'EB.forms.addTags("' + tagEditorId + '", "' + escapedTag + '");';

			helpHTML += "<span class='keepTogether'>";
			helpHTML += "<a href='javascript:none();' onclick='" + onClickCode + "'>";
			helpHTML += tag;
			helpHTML += '</a>';
			helpHTML += '</span>';
		}
	}

	tagEditorHelpNode.innerHTML = helpHTML;

	tagEditor.appendChild(tagEditorHelpNode);
}

/*
 * Creates an 'entered tag' node in the tag editor.
 *
 * This node consists of the following code:
 *
 *    <div class='enteredTag'>
 *       <span class='textPart'>
 *          tag text
 *       </span>
 *       <span class='closeBox'></span>
 *    </div>
 */
EB.forms._createEnteredTagNode = function(tagEditorId, tag) {
	var		enteredTag = document.createElement('div');
	
	enteredTag.className = EB.forms.classNameTagEditorEnteredTag;

	var		textSpan = document.createElement('span');

	textSpan.innerHTML = tag;
	textSpan.className = EB.forms.classNameTagEditorEnteredTagText;

	var		closeSpan = document.createElement('span');

	closeSpan.className = EB.forms.classNameTagEditorEnteredTagClose;
	closeSpan.onmouseover = EB.forms._onCloseBoxMouseOver;
	closeSpan.onmouseout = EB.forms._onCloseBoxMouseOut;
	closeSpan.onclick = function(event) {
								/*
								 * A local closure to hold the tag that we want to delete.
								 */
								var		editorId = tagEditorId;
								var		thisTag = tag;
	
								function closeTag(event) {
												EB.forms._onCloseBoxClick(event, editorId, thisTag);
											}

								closeTag(event);
							};

	enteredTag.appendChild(textSpan);
	enteredTag.appendChild(closeSpan);
	
	return enteredTag;
}

/*
 * Tag editor event handlers.
 */

/*
 * Called when the mouse moves over the close box in a selected tag.
 */
EB.forms._onCloseBoxMouseOver = function(event) {
	event = EB.dom.fixEvent(event);
	
	if (event.target) {
		var		enteredTagNode = event.target.parentNode;
		
		if (enteredTagNode.className == EB.forms.classNameTagEditorEnteredTag) {
			enteredTagNode.className = EB.forms.classNameTagEditorEnteredTagHilited; 
		}
	}
}

/*
 * Called when the mouse moves over the close box in a selected tag.
 */
EB.forms._onCloseBoxMouseOut = function(event) {
	event = EB.dom.fixEvent(event);
	
	if (event.target) {
		var		enteredTagNode = event.target.parentNode;
		
		if (enteredTagNode.className == EB.forms.classNameTagEditorEnteredTagHilited) {
			enteredTagNode.className = EB.forms.classNameTagEditorEnteredTag;
		}
	}
}

/*
 * Called when the mouse moves over the close box in a selected tag.
 */
EB.forms._onCloseBoxClick = function(event, tagEditorId, tag) {
	/*
	 * Make sure that the tag isn't hilited.
	 */
	EB.forms._onCloseBoxMouseOut(event);

	EB.forms.removeTags(tagEditorId, tag)
}

/*
 * Called when the the mouse is clicked in the tag editor.  Used in several places, sets focus to edit
 * field for the tag editor.
 */
EB.forms._onTagEditorClick = function(event, tagEditorId) {
	var		editField = id(tagEditorId + EB.forms.tagsEditFieldPostfix);
	
	if (editField) {
		editField.focus();
	}
}

/*
 * Called when the the mouse is clicked in the tag editor.  Used in several places, sets focus to edit
 * field for the tag editor.
 */
EB.forms._onTagEditorBlur = function(event, tagEditorId) {
	var		editField = id(tagEditorId + EB.forms.tagsEditFieldPostfix);
	
	if (editField) {
		var		workingTag = editField.value.trim().toLowerCase();
		var		validNewTag = (workingTag.length > 0);
		
		if (validNewTag) {
			EB.forms.addTags(tagEditorId, workingTag);
			editField.value = '';
		}
	}
}

/*
 * Called when the user types into the tag editor.
 */
EB.forms._onTagEditorKeyPress = function(event, tagEditorId) {
	event = EB.dom.fixEvent(event);		// Key event object
	var		passThrough = true;
	var		editField = id(tagEditorId + EB.forms.tagsEditFieldPostfix);

	if (event && editField) {
		var		code = event.charCode || event.keyCode;		// What key was pressed
		var		addTag = false;

		var		workingTag = editField.value.trim().toLowerCase();
		var		validNewTag = (workingTag.length > 0);

		/*
		 * If a return key is pressed, add the item as a tag.
		 */
		if (event.keyCode === 13) {
			if (validNewTag) {
				addTag = true;
			}
		}

		/*
		 * If the key code is a control or alt key code, then ignore it.
		 */
		if (event.ctrlKey || event.altKey) {
			return true;	// Control or Alt was held down
		}

		var		keyChar = String.fromCharCode(code);
		switch (keyChar) {
			case ',':
				passThrough = false;
				
				if (validNewTag) {
					addTag = true;
				}
				break;

			case '"':
				passThrough = false;
				break;

			case '\t':
				if (validNewTag) {
					passThrough = false;
					addTag = true;
				}
				break;

			case '\n':
			case '\r':
				if (validNewTag) {
					addTag = true;
				}
				break;
		}
		
		
		if (addTag) {
			EB.forms.addTags(tagEditorId, workingTag);
			editField.value = '';
		}
	}

	return passThrough;
}

