// Header {{{
/* -File           $Id: CSFValidator.js 20 2005-03-20 21:14:59Z ceefour $
 * -License        LGPL (http://www.gnu.org/copyleft/lesser.html)
 * -Copyright      2003-2005, Hendy Irawan
 * -Author         Hendy Irawan, ceefour@gauldong.net
 */
// }}}
/**
 * CSF Validator
 * Copyright (C) 2005 Hendy Irawan
 *
 * Web: http://dev.gauldong.net/csf/
 * E-mail: see web site
 *
 * Usage:
 * 1. Include CSFValidator.js in your <head> element
 * 2. Put CSF Validator attributes inside your elements.
 *
 * Supported CSF Validator attributes (they are not case-sensitive though, in current browser XHTML implementations):
 * - csfvName (string): Uses this name instead of the NAME attribute to display error message.
 * - csfvMessage (string): Displays this message instead of the default message when error occurs.
 * - csfvGroup (string): Only used on checkboxes. If present, at least one of the checkboxes in the same group
 *   must be checked. The group string should be friendly (i.e. human readable) unless you provide the csfvName attribute.
 * - csfvRequired: If present, the entry must be filled. The definition of "filled" is there is at least one non-whitespace
 *   character.
 * - csfvRegExp (string): The entry will be checked against the specified pattern (no modifiers allowed).
 * - csfvEmail: If present, the entry will be checked against a built-in regex pattern for e-mail address.
 * - csfvMatch (string): Checks the entry if it matches exactly the contents of another input element.
 *   Usually used for password confirmation check. The attribute value is the name of the other input element.
 * - csfvPassword: If present, the entry will be checked for a "standard" CSF password requirement, which
 *   is anything less than or equal than 32 characters in length. It can be empty.
 * - csfvUsername: If present, the entry will be checked against a built-in regex pattern for "standard"
 *   CSF usernames, which must contain only alphanumeric, hyphen ('-'), or underscore ('_'), and with
 *   a maximum length of 32 characters (1 character minimum).
 *
 * CSF Validator also adds a DIV element with class "csfvMessage" before the element to display all
 * error messages.
 */

function CSFValidator() {}

/**
 * If an alert should be displayed, the value of this variable will be true.
 *
 * After the first alert is displayed, this variable is set to false.
 * This variable will be reset to true when form validation begins.
 */
CSFValidator.displayAlert = true;

/**
 * Adds a CSF Validator message to an element.
 */
CSFValidator.addMessage = function(e, message) {
	CSFValidator.removeMessage(e);
	var div = document.createElement('div');
	div.className = 'csfvMessage';
	div.appendChild(document.createTextNode(message));
	e.parentNode.insertBefore(div, e);
	e.csfvInline = div;
}

/**
 * Attaches the CSF Validator to a form.
 *
 * This method uses the traditional onsubmit event handler.
 * The previous event handler is saved, and called later if the validation
 * succeeds. This is based on the assumption that you need the form to validate first
 * before the onsubmit event handler processes the form (which is usually the case).
 */
CSFValidator.attachForm = function(form) {
	if (form.onsubmit != CSFValidator.validateForm)
		form.csfvOrigOnSubmit = form.onsubmit;
	form.onsubmit = CSFValidator.validateForm;
}

/**
 * Attaches the CSF Validator to all forms in the page.
 */
CSFValidator.attachForms = function() {
	for (var i = 0; i < document.forms.length; i++) {
		CSFValidator.attachForm(document.forms[i]);
	}
}

/**
 * Raises a "this field is invalid" error.
 */
CSFValidator.raiseInvalid = function(e, defaultMessage, useInline) {
	var message = e.getAttribute("csfvMessage");
	if (!message) {
		var name = e.getAttribute("csfvName");
		if (!name) name = e.getAttribute("csfvGroup");
		if (!name) name = e.name;
		if (!name) name = e.id;
		if (typeof(defaultMessage) == "undefined" || defaultMessage == "") {
			defaultMessage = "Isian %s tidak valid. Silakan diperiksa kembali.";
		}
		message = defaultMessage.replace(/%s/g, name);
	}
	if (CSFValidator.displayAlert) {
		alert(message);
		CSFValidator.displayAlert = false;
	}
	if (typeof(useInline) == "undefined" || useInline)
		CSFValidator.addMessage(e, message);
	return false;
}

/**
 * Removes a CSF Validator message from an element.
 */
CSFValidator.removeMessage = function(e) {
	if (typeof(e.csfvInline) != "undefined") {
		e.csfvInline.parentNode.removeChild(e.csfvInline);
		e.csfvInline = undefined;
	}
}

/**
 * Validates an element.
 */
CSFValidator.validateElement = function(e) {
	var value = e.value;
	if (e.getAttribute("csfvRequired")) {
		if (!value.match(/\S/)) return CSFValidator.raiseInvalid(e, "%s harus diisi, tidak boleh dikosongkan.");
	}
	if (pattern = e.getAttribute("csfvRegExp")) {
		var re = new RegExp(pattern);
		if (!re.test(value)) return CSFValidator.raiseInvalid(e);
	}
	if (e.getAttribute("csfvEmail")) {
		var re = new RegExp("^([*+!.&#$|\'\\%\/0-9a-z^_`{}=?~:-]+)@(([0-9a-z-]+\.)+[0-9a-z]{2,4})$", "i");
		if (!re.test(value)) return CSFValidator.raiseInvalid(e, "Alamat e-mail yang Anda tuliskan tidak valid.");
	}
	// TODO: optimize csfvGroup so it doesn't check all other checkboxes in the same group that has failed
	// validation
	if ((group = e.getAttribute("csfvGroup")) && e.type=='checkbox' && !e.checked) {
		var ok = false;
		for (var i = 0; i < e.form.elements.length; i++) {
			var cb = e.form.elements[i];
			if (cb.type != 'checkbox') continue;
			if (cb.getAttribute("csfvGroup") == group && cb.checked) {
				ok = true;
				break;
			}
		}
		if (!ok) return CSFValidator.raiseInvalid(e, "Anda harus memilih minimal satu checkbox untuk %s.", false);
	}
	if (e.getAttribute("csfvPassword2")) {
		if (value.length > 32 || value.length < 6)
			return CSFValidator.raiseInvalid(e, "Password yang diperbolehkan minimal 6 karakter maksimal 32 karakter.");
	}
	if (e.getAttribute("csfvPassword")) {
		if (value.length > 32 || value.length < 6)
			return CSFValidator.raiseInvalid(e, "Password yang diperbolehkan minimal 6 karakter maksimal 32 karakter.");
	}
	if (otherName =e.getAttribute("csfvMatch")) {
		var otherElement = e.form.elements[otherName];
		if (typeof otherElement != "object") return alert('Internal error: form element '+ otherName +' not found!');		
		if (e.value != otherElement.value)
			return CSFValidator.raiseInvalid(e, "%s yang Anda masukkan tidak cocok.");
	}	
	if (e.getAttribute("csfvUsername")) {
		var re = /^[a-zA-Z0-9\-\_]{1,32}$/;
		if (!re.test(value))
			return CSFValidator.raiseInvalid(e, "Username yang Anda masukkan tidak valid."
				+" Username hanya dapat terdiri dari angka, huruf, tanda hubung ('-'), dan garis bawah ('_').");
	}
	return true;
}

/**
 * Validates a form.
 */
CSFValidator.validateForm = function(evt) {
	// do NOT use evt.target (you might get an INPUT instead of the form)
	var form = typeof(event) != "undefined" ? event.srcElement : evt.currentTarget;
	CSFValidator.displayAlert = true;
	// remove all inline messages
	for (var i = 0; i < form.elements.length; i++) {
		CSFValidator.removeMessage(form.elements[i]);
	}
	// TODO: validate the form itself (if there's such feature)
	
	// validate elements
	var success = true;
	var errorElement = null;
	for (var i = 0; i < form.elements.length; i++) {
		var ok = CSFValidator.validateElement(form.elements[i]);
		if (!ok && errorElement == null) errorElement = form.elements[i];
		success = success && ok;
	}
	if (success) {
		if (form.csfvOrigOnSubmit) return form.csfvOrigOnSubmit(evt);
		else return true;
	} else {
		if (errorElement != null) errorElement.focus();
		return false;
	}
}

// prevent multiple invocations
if (typeof(window.csfvLoaded) == "undefined") {
	window.csfvLoaded = true;
	if (window.addEventListener) window.addEventListener("load", CSFValidator.attachForms, false);
	else if (window.attachEvent) window.attachEvent("onload", CSFValidator.attachForms);
	else { /* whoops! we have no options here! */ }
}
