/***************************************************************************

js/form.js

Commonly used data and functions for forms.

***************************************************************************/

/*
	Form element object and functions.
*/

function formArea(id) {
	// Members.
	this.id			= id;
	this.inpIDs		= new Array();	// See inputItem object.
	this.btnsArea	= document.getElementById(id + "ButtonsArea");
	this.hasPreview	= false; // Set to true if preview area for this form loaded.

	// Methods.
	this.resetForm	= resetForm;
	this.sendIt		= sendIt;
	this.enable		= enableForm;
	this.showResult	= showResult;

	// Submission result message.
	this.result					= document.createElement("div");
	this.messageHead			= document.createElement("div");
	this.messageBody			= document.createElement("div");
	this.result.style.display	= "none";	
	this.messageHead.id			= "sub_result_head";
	this.messageBody.id			= "sub_result_body";

	// Placed before form area.
	document.getElementById(id + "Form").insertBefore(this.result, document.getElementById(id + "FormArea"));
	this.result.appendChild(this.messageHead);
	this.result.appendChild(this.messageBody);

	// Submission status message (replaces buttons during submission).
	this.status		= document.createElement("div");
	this.status.id	= this.id + "Status";
	document.getElementById(id + "Form").appendChild(this.status);
}

// Reset form colors.

function resetForm() {
	for(i in this.inpIDs) eval(this.inpIDs[i]).change(0);

	// If form has preview, revert to form if showing.
	if(this.hasPreview && eval(this.id + "Preview").prevButton.value == "Edit") prevArea.preview();
}

// Sends input values to checker form.

function sendIt() {
	// If viewing preview area, switch to form; do nothing else.
	if(this.hasPreview && eval(this.id + "Preview").prevButton.value == "Edit") {
		eval(this.id + "Preview").preview();
		return false;
	}

	// Hide submission result message if showing.
	this.result.style.display = "none";

	var checker = eval(this.id + "Checker");

	// Send input values.
	for(i in this.inpIDs) {
		var
			inpID		= this.inpIDs[i],
			input		= eval(inpID),
			frmInput	= input.element,
			chkInput	= checker.document.getElementById(inpID),
			eType		= frmInput.type;
		if(frmInput.nodeName == "INPUT" && frmInput.type != "hidden") input.deselect();

		/*	Checkbox and radio inputs get their checked status sent.
			All other inputs get their value sent.	*/
		if		(frmInput.nodeName == "SELECT")	chkInput.value = frmInput.options[frmInput.selectedIndex].value;
		else if	(eType == "checkbox" || eType == "radio")	chkInput.checked = frmInput.checked;
		else	chkInput.value = frmInput.value;
	}

	// If multiple submission modes exist.
	var mode = document.getElementById(this.id + "Mode");
	if(mode) {
		checker.document.getElementById("mode").value = mode.value;
		this.status.innerHTML = mode.value == "preview" ? "Previewing..." : "Submitting...";
	}
	else this.status.innerHTML = "Submitting...";

	// Displays submission status; prevents editing form.
	this.btnsArea.style.display	= "none";
	this.status.style.display	= "block";
	this.enable(false);
	checker.form.submit();
	return false;
}

// Enable / disable inputs.

function enableForm(on) {
	for(i in this.inpIDs) eval(this.inpIDs[i]).element.disabled = !on;
}

// Shows result message.

function showResult() {
	var checker = eval(this.id + "Checker");
	this.result.id				= "submission_result_" + checker.message[0];
	this.messageHead.innerHTML	= checker.message[1];
	this.messageBody.innerHTML	= checker.message[2];
	this.result.style.display	= "block";
}

/*
	Input element object and functions.
*/

function inputItem(id, oid) {
	// Members.
	this.id			= id;
	this.element	= document.getElementById(id);
	this.label		= document.getElementById("lbl" + (oid ? oid : id));
	this.info		= document.getElementById("info" + (oid ? oid : id));

	// Methods.
	this.deselect	= willItFloat;
	this.change		= change;

	// Element-specific.
	var
		nName	= this.element.nodeName,
		form	= eval(this.element.form.id);

	/*	SELECT elements do not support "required" attribute. They're
		required if first item value is 0.	*/
	if(nName == "SELECT") this.required = this.element.options[0].value == "0";
	else {
		if(nName == "TEXTAREA") this.allowedTags = new Array();
		this.required	= this.element.getAttribute("required");
		this.stripTags	= stripTags;
		this.trim		= trim;
	}

	// ID is name of input object. Add to form's ID array.
	form.inpIDs.push("inp" + id);
}

// Perform formatting on input value when focus is taken off.

function willItFloat() {
	var nName = this.element.nodeName;
	if(nName != "SELECT") {
		if(nName == "TEXTAREA" && this.allowedTags != "all") this.stripTags();
		this.trim();
	}
	this.change(0);
}

/*	Change input and label colors.
	If input has error, shows info area with error message.	*/

function change(col, err) {
	var tr = document.getElementById("row" + this.id);
	if(!tr) return;	// Some inputs aren't in a table.

	switch(col) {
		case 0: var cName = "normal";	break;
		case 1: var cName = "selected";	break;
		case 2: var cName = "error";	break;
	}
	tr.className = cName;
	if(err) this.info.innerHTML = err;

	/*	A textarea may be set outside the table row,
		so this makes sure it gets changed.	*/
	if(this.element.nodeName == "TEXTAREA") this.element.className = cName;		
}

