/*
 * system.forms.js
 * version 2.1.6
 * Copyright (C) Jason Mingl (Ratheous)
 *
 * Requires: system.tools.js  5.5.2+
 *           system.events.js 1.0.4+
 *
 * Optional: system.debug.js
 *
 <CHANGELOG>
 *
 *	05/11/2007 : 2.1.6 - Fields should now interpret 0|'' as false on checkboxes in set
 *	05/09/2007 : 2.1.5 - Added field cfg option default_init
 						 Fixed bug in form.reset/unlink. reset will now reset all fields without unlinking, unlink now properly handles unlinking
						 Fixed a bug with field.normal/original_class
						 Renamed validation.anything to unspecified
 *	05/08/2007 : 2.1.4 - Data can now be bound to forms and fields before or after initialization, individually by field or collectively by form
 						 Changed field.set_initial_value to field.set
 						 Added field option opts.bind for bound object
 						 Added field.get, bind & update
						 Added form.bind & update
						 Changed field.element.field_object to field.element.field
 *	04/30/2007 : 2.1.3 - Added id property to field class, converted to prototype-style class definitions
 *	04/29/2007 : 2.1.2 - Moved validation, notifications and rules routines into system.forms.validation.js, .notification.js and .rules.js
 *	04/19/2007 : 2.1.1 - Forms now auto-register disabled fields only if include_disabled is specified
 						 Added field.set_initial_value with option to hilight
						 Added form.check_modified
						 Added form.display_message convenience function
						 Fixed an error with field.original_class
 *	04/15/2007 : 2.1.0 - Fixed minor error with email validator
 						 Revamped/optimized events, added field.reset/unlink, form.reset/unlink, form_manager.remove_form
						 Fixed register_all_fields - wasn't loading checkboxes
 *	04/13/2007 : 2.0.9 - Fields now support the 'classname' option for setting which class(es) will be used for error/warning/modified hilighting
 								(used to be 'class' - #$%@ing IE)
 						 Removed date validator 'Date is in the future' warning
						 Cleaned up and shortened a bunch of code and corrected errors ... all over the place
						 Moved field configuration to a function 'cfg'
						 Aliased the form field registry as 'F' and '$'
						 Changed 'auto_register' to 'register'
						 Added 'required' to the form options as the default setting for auto registered fields
 *	04/08/2007 : 2.0.8 - Added integer validation
 						 Modified system.forms.form.register_field to accept registration of a particular field multiple times
						 Added auto_register option to form constructor
 *	03/30/2007 : 2.0.7 - Improved date validation
 *	03/15/2007 : 2.0.6 - checkbox validation - check_required_field
 *	03/04/2007 : 2.0.5 - Statusbar notifications should REALLY appear this time...
 *	03/04/2007 : 2.0.4 - Statusbar notifications now should appear on any button that submits the form, or the submit_element if it has been defined
 *	03/03/2007 : 2.0.3 - Field conditions now add style classes rather than changing them
 *
 </CHANGELOG>
 */

// ----------------------------------------------

if(typeof system != "object")
	var system = new Object;
if(typeof system.forms != "object") 
	system.forms = new Object;

system.forms.form_manager =
{
	form_registry: {},
	register_form: function(form)
	{
		if(!form instanceof system.forms.form)
			throw new Error("form must be an instance of system.forms.form");
		this.form_registry[form.name] = form;
	},
	remove_form: function(form)
	{
		if(!form instanceof system.forms.form)
			throw new Error("form must be an instance of system.forms.form");
		this.form_registry[form.name] = null;
	},
	handle_submit: function(form, evt) 
	{ 
		if(!form.validate())
		{
			system.events.halt(evt);

			if(system.forms.form_manager.last_click_event)
				form.display_message(system.forms.form_manager.last_click_event.target, form.submit_error);
			else if(form.submit_element)
				form.display_message(form.submit_element, form.submit_error);
		}
	},
	last_click_event: false,
	// This crap shouldn't be necessary, but for some reason getElementsByTagName can't find <input type="image">!?
	register_submit: function(element) { system.events.add($(element), 'click', form_mgr.default_click_event); },
	default_click_event: function(evt) { system.forms.form_manager.last_click_event = system.events.translate(evt); }
	
}; var form_mgr = system.forms.form_manager;

