/*
	__foundation/_js/form.js
	purpose:
		form and field classes, with protoytype methods
			Form class:
				* instanciates Field class for each of its fields
				* can validate itself
				* has methods to toggle certain behaviors
				* has method to find specific child field's class or jquery element reference
				* can "unset" itself (rollback all behaviors) and optionally remove the entire form from the DOM
				* can receive new fields to instanciate Field classes for
				* can tell individual field classes to unset themselves, and optionally remove the field from the DOM
			Field class:
				* has methods to toggle certain behaviors
				* can validate itself
				* extends itself based on specific properties / attributes of the field
				* has method to extend itself
				* has method to remove extensions
				* can unset itself, and optionally remove the field from the DOM
			Field class extensions:
				* can each have init() function
				* can each have remove function (which should be prefixed with extension name, ie labelRemove
				* each have relevant support methods that lend themselves to the field
	dependencies:
		jQuery
		jQuery.common (pre-bind function)
		modernizr
	good-to-haves:
		O_O (validation, password strength)
	todo:
		- remove shim for placeholder functionality?
		- further validations for html5 fields (color, date, etc)		
*/



// - ensure we have the following:
if(!O_O) var O_O = {};

if(!O_O.Str) O_O.Str = {};

if(typeof(O_O.Str.hds) != 'function') {
	O_O.Str.hds = function(str) {
		return '«'+str+'»';	
	}
}


if(typeof(Object.size) != 'function') {
	Object.size = function(obj) {
		 var size = 0, key;
		 for(key in obj) {
			 if(obj.hasOwnProperty(key)) size++;
		 }
		 return size;
	};	
}



// - form class
O_O.Form = function(form_id, j_form, lang_env) {
	
	// -- some immediate handling
	if(!form_id && !j_form) {
		// --- require form id or form jquery object
		return false;	
	} else if(!j_form) {
		// ---- get jquery form object by id
		j_form = $('form#'+form_id);	
	} else if(!form_id) { 
		// ---- get id from jquery form object
		form_id = j_form.attr('id');	
	}
	
	
	
	// -- variables
	this.vars = {
		alert_handler : null,		// custom alert handler for errored form
		disable_submit : false,		// disable form submission (ie form checks AND default behavior)
		fields : {},					// array of all fields (except hidden ones). selector : field_class
		fields_button : {},			// button-style input fields
		verify_callback : null,		// additional function to execute on successful form validation
		lang_env : lang_env,			// language environment, if any
		lang : {}						// reference to language object - if available
		
	}
	
	
	
	// -- jquery objects
	this.j = {
		form: j_form // this form
	}
	
	
	
	
	// -- initialize the form
	this.init();
}





// - Form prototype methods

// = construct, destruct


// -- initialization
O_O.Form.prototype.init = function() {
	
	if(!this.j.form.length) {
		// --- form was not found. bail out.
		return;
	}
	
	// --- (try to) assign language
	try {
		if(this.vars.lang_env) {
			this.vars.lang	= O_O.lang[this.vars.lang_env];
		} else {
			this.vars.lang = O_O.lang;
		}			
	} catch(err) {
	}
	
	
	if(!this.vars.lang) {
		
		// ---- assign empty objects to satisfy future references
		this.vars.lang = {};
		if(this.vars.lang_env) {
			this.vars.lang.subset = {};	
		}
	}
	
	
	
	// --- disable html5 form validation
	this.j.form.attr('novalidate', 'novalidate');
		
	
	// --- enable form validation on submit
	this.submitSwitch();
	
	
	
	// --- loop all fields (except input:hidden), running fieldAdd() on them
	var self = this;
	$('input:not(":hidden"), select, textarea', this.j.form).each(function() {
		self.fieldAdd($(this), self);
	});
}
	
	
	
	
	
// -- removes assigned listeners, empties class, and optionally deletes the DOM element
O_O.Form.prototype.remove = function(remove_dom) {
	
	
	// --- loop all fields, running their remove() function
	var x,y;
	for(x in this.vars.fields) {
		y = this.vars.fields[x];
		
		// ---- remove
		y.remove(remove_dom);
	}
	
	
	
	if(remove_dom) {
		// --- remove DOM element
		this.j.form.remove();	
	} else {
		// --- re-enable html5 form validation
		this.j.form.removeAttr('novalidate');
		
		// --- remove form validation on submit
		this.submitSwitch();
	}
	
	
	
	// --- empty our form class
	this.unset();		
}