/*	Removes HTML tags from a string.
	List of HTML5 tags are taken from:
	http://www.w3schools.com/html5/html5_reference.asp
	Deprecated tags from previous HTML / XHTML versions are not used.	*/

var htmlTags = new Array(
	"!--", "!DOCTYPE", "a", "abbr", "address", "area", "article", "aside",
	"audio", "b", "base", "bdo", "blockquote", "body", "br", "button",
	"canvase", "caption", "cite", "code", "col", "colgroup", "command",
	"datalist", "dd", "del", "details", "dfn", "div", "dt", "em", "embed",
	"fieldset", "figcaption", "figure", "footer", "form", "h1", "h2", "h3",
	"h4", "h5", "h6", "head", "header", "hgroup", "hr", "html", "i",
	"iframe", "img", "input", "ins", "keygen", "kbd", "label", "legend",
	"li", "link", "map", "mark", "menu", "meta", "meter", "nav", "noscript",
	"object", "ol", "optgroup", "option", "output", "p", "param", "pre",
	"progress", "q", "rp", "rt", "ruby", "s", "samp", "script", "section",
	"select", "small", "source", "span", "strong", "style", "sub",
	"summary", "sup", "table", "tbody", "td", "textarea", "tfoot", "th",
	"thead", "time", "title", "tr", "ul", "var", "video", "wbr"
);

/*	Variable "tag" is used instead of htmlTags[i] in the loop because of an
	issue where when the first item in allowedTags is found, it tries to
	filter out the first item in htmlTags (!--) instead of the actual tag. */

function stripTags() {
	var str = this.element.value;
	for(i in htmlTags) {
		var tag = htmlTags[i];
		// Skip tag if allowed in string (TEXTAREA elements only).
		if(this.element.nodeName == "TEXTAREA" && inArray(tag, this.allowedTags)) continue;
		var pattern = new RegExp("<[/]?" + tag + "([^>]+)?>", "g");
		str = str.replace(pattern, "");
	}
	this.element.value = str;
}

/*	Removes whitespace (and newlines for textareas) from ends of string.
	Converts double spaces in string to single spaces. */

function trim() {
	var str = this.element.value;
	if(!str.length) return; // Empty string. Nothing to do.
	str = str.replace(/^ +| +$/g, "");
	str = str.replace(/ {2,}/g, " ");
	if(this.element.nodeName == "TEXTAREA") {
		str = str.replace(/^[ \n]+|[ \n]+$/g, "");
		str = str.replace(/^ ?| ?$/mg, "");
		str = str.replace(/\n{3,}/g, "\n\n");
	}
	this.element.value = str;
}

// Checks if a value exists in an array.

function inArray(val, arr) {
	for(i in arr)
		if(arr[i] == val) return true;
	return false;
}

/*
	Form checker object and functions.
*/

function formChecker(id, cData) {
	// Members.
	this.id			= id;
	this.iframe		= document.createElement("iframe");
	
	// Must be appended first or "src" URL won't load.
	document.getElementById(id + "Form").appendChild(this.iframe);
	this.iframe.addEventListener("load", eval(id + "Checked"), false);
	this.iframe.id	= id + "Checker";
	this.iframe.src	= "?checker:" + id;
	if(cData) this.iframe.src += ":" + cData;

	// These are set when the document is loaded.
	this.document;
	this.result;
	this.message;
	this.failures;
	this.form;

	// Methods.
	this.loaded = checkerLoaded;
}

// Checker form loaded.

function checkerLoaded() {
	this.document	= (this.iframe.contentWindow.document || this.iframe.contentDocument.document);
	this.loadup		= this.document.getElementById("loadup").innerHTML, // Initial data for form.
	this.result		= this.document.getElementById("result").innerHTML,
	this.message	= this.document.getElementById("message").innerHTML.split(":"),
	this.failures	= this.document.getElementById("failures").innerHTML,
	this.form		= this.document.getElementById("checkerForm");

	/*	Create input elements matching those for the parent form.
		Text inputs are used for SELECT elements.	*/
	var form = eval(this.id + "Form");
	for(i in form.inpIDs) {
		var
			frmInput = eval(form.inpIDs[i]).element,
			chkInput = this.document.createElement(frmInput.nodeName == "SELECT" ? "input" : frmInput.nodeName);
		chkInput.id			= form.inpIDs[i];
		chkInput.name		= frmInput.name;
		chkInput.setAttribute("readonly");	// Must be set or it won't apply.
		chkInput.readonly	= "readonly";
		if(frmInput.nodeName == "INPUT") chkInput.type = frmInput.type;
		this.document.getElementById("checkerForm").appendChild(chkInput);
	}

	// If form has multiple submission modes.
	if(document.getElementById(this.id + "Mode")) {
		var	chkInput = this.document.createElement("input");
		chkInput.id			= "mode";
		chkInput.name		= "mode";
		chkInput.setAttribute("readonly");
		chkInput.readonly	= "readonly";
		chkInput.type		= "text";
		this.document.getElementById("checkerForm").appendChild(chkInput);
	}

	/*	If submission failed, mark invalid items showing error set in checker form.
		Display result message.	*/
	if(this.result == "fail") {
		if(this.failures) {
			var failures = this.failures.split("\n");
			for(i in failures) {
				var
					iData		= failures[i].split(":");
					frmInput	= eval("inp" + iData[0]);
				frmInput.change(2, iData[1]);
			}
		}
		form.showResult();
	}
	else if(this.result == "preview") eval(this.id + "Preview").preview();

	form.enable(true);
	form.btnsArea.style.display	= "block";
	form.status.style.display	= "none";
}