// ----------------------------------------------

system.forms.form = Class.prepare(
{
	initialize: function(opts)
	{
		try
		{
			this.element = $(opts.element);
			if(typeof this.element != "object")
				throw new Error("form ID not found");
			this.name = this.element.id;
			
			this.events = [];
			this.invoke = function(obj, observer, args) { return function() {  observer.apply(obj, [system.events.translate(arguments[0])].concat(args)); }; };
			this.handle_submit = function(form) { return function() { form_mgr.handle_submit(form, arguments[0]); }; };
			this.onsubmit = this.handle_submit(this);	this.events.push(system.events.add(this.element, "submit", this.onsubmit));
			
			this.opts = opts;
			this.bound = false;
			this.enable_submit = true;			this.field_list = new Array;	this.$ = this.F = this.field_registry = new Object;
			this.errors = false;				this.group_list = new Array;
			this.statusbar = opts.statusbar;	this.error_list = new Array;
			this.form_rules = new Array;		this.element.managed = true;
			this.submit_element = opts.submit_element == null ? null : $(opts.submit_element);
			this.submit_error = opts.submit_error == null ? "Errors on this form prevent it from being submitted" : opts.submit_error;
			
			if(opts['bind']) this.bind(opts['bind']);
			
			form_mgr.register_form(this);
			if(opts.register) this.register_all_fields(opts);
			else this.events.concat(system.events.add_by_type(document, 'INPUT', 'click', form_mgr.default_click_event));
		}
		catch(e) { system.debug.trace("system.forms.form: " + e, "error"); }
	},
	
	register_field: function(field)
	{
		if(!field instanceof system.forms.field) throw new Error("field must be an instance of system.forms.field");
		if(this.field_registry[field.element.id] instanceof system.forms.field)
		{
			var old_field = this.field_registry[field.element.id];
			old_field.unlink();
			field.index = old_field.index;
			field.next_field = old_field.next_field;
			field.prev_field = old_field.prev_field;
			if(field.prev_field /*!= null*/) field.prev_field.next_field = field;
			if(field.next_field /*!= null*/) field.next_field.prev_field = field;
			this.field_registry[field.element.id] = field;
			this.field_list[field.index] = field;
		}
		else
		{
			this.field_registry[field.element.id] = field;
			field.index = this.field_list.length;
			this.field_list[this.field_list.length] = field;
			if(this.last_field_inserted) { this.last_field_inserted.next_field = field; field.prev_field = this.last_field_inserted; }
			this.last_field_inserted = field;
		}
		if(this.bound) field.bind(this.bound_object);
	},
	
	register_field_group: function(group)
	{
		if(!field instanceof system.forms.field_group) throw new Error("field must be an instance of system.forms.field_group");
		this.group_list.push(group);
	},
	
	register_form_rule: function(rule)
	{
		if(!rule instanceof system.forms.rules.rule) throw new Error("rule must be an instance of system.forms.rules.rule");
		this.form_rules.push(rule);
	},
	
	validate: function()
	{
		this.error_list = new Array;
		for(var i = 0; i < this.field_list.length; i++)
		{
			this.field_list[i].initialized = true;
			this.field_list[i].validate();
			this.field_list[i].hilight();
			if(this.field_list[i].error) this.error_list.push(this.field_list[i].message);
		}
		if(this.error_list.length) this.errors = true;
		else this.errors = false;
		return !this.errors;
	},
	
	display_message: function(target, message) { if(this.statusbar) this.statusbar.show(target, message); },
	hide_message: function() { if(this.statusbar) this.statusbar.hide(); },
	
	check_modified: function(hilight)
	{
		for(var i = 0; i < this.field_list.length; i++)
		{
			if(hilight) this.field_list[i].hilight();
			else this.field_list[i].check_modified();
			if(this.field_list[i].modified) return true;
		}
		return false;
	},
	
	bind: function(obj, get, initial, validate, hilight)
	{
		this.bound = true;
		this.bound_object = obj;
		for(var i = 0; i < this.field_list.length; i++)
			this.field_list[i].bind(obj, get, initial, validate, hilight);
	},
	
	update: function(get, initial, validate, hilight)
	{
		for(var i = 0; i < this.field_list.length; i++)
			this.field_list[i].update(get, initial, validate, hilight);
	},
	
	register_all_fields: function(opts)
	{
		var itms = [];
		var l = this.element.getElementsByTagName('*');
		for (var i = 0; i < l.length; i++)
		{
			//alert(l[i].id);
			if(!opts['include_disabled'] && (l[i].disabled || l[i].readonly)) continue;
			if((l[i].tagName == 'INPUT' && (l[i].type == 'text' || l[i].type == 'checkbox' || l[i].type == 'radio' || l[i].type == 'password')) ||
				l[i].tagName == 'SELECT' ||
				l[i].tagName == 'TEXTAREA'
			  ) itms.push(l[i]);
			if(l[i].tagName == 'INPUT') this.events.concat(system.events.add(l[i], 'click', form_mgr.default_click_event));
		}
		for(var i = 0; i < itms.length; i++)
			new system.forms.field({form:this, element:itms[i], name:itms[i].title, 
									required:opts['required'], default_init:opts['default_init']});
	},
	
	reset: function()
	{
		this.hide_message();
		for (var i = 0; i < this.field_list.length; i++) 
			this.field_list[i].reset();
	},
	
	unlink: function()
	{
		this.hide_message();
		for (var i = 0; i < this.field_list.length; i++) 
			this.field_list[i].unlink();
		for (var i = 0; i < this.events.length; i++)
			system.events.remove(this.events[i]);
		form_mgr.remove_form(this);
	},
	
	first_field: function() { return this.field_list[0]; },
	prev_field: function(field) { return field.prev_field; },
	next_field: function(field) { return field.next_field; },
	last_field: function() { return this.field_list[this.field_list.length-1]; }

}); var form = system.forms.form;