// -- deletes all properties of this, as we can't delete the object itself
O_O.Form.prototype.unset = function() {
	var x;
	for(x in this) {
		delete this[x];
	}
}





// = common methods


// -- alerts errors caught on form submission
O_O.Form.prototype.alert = function(error_msg, j_form) {
	try {
		O_O.Dia.alertCustom(error_msg, 'error', 'error', this.vars.lang_env);
	} catch(err) {
		alert(error_msg);	
	}
	
	return false;
}





// -- assigns custom alert handler @fn for form. can unset by passing empty value
O_O.Form.prototype.alertHandlerAssign = function(fn) {
	if(!fn) fn = null;
	this.vars.alert_handler = fn;
}





// -- disables (or re-enables) submission
// -- note: this differs from submitSwitch, which switches the formCheck submit handler on and off
O_O.Form.prototype.disableSubmit = function(off) {
	this.vars.disable_submit = (off) ? false : true;
}





// -- add a jquery element field to the form class
O_O.Form.prototype.fieldAdd = function(j, c_form) {
	
	var selector;
	
	if((selector = j.attr('id'))) { // id (preferred)
		selector = '#'+selector;
	} else if(!(selector = j.attr('name'))) { // name
		selector = Object.size(this.vars.fields); // next available index
	}
	
	f = new O_O.Field(j, selector, c_form);
	
	
	// --- add class to fields object
	this.vars.fields[selector] = f;
	
	switch(j.attr('type')) {
		case 'button':
		case 'reset':
		case 'submit':
			// --- add class to button fields object
			this.vars.fields_button[selector] = f;
		break;
	}
	
	
	// --- return class instance
	return f;
}





// -- retrieves field element by @selector and @format
// -- @selector(string): key of this.vars.field
// -- @format(string): j=jquery object || c=class object)
O_O.Form.prototype.fieldGet = function(selector, format) {
	
	var c = this.vars.fields[selector];
	
	if(!c) {
		return false;	
	}
	
	if(format == 'j') {
		return c.j.field	
	} else {
		return c;	
	}
}





// -- removes field element class, by @selector.
// -- @selector(string): key of this.vars.field
// -- @remove_dom(bool): whether to delete the DOM element
O_O.Form.prototype.fieldRemove = function(selector, remove_dom) {
	
	var c;
	if(!(c = this.fieldGet(selector, 'c'))) {
		return false;
	}
	
	c.remove(remove_dom);
}





// -- enable / disable submit listener.
// -- note: this differs from disableSubmit, which returns false to avoid allowing the form to be submitted
O_O.Form.prototype.submitSwitch = function(off) {
	
	if(off) {
		// --- unbind event, exit
		this.j.form.unbind('submit.formCheck');
		return;
	};
	
	
	// --- (pre)bind event
	var self = this;
	this.j.form.preBind('submit.formCheck', function() {
		return self.verify();
	});	
}





// -- verify the form (such as on submit)
O_O.Form.prototype.verify = function() {
	
	
	// --- we are meant to disable form submission
	if(this.vars.disable_submit) return false;
	
	
	// --- vars re-used for many things
	var x,y;
	
	
	// --- disable buttons (prevent double-submit)
	for(x in this.vars.fields_button) {
		y = this.vars.fields_button[x];
		y.disabledSwitch();
	}
	
	// --- loop all fields, running their verify functions
	var error_msg = '';
	var verified_radios = {};
	for(x in this.vars.fields) {
		y = this.vars.fields[x];
		
		if(!y.hasExtension('nonbutton')) {
			// ---- only non-buttons need to be processed
			continue;
		}
		
		if(y.j.field.is(':radio')) {
			// ---- radios should be processed once per group, rather than once per radio
			if(verified_radios[y.j.field.attr('name')]) {
				continue;
			}
			verified_radios[y.j.field.attr('name')] = true;
		}
		
		
		error_msg += y.verify();
	}
	
	
	
	if(error_msg) {
		// --- error occured
		
		// ---- re-enable buttons
		for(x in this.vars.fields_button) {
			y = this.vars.fields_button[x]
			y.disabledSwitch(true);
		}
		
		// ---- run callback function or return error string
		if(typeof(this.vars.alert_handler) == 'function') {
			return this.vars.alert_handler(error_msg, this.j.form);
		} else {
			return this.alert(error_msg, this.j.form);
		}
	}
	
	
	// --- succeeded
	
	// ---- re-enable all disabled fields (avoid empty values on submit)
	for(x in this.vars.fields) {
		y = this.vars.fields[x];
		y.disabledSwitch(true);
	}
	
	
	if(typeof(this.vars.verify_callback) == 'function') {
		return this.vars.verify_callback();
	}
			
	return true;
}





// -- assigns verify callback function. can unset by passing empty value
O_O.Form.prototype.verifyCallbackAssign = function(fn) {
	if(!fn) fn = null;
	this.vars.verify_callback = fn;
}






// ===========================================================================





// - field (base) class
O_O.Field = function(j_field, selector, c_form) {

	this.j = { // collection of jquery objects
		field: j_field, // jquery form element
		label: {} // label associated with this field
	}
	
	this.vars = { // variables
		form: c_form, // parent form class (if available)
		extensions: {}, // extensions applied to this class (key:true)
		'selector': selector,
		type: 'text', // type of field (assume text for now)
		alert_handler: null // custom alert handler for errored field
	}
	
	
	
	// --- initialize the field
	this.init();
}





// -- initialization 
O_O.Field.prototype.init = function() {
	
	// --- determine type of field (type attribute, or tag name);
	var type;
	if(!(type = this.j.field.attr('type'))) {
		var tag_name = this.j.field.prop('tagName').toLowerCase();
		if(tag_name == 'input') {
			type = 'text';	
		} else {
			type = tag_name;	
		}
	}
	this.vars.type = type;
	
	
	
	// --- apply extensions based on type. some of these are order-sensitive
	
	// ---- "Nonbutton"
	switch(type) {
		default:	
			this.extend('Nonbutton');
		break;
		case 'button':
		case 'reset':
		case 'submit':
			// ...
		break;
	};
	
	
	
	// ---- "Range"
	switch(type) {
		default:
			// ...
		break;
		case 'number':
		case 'range':
			this.extend('Range');
		break;
	};
	
	
	
	// ---- "Text"
	switch(type) {
		default:
			// ...
		break;
		case 'color':
		case 'date':
		case 'datetime':
		case 'datetime-local':
		case 'email':
		case 'month':
		case 'number':
		case 'password':
		case 'range':
		case 'tel':
		case 'text':
		case 'textarea':
		case 'time':
		case 'url':
		case 'week': 
			this.extend('Text');
		break;
	};
	
	
	
	// ---- "Textarea"
	if(type == 'textarea') {
		this.extend('Textarea');
	}
}




// -- remove field class, optionally removing the DOM element as well
O_O.Field.prototype.remove = function(remove_dom) {
	
	if(remove_dom) {
		this.j.field.remove();
	}

	
	
	// --- loop extensions, running their xRemove() method
	var x;
	for(x in this.vars.extensions) {
		y = this.vars.extensions[x];
		
		if(typeof(this[x+'Remove']) == 'function') {
			this[x+'Remove'](remove_dom);
		}
	}
	
	
	
	// --- remove class reference from fields object
	delete this.vars.form.vars.fields[this.vars.selector];
	// --- remove class reference from button fields object
	delete this.vars.form.vars.fields_button[this.vars.selector];
	
	// --- remove all properties of the class instance (as we can't delete the instance itself)
	this.unset(); 
	
}




// -- deletes all properties of c_field, as we can't delete the object itself
O_O.Field.prototype.unset = function() {
	var x;
	for(x in this) {
		delete this[x];
	}
}





// = common methods


// --  facilitates a number of xSwitch methods that toggle the status / value of attributes 
// -- @is_static indicates that the attribute value, when enabled, matches the name (ie verify="verify")
// -- whereas non-static values need to be moved to temporary attributtes (ie placeholder_temp="jane@doe.com")
O_O.Field.prototype.attrSwitch = function(attr, is_static, off) {
	
	var j = this.j.field;
	
	if(
		(off && !j.attr(attr)) ||
		(!off && is_static && j.attr(attr)) ||
		(!off && !is_static && !j.attr(attr+'_temp'))
	) {
		// --- nothing to do
		return false;
	}
	
	
	
	if(off) {
		// --- remove
		if(!is_static) {
			// ---- store value to temporary attribute
			j.attr(attr+'_temp', j.attr(attr));
		}
		
		j.removeAttr(attr);
	} else {
		// --- restore
		if(is_static) {
			j.attr(attr, attr);
		} else {
			// ---- move temporary attribute to attribute, then remove temporary attribute
			j.attr(attr, j.attr(attr+'_temp'));
		}
	}
	
	
	
	return true
}