// ----------------------------------------------

system.forms.field = Class.prepare(
{
	initialize: function(opts)
	{
		try
		{
			if(!opts.form instanceof system.forms.form) throw new Error("'form' must be a form object");
			this.form = opts.form;	
			
			this.id = typeof(opts.element) == 'object' ? opts.element.id : opts.element;
			this.element = $(opts.element);
			this.bound = false;
			if(typeof(this.element) != "object") throw new Error("'element' (" + typeof(this.element) + ") must be a form element");
				
			this.prev_field = null;
			this.next_field = null;
	
			var e = [], add = system.events.add, el = this.element, f = this.form;
			e.push(add(el, "mouseover", f.invoke(this, this.mouseover)));
			e.push(add(el, "mouseout", f.invoke(this, this.mouseout)));
			e.push(add(el, "keydown", f.invoke(this, this.keydown)));
			e.push(add(el, "keyup", f.invoke(this, this.keyup)));
			e.push(add(el, "focus", f.invoke(this, this.focus)));
			e.push(add(el, "blur", f.invoke(this, this.blur)));
			e.push(add(el, "change", f.invoke(this, this.change)));
			this.events = e;

			this.cfg(opts);
			this.form.register_field(this);
		}
		catch(e) { system.debug.trace("system.forms.field: " + e, "error"); }
	},
	
	mouseover: function(evt) { if(this.form.statusbar) this.form.statusbar.field_message(this); },
	mouseout: function(evt) { if(this.form.statusbar) this.form.statusbar.hide(); },
	keydown: function(evt) { if((evt ? evt.which : window.event.keyCode) == 13) { this.update_status(); this.hilight(); system.events.halt(evt); } },
	keyup: function(evt) { if(this.ilock) { this.ilock = false; return; } if(this.initialized) { this.validate(false); this.update_status(); this.hilight(); } },
	focus: function(evt)
	{
		if(this.initialized) { this.validate(false); this.update_status(); this.hilight(); }
		if(this.prev_field instanceof system.forms.field) 
		{
			var field = this.prev_field;
			do { if(!field.initialized) field.validate(false); field.hilight(); field.initialized = true; } while(field = field.prev_field); 
		}
	},
	blur: function(evt) { this.validate(true); this.hilight(); this.initialized = true; if(this.form.statusbar) this.form.statusbar.hide(); },
	change: function(evt) { this.validate(true);  this.hilight(); },
	
	check_modified: function()
	{
		if(this.element.type == "checkbox")
			if(this.element.checked != this.initial_checked_state) this.modified = true;
			else this.modified = false;
		else
			if(this.element.value != this.initial_value) this.modified = true;
			else this.modified = false;
		return this.modified;
	},
	validate: function(format)
	{
		if(this.element.disabled) return this.error = false;
		for(var i = 0; i < this.field_rules.length; i++)
			if(!this.field_rules[i].test())
				return this.error;
		this.validator.validate(this, format);
		this.update(true);
		return this.error;
	},
	hilight: function()
	{
		this.check_modified();
		if(this.modified) this.style_target.className = this.modified_class;
		if(this.warning) this.style_target.className = this.warning_class;
		if(this.error) this.style_target.className = this.error_class;
		if(!this.modified && !this.warning && !this.error) this.style_target.className = this.normal_class;
	},
	update_status: function() { if(this.form.statusbar) this.form.statusbar.field_message(this); else this.element.title = this.message; },
	
	register_field_rule: function(rule)
	{
		if(!rule instanceof system.forms.rules.rule)
			throw new Error("rule must be an instance of system.forms.rules.rule");
		this.field_rules.push(rule);
	},
	
	reset: function()
	{
		this.style_target.className = this.normal_class;
		this.modified = this.warning = this.error = false; this.message = '';
		this.initial_checked_state = this.element.checked;
		this.initial_value = this.element.value;
		this.initialized = this.opts['default_init'] ? true : false;
		if(this.form.statusbar) this.form.statusbar.hide();
	},
	set: function(value, initial, validate, hilight)
	{
		// Need to do some testing on radio buttons
		if(this.element.tagName == 'INPUT' && this.element.type == 'checkbox')
			{ value = !value||value==0||value=='' ? false : true; if(initial) this.initial_checked_state = value; this.element.checked = value; }
		else 
			{ value = value ? value : ''; if(initial) this.initial_value = value; this.element.value = value; }
		if(validate) this.validate(true);
		if(hilight) this.hilight();
	},
	get: function(initial)
	{ 
		if(this.element.tagName == 'INPUT' && this.element.type == 'checkbox')
			return initial ? this.initial_checked_state : this.element.checked;
		else
			return initial ? this.initial_value : this.element.value;
	},
	bind: function(obj, get, initial, validate, hilight) 
	{ 
		this.bound = true;
		this.bound_object = obj; 
		if(get != null) this.update(get, initial, validate, hilight);
	},
	update: function(get, initial, validate, hilight)
	{
		if(!this.bound_object) return;
		if(get) this.bound_object[this.element.name] = this.get();
		else this.set(this.bound_object[this.element.name], initial, validate, hilight);
	},
	
	unlink: function() 
	{ 
		this.style_target.className = this.normal_class;
		for (var i = 0; i < this.events.length; i++)
			system.events.remove(this.events[i]);
	},
	
	cfg: function(opts)
	{
		if(this.opts) { Object.extend(this.opts, opts); opts = this.opts; } else this.opts = opts;
		this.element = $(opts.element);
		if(typeof(this.element) != "object") 
			throw new Error("'element' (" + typeof(this.element) + ") must be a form element");
		this.initialized = opts['default_init'] ? true : false;
		this.initial_value = this.element.value ? this.element.value : '';
		this.initial_checked_state = this.element.checked;
		this.required = opts.required;
		this.validator = opts.validate_as != null ? opts.validate_as : system.forms.validation.unspecified;
		this.field_rules = new Array;
		this.error = this.warning = this.modified = false;
		this.name = opts.name != null ? opts.name : this.element.name;
		this.style_target = opts.style_target == null ? this.element : $(opts.style_target);
		this.message_target = opts.message_target == null ? this.style_target : $(opts.message_target);
		this.original_class = this.style_target.className;
		this.normal_class = opts.classname ? this.original_class + ' ' + opts.classname : this.original_class;
		this.modified_class = this.original_class + ' ' + this.normal_class + '_modified';
		this.warning_class = this.original_class + ' ' + this.normal_class + '_warning';
		this.error_class = this.original_class + ' ' + this.normal_class + '_error';
		this.element.field = this;
		if(opts['bind']) this.bind(opts['bind']);
	}

}); var field = system.forms.field;