// -- toggles disabled attribute for field
O_O.Field.prototype.disabledSwitch = function(off) {
	
	// --- (attempt to) toggle the attribute
	if(!this.attrSwitch('disabled', true, off)) {
		// ---- there was nothing to do
		return false;	
	}
	
	
	
	// --- toggle the class
	this.j.field.toggleClass('disabled');
}





// -- extends this class, and run the extension's init function
O_O.Field.prototype.extend = function(extension) {
	this.init = function(){};
	this.vars.extensions[extension.toLowerCase()] = true;
	$.extend(this, new O_O.Field[extension](this));
	this.init();
}





// -- determines if extension is laoded for this class
O_O.Field.prototype.hasExtension = function(str) {
	return (this.vars.extensions[str] == true);
}





// ===========================================================================





// -- extension for fields that are not buttons (ie: can be validated, can have labels, can be read-only)
// -- dependencies: none
O_O.Field.Nonbutton = function() {}





// = methods (constructor, desructor)

// --- initialization
O_O.Field.Nonbutton.prototype.init = function() {
	
	// ---- assign field's associated label
	this.labelSet();
	
	// ---- assign label text
	this.labelTextSet();
	
	
	if(!this.j.field.attr('no_blur') && (this.j.field.attr('required') || this.j.field.attr('match') || this.j.field.attr('strength') || this.j.field.attr('validate'))) {
		// --- enable verification on blur
		this.blurSwitch();
	}
	
	
	// ---- configure, then add verification methods (required)
	this.vars.verification_types = {};
	
	this.verificationTypesAdd('required', ((this.vars.form.vars.lang['%s_is_required']) ? this.vars.form.vars.lang['%s_is_required'] : "%s is required"));
}





// --- removal
O_O.Field.Nonbutton.prototype.nonbuttonRemove = function(remove_dom) {
	if(remove_dom && this.j.label) {
		// ---- remove label from dom
		this.j.label.remove();	
	} else {
		// ---- remove blur listener
		this.blurSwitch(true);
		// ---- remove errored state
		this.erroredSwitch(true);
	}
}




// = methods (common)

// --- alerts error_msg when an individual field check fails
O_O.Field.Nonbutton.prototype.alert = function(error_msg, j_field) {
	try {
		O_O.Dia.alertCustom(error_msg, 'error', 'error', this.vars.form.vars.lang_env);
	} catch(err) {
		alert(error_msg);	
	}
}





// --- assigns custom alert handler @fn for form. can unset by passing empty value
O_O.Field.Nonbutton.prototype.alertHandlerAssign = function(fn) {
	if(!fn) fn = null;
	this.vars.alert_handler = fn;
}





// --- enable / disable blur listener
O_O.Field.Nonbutton.prototype.blurSwitch = function(off) {
	
	var j = this.j.field;
	
	if(off) {
		// ---- unbind checkField
		this.j.field.unbind('blur.checkField');
		return;
	}
	
	// ---- move inline "onblur" functions to object's events. this ensures they will execute *after* checkField.
	var t_onblur_functions = this.j.field.attr('onblur');
	if(t_onblur_functions) {
		j.removeAttr('onblur');
		j.bind('blur', function(){
			eval(t_onblur_functions);
		});
	}
	
	
	
	// ---- (pre)bind checkField
	var self = this;
	j.preBind('blur.checkField', function() {
		self.verify(true);
	});
}





// --- "unerror" a field, by type of verification it had failed
O_O.Field.Nonbutton.prototype.erroredOffByType = function(type) {
	
	if(!type) {
		return;	
	}
	
	var class_name = type+'_failed';
	
	if(this.j.field.hasClass(class_name)) {
		this.j.field
			.removeClass(class_name)
			.removeClass('errored');
		if(this.j.label) {
			this.j.label
				.removeClass(class_name)
				.removeClass('errored');
		}
	}
}





// --- enable / disable errored state of field
O_O.Field.Nonbutton.prototype.erroredSwitch = function(off) {
	
	if(off) {
		this.j.field.removeClass('errored');
		if(this.j.label) {
			this.j.label.removeClass('errored');
		}
		return;	
	}
	
	
	
	this.j.field.addClass('errored');
	if(this.j.label) {
		this.j.label.addClass('errored');
	}
}





// --- assigns label object to class variable
O_O.Field.Nonbutton.prototype.labelAssign = function(label) {
	this.j.label = label;	
}





// --- sets (determines) label object for this field (via selector, object, or auto-detect if blank)
O_O.Field.Nonbutton.prototype.labelSet = function(label) {
	if(label instanceof jQuery) {
		// ---- jquery object has been passed
		this.labelAssign(label);
		return;
	} else if(typeof(label) == 'object') {
		// ---- a regular object has been passed (ie an html node)
		this.labelAssign($(label));
		return;
	}
	
	
	var field_id = this.j.field.attr('id');
	var j_label;
	
	
	// --- try to find label with "for" matching this field's id
	if(field_id) {
		j_label = $('label[for='+field_id+']', this.j.form);
		if(j_label.length) {
			this.labelAssign(j_label);
			return;
		}
	}
	
	// ---- try to find label that is sibling to this field
	j_label = this.j.field.siblings('label');
	if(j_label.length == 1) {
		// ----- only proceed if we found exactly one match
		this.labelAssign(j_label);
		return;
	}
	
	// ---- give up
	this.labelAssign(null);
}





// --- (re)assigns label text to class variable
O_O.Field.Nonbutton.prototype.labelTextAssign = function(text) {
	this.vars.label_text = text;
}





// --- determines label text of field (for alerts)
O_O.Field.Nonbutton.prototype.labelTextSet = function(text) {
	
	if(text) {
		this.labelTextAssign(text);
		return;
	}
	
	var text;
	var rel;
	var field_id = this.j.field.attr('id');
	
	
	// ---- get text from label object
	if(this.j.label) {
		var t_text = $('> span', this.j.label).text();
		if(t_text) {
			text = t_text;
		}
		
		var t_rel = this.j.label.attr('rel');				
		if(t_rel) {
			rel = t_rel;	
		}
	}
	
	// ---- settle for the field's name
	if(!text) {
		text = this.j.field.attr('name');//.replace(/_/g, ' ');//replace understrengths with spaces
	}
	
	// --- add the rel
	if(rel) {
		text += ' ['+rel+']';	
	}
	
	this.labelTextAssign(text);		
}	





// --- toggles readonly attribute for field. 
O_O.Field.Nonbutton.prototype.readonlySwitch = function(off) {
	
	// ---- certain field types should be disabled, not readonly
	switch(this.vars.type) {
		case 'checkbox':
		case 'radio':
		case 'select':
			this.disabledSwitch(off);
			return;
		break;
	}
	
	
	// ---- (attempt to) toggle the attribute
	if(!this.attrSwitch('readonly', true, off)) {
		// ----- there was nothing to do
		return;	
	}
	
	
	// ---- toggle class
	this.j.field.toggleClass('readonly');
}





// --- toggles required attribute for field
O_O.Field.Nonbutton.prototype.requiredSwitch = function(off) {
	
	// ---- (attempt to) toggle the attribute
	if(!this.attrSwitch('required', true, off)) {
		// ----- there was nothing to do
		return false;	
	}
	
	
	
	if(off) {
		
		// ---- if field is errored due to required check having failed, 'unerror' it
		this.erroredOffByType('required');
		
		if(this.j.label) {
			// ---- remove "* " from label
			this.j.label.html(this.j.label.html().replace('* ', ''));
		}
		return;
	}
	
	
	
	if(this.j.label) {
		// --- add "* " to label
		if(this.j.label.html().indexOf('* ') !== 0) {
			this.j.label.prepend('* ', '');
		}
	}
}





// --- verifies required field. returns true passed; false otherwise
O_O.Field.Nonbutton.prototype.requiredVerify = function() {
	// ---- these are (often) arrayed fields. determine whether one is checked
	//if(this.j.field.is('input:checkbox') || this.j.field.is('input:radio')) {
	
		
	 if(this.j.field.is('input:checkbox')) {
		// ---- checkboxes need to determine whether they are checked or not
		return (this.j.field.is(':checked'));
	} else if(this.j.field.is('input:radio')) {
		// ---- radios need only determine whether one is checked
		var has_selection = false;
		$('input[name='+this.j.field.attr('name')+']', this.vars.form.j.form).each(function() {
			if($(this).is(':checked')) {
				// ----- don't simply return true here - that only breaks the "for" loop.
				has_selection = true;
				return false; // break the loop
			}
		});
		return has_selection;
	}
	
	
	// ---- otherwise, just check for val()
	return this.j.field.val().length;			
}





// -- adds a type of verification to check, for this field
// this verification will only happen if the field has the corresponding attribute, ie required="required"
O_O.Field.Nonbutton.prototype.verificationTypesAdd = function(type, string) {
	if(!string) string = '';
	this.vars.verification_types[type] = string;
}





// --- verifies an individual field, with optional callback function
O_O.Field.Nonbutton.prototype.verify = function(on_error) {
	
	if(this.j.field.attr('readonly') || this.j.field.attr('disabled')) {
		// ---- do not validate a field that the user is unable to use
		return '';	
	}
	
	var error_msg = '';
	var x, class_name;
	for(x in this.vars.verification_types) {
		// ---- class to add / remove accordingly
		class_name = x+'_failed';
		
		if(this.j.field.attr(x)) {
			// ---- need to run this type of verification
			
			if(!this[x+'Verify']()){
				// ----- failed this verification type
				error_msg += this.vars.verification_types[x].replace('%s', O_O.Str.hds(this.vars.label_text))+"\n";
				this.j.field.addClass(class_name);
				if(this.j.label) {
					this.j.label.addClass(class_name);
				}
				break;
			}
		}
		
		
		this.j.field.removeClass(class_name);
		if(this.j.label) {
			this.j.label.removeClass(class_name);
		}
	}
	
	
	
	
	if(error_msg) {
		
		// ---- failed verification
		this.erroredSwitch();
		if(typeof(on_error) == 'function') {
			// ----- custom callback handed directly to this function call
			return on_error(error_msg, this.j.field);
		} else if(on_error) {
			// ----- boolean. use default callback
			if(typeof(this.vars.alert_handler) == 'function') {
				return this.vars.alert_handler(error_msg, this.j.field);
			} else {
				return this.alert(error_msg, this.j.field);
			}
		} else {
			// ----- empty. simply return the error handler
			return error_msg;
		}
	}
	
	
	
	// ---- all clear
	this.erroredSwitch(this.j.field, true);
	return '';
}





// ===========================================================================





// -- extension for ranges (number, range)
// -- dependencies: nobutton
O_O.Field.Range = function() {}





// = methods (constructor, destructor)


// --- initializaiton
O_O.Field.Range.prototype.init = function() {
	this.rangeSwitch();	
	
	
	// ---- add verification methods (range)
	this.verificationTypesAdd('range', ((this.vars.form.vars.lang['x_is_out_of_range']) ? this.vars.form.vars.lang['x_is_out_of_range'] : "%s is out of range"));
}





// --- removal
O_O.Field.Range.prototype.rangeRemove = function() {
	this.rangeSwitch(true);
}




// = methods (common)


// --- enable / disable range verification
O_O.Field.Range.prototype.rangeSwitch = function(off) {
	var c = this.j.field;
	
	if(off) {
		if(!c.attr('range')) {
			// ---- nothing to do
			return;
		}
		
		
		// ---- if field is errored due to range check having failed, 'unerror' it
		if(this.j.field.hasClass('errored') && !this.rangeVerify(this.j.field)) {
			this.erroredSwitch(this.j.field, true);
		}
		
		
		c.removeAttr('range');
		return;
	}
	
	
	
	if(typeof(O_O.Math.inRange) != 'function') {
		// ---- require our inRange function
		return;	
	}
	
	
	if(c.attr('range')) {
		// ---- nothing to do
		return;
	}
	
	c.attr('range', 'range');
	return;
}





// --- verify range
O_O.Field.Range.prototype.rangeVerify = function() {
	var c = this.j.field;
	
	if(c.attr('range_skip')) {
		// ---- nothing to do
		return true;	
	}
	
	return O_O.Math.inRange(c.val(), c.attr('min'), c.attr('max'), c.attr('step'));
}





// ===========================================================================





// -- extension for text-based fields
// -- dependencies: Nobutton
O_O.Field.Text = function() {}





// = methods (constructur, destrcutor)
		
		
// --- initialization
O_O.Field.Text.prototype.init = function() {
	
	// ---- enable placeholder listener
	this.placeholderSwitch();
	
	
	// ----- add verification methods (match, pattern, validate)
	this.verificationTypesAdd('match', ((this.vars.form.vars.lang['%s_does_not_match']) ? this.vars.form.vars.lang['%s_does_not_match'] : "%s does not match"));
	var str_invalid_format = (this.vars.form.vars.lang['Invalid_format_for_%s']) ? this.vars.form.vars.lang['Invalid_format_for_%s'] : "Invalid format for %s";
	this.verificationTypesAdd('pattern', str_invalid_format);
	this.verificationTypesAdd('validate', str_invalid_format);
	this.verificationTypesAdd('strength', '');
}





// --- removal
O_O.Field.Text.prototype.textRemove = function(remove_dom) {
	if(!remove_dom) {
		this.placeholderSwitch(true);	
	}
}





// = methods (common)


// --- toggles match attribute for field
O_O.Field.Text.prototype.matchSwitch = function(off) {
	
	// ---- (attempt to) toggle the attribute
	if(!this.attrSwitch('match', false, off)) {
		// ----- there was nothing to do
		return false;	
	}
	
	
	
	if(off) {
		// ---- if field is errored due to validation check having failed, 'unerror' it
		this.erroredOffByType('match');
	}
}





// --- conducts field match checks. returns true if passed; false otherwise
O_O.Field.Text.prototype.matchVerify = function() {
	
	var val = this.j.field.val();
	var selectors = this.j.field.attr('match').split(',');
	//O_O.Bro.da(this.vars.form.vars.fields, 1);
	var x, y, t; // loop variables
	for(x in selectors) {
		y = selectors[x];
		
		
		if(!(t = this.vars.form.fieldGet(y, 'j'))) {
			// ---- field was not found - skip ahead.
			continue;
		}

		
		if(t) {
			if(val != t.val()) {
				return false;	
			}
		}
	}
	return true;
}





// --- switches maxlength on or off
O_O.Field.Text.prototype.maxlengthSwitch = function(off) {
	var j = this.vars.form.j.field;
	
	
	if(off) {
		if(!j.attr('maxlength')) {
			// ---- nothing to do
			return;	
		}
		
		
		// ---- reassign maxlength to maxlength_prev, then remove maxlength
		j.addAttr('maxlength_prev', j.attr('maxlength'));
		j.removeAttr('maxlength');
		
		if(this.vars.form.hasExtension('textarea')) {
			// ---- disable limitLength
			this.vars.form.limitLengthSwitch(true);	
		}
		
		if(this.vars.form.hasExtension('label')) {
			// ---- hide / remove the limit portion of the label
			//================================== maybe later	
		}
		
		return;	
	}
	
	
	if(!j.attr('maxlength_prev')) {
		// ---- nothing to do
		return;	
	}
	
	
	// ---- reassign maxlength_prev to maxlength, then remove maxlength_prev
	j.addAttr('maxlength', j.attr('maxlength_prev'));
	j.removeAttr('maxlength_prev');
	
	if(this.vars.form.hasExtension('textarea')) {
		// ---- enable limitLength
		this.vars.form.limitLengthSwitch();
	}
	
	
	if(this.vars.form.hasExtension('label')) {
		// ---- show / add the limit portion of the label
		//================================== later	
	}
}





// -- toggle placeholder text (remove / add text field)
O_O.Field.Text.prototype.placeholderToggle = function(is_focused) {
	var j = this.j.field;
	var v = j.val();
	var p = j.attr('placeholder');
	
	if(is_focused && v == p) {
		j.val('');
	} else if (!is_focused && !v) {
		j.val(p);
	}
	
	if(j.val() == p) {
		j.addClass('placeholder');
	}
}





// -- enable / disable placeholder (shim for html5 feature)
O_O.Field.Text.prototype.placeholderSwitch = function(off) {
	var j = this.j.field;
	
	if(!j.attr('placeholder')) {
		// ---- nothing to do
		return;	
	}
	
	if(typeof(Modernizr) == 'undefined') {
		// ---- modernizr is not available. safest assumption is to do nothing
		return;
	}
	
	if(Modernizr.input.placeholder) {
		// ---- our browser has handling for placeholder
		return;	
	}
	
	
	
	if(off) {
		// --- unbind placeholderToggle()
		j.unbind('focus.placeholderToggle');
		j.unbind('blur.placeholderToggle');
		// --- remove placeholder class
		j.removeClass('placeholder');
		return;
	}
	
	
	
	// --- bind placeholderToggle()
	j.bind('focus.placeholderToggle', function() {
		this.placeholderToggle(true);	
	});
			
	j.bind('blur.placeholderToggle', function() {
		this.placeholderToggle();	
	});
	
	
	// --- add placeholder class
	j.addClass('placeholder');
}





// --- toggles pattern attribute for field
O_O.Field.Text.prototype.patternSwitch = function(off) {
	
	
	// ---- (attempt to) toggle the attribute
	if(!this.attrSwitch('match', false, off)) {
		// ----- there was nothing to do
		return false;	
	}
	
	
	
	if(off) {
		// ---- if field is errored due to validation check having failed, 'unerror' it
		this.erroredOffByType('pattern');
	}
}





// --- conducts field pattern checks. returns true if passed; false otherwise
O_O.Field.Text.prototype.patternVerify = function() {
	var pattern = new RegExp("^(?:"+this.j.field.attr('pattern')+")$");
	return pattern.test(this.j.field.val());
}





// --- toggles strength attribute for field
O_O.Field.Text.prototype.strengthSwitch = function(off) {
	
	// ---- (attempt to) toggle the attribute
	if(!this.attrSwitch('strength', false, off)) {
		// ----- there was nothing to do
		return false;	
	}
	
	
	this.j.field.toggleClass('strength');
}





// --- assigns password strength to field's attribute, and adds class
O_O.Field.Text.prototype.strengthVerify = function() {
	
	
	if(typeof(O_O.Str.passwordStrength) != 'function') {
		return true; // can't do anything without the supporting function
	}
	
	var val = this.j.field.val();
	var strength_old = this.j.field.attr('strength');
	var strength_new = String(O_O.Str.passwordStrength(val));
	
	if(strength_new != strength_old) {
		this.j.field
			.attr('strength', strength_new)
			.addClass(strength_new)
			.removeClass(strength_old);
	}
	
	return true;
}





// --- toggles validate attribute for field
O_O.Field.Text.prototype.validateSwitch = function(off) {
	
	// ---- (attempt to) toggle the attribute
	if(!this.attrSwitch('validate', false, off)) {
		// ----- there was nothing to do
		return false;	
	}
	
	
	
	if(off) {
		// ---- if field is errored due to validation check having failed, 'unerror' it
		this.erroredOffByType('validate');
	}
}





// --- conducts field validation. returns true if passed; false otherwise
// --- not sure when it would be useful, but this can handle multiple validations
O_O.Field.Text.prototype.validateVerify = function() {
	
	if(typeof(O_O.Str.vdt) != 'function') {
		// ---- we don't have the supporting validate function, so we can't continue
		return true;	
	}
	
	var val = this.j.field.val();
	var validations = this.j.field.attr('validate').split(','); 
	
	var x, y; // loop variables
	for(x in validations) {
		y = validations[x];
		if(!O_O.Str.vdt(val, y)) {
			return false;	
		}
	}
	return true;
}





// ===========================================================================





// -- extension for textareas
// -- dependencies: Nobutton, Text
O_O.Field.Textarea = function() {}





// = methods (constructor, destructor)


// --- initialization
O_O.Field.Textarea.prototype.init = function() {
	if(this.j.field.attr('maxlength')) {
		this.limitLengthSwitch();
	}
}





// = methods (common)


// --- forcibly limits the amount of characters in the field
O_O.Field.Textarea.prototype.limitLength = function() {
	var j = this.j.field;
	var max = parseInt(j.attr('maxlength'));
	if(max == 0) return;
	if(j.val().length > max){
		j.val(j.val().substr(0, max));
	}
}


// --- enables or disables limitLength
O_O.Field.Textarea.prototype.limitLengthSwitch = function(off) {
	
	if(off) {
		this.j.field.unbind('keyup.maxlength');
		return;	
	}
	
	this.j.field.bind('keyup.maxlength', this.limitInput);
}		
	





// ===========================================================================