// ----------------------------------------------

system.forms.field_group = Class.prepare(
{
	initialize: function(o)
	{
		try
		{
			if(!o.form instanceof system.forms.form)
				throw new Error("'form' must be a form object");
			this.form = o.form;
			if(!o.fields instanceof Array)
				throw new Error("'fields' must be an Array object");
			this.fields = o.fields;
			this.disabled = false; // Need to make these arguments
			this.locked = false;
			this.hidden = false;
			this.enable = function(enable) { for(field in this.fields) field.element.disabled = !enable; this.disabled = !enable; };
			this.lock = function(lock) { for(field in this.fields) field.element.readonly = lock; this.locked = lock; };
			this.hide = function(hide) { for(field in this.fields) field.element.style.visibility = hide ? "hidden" : "visible"; this.hidden = hide; };
			this.form.register_field_group(this);
		}
		catch(e) { system.debug.trace("system.forms.form_manager.field_group: " + e, "error"); }
	}
}); var field_group = system.forms.field_group;

// ----------------------------------------------------------------------
// form rules

system.forms.rules = new Object;
var form_rules = system.forms.rules;

system.forms.rules.rule = Class.prepare(
{
	initialize: function(o)
	{
		this.form = o.form;
		this.criteria = o.criteria;
		this.enabled = o.enabled == null || o.enabled ? true : false;
		this.enable = function(enable) { this.enabled = enable; };
		this.message = o.message;
		this.group = o.group;
		var fs = this.group.fields;
		for(var i = 0; i < fs.length; i++) fs[i].register_field_rule(this);
		this.test = function()
		{
			if(this.enabled) return this.criteria(this, o); 
			return true; 
		};
		this.form.register_form_rule(this);
	}
}); var form_rule = system.forms.rules.rule;

// ----------------------------------------------------------------------
// form validation

system.forms.validation = {};
var validators = system.forms.validation;

var empty_string = /^\s*$/;

// Check required field ----------------------------------------------------------------------
// Returns true if required and present OR if not required. false if required and not present
system.forms.validation.check_required_field = function(f)
{
	if(eval(f.required))
	{
		if(f.element.type == 'radio')
		{
			var itms = document.getElementsByName(f.element.name);
			for(var i = 0, checked = false; i < itms.length; i++) if(itms.item(i).checked == true) checked = true;
			if(!checked) { f.error = true; f.message = f.name + ": please select an option"; return false; }
		}
		else if(f.element.type == 'checkbox') { if(!f.element.checked) { f.error = true; f.message = f.name + " must be checked"; return false; } }
		else if(empty_string.test(f.element.value)) { f.error = true; f.message = f.name + " is required"; return false; }
	}
	f.error = false; f.message = ""; return true;
};

// Validate unspecified ----------------------------------------------------------------------

system.forms.validation.unspecified =
{
	name: 'unspecified',
	format: function(v) { return v; },
	validate: function(f) { f.error = false; system.forms.validation.check_required_field(f); }
};