1 /**
  2  * @fileOverview Form Functions & Classes
  3  * @author <a href="http://www.bobs-bits.com">Stephen Reay</a>
  4  */
  5 
  6 /**
  7  * @namespace Form and Form Element functionality
  8  * @requires BTM.DOM
  9  */
 10 BTM.Form = {};
 11 
 12 /**
 13  * Get the value from a form element. If element is a Multi-Select, all selected values are returned in an Array
 14  * @param {HTMLElement|String} element element ID or element reference to the form element
 15  * @returns {String|String[]} the element value. Multi-Select elements return an Array of selected option values
 16  */
 17 BTM.Form.getValue = function getValue(element) {
 18 	element = BTM.$(element);
 19 	BTM.log("Getting form field value", element);
 20 	
 21 	if (!Object.isElement(element) && element.hasOwnProperty(0) && Object.isElement(element[0], 'input')) {
 22 		return Object.toArray(element).map(BTM.Form.getValue);
 23 	}
 24 	switch(element.type) {
 25 		case 'checkbox':
 26 		case 'radio':
 27 				if (BTM.Form.Checkbox.isChecked(element)) {
 28 					return element.value;
 29 				}
 30 				
 31 			break;
 32 		
 33 		case 'select-multiple':
 34 			var vals = [];
 35 			for (var i = 0; i < element.options.length; i++) {
 36 				if (element.options[i].selected) {
 37 					vals.push(element.options[i].value);
 38 				}
 39 			}
 40 			
 41 			return vals;
 42 			break;
 43 		
 44 		default:
 45 			return element.value;
 46 			break;
 47 	}
 48 };
 49 
 50 /**
 51  * @namespace Form Select element functionality
 52  */
 53 BTM.Form.Select = {};
 54 	
 55 /**
 56  * Add a new Option to a Select element
 57  * @param {HTMLElement|String} element element ID or element reference to add the Option to
 58  * @param {String|Object} text the display text for the new Option, or an object with text and value properties
 59  * @param {String} [value] the value attribute for the new Option (ignored if text is an Object with text and value properties
 60  * @returns {HTMLElement} the original element
 61  */
 62 BTM.Form.Select.addOption = function addOption(element, text, value, defaultSelected, selected) {
 63 	element = BTM.$(element);
 64 	var delayOnChange = false;
 65 	
 66 	defaultSelected = defaultSelected || false;
 67 	selected = selected || false; 
 68 	if (Object.isArray(text)) {
 69 		text.forEach(BTM.Form.Select.addOption.curry(element));
 70 	}
 71 	else if (Object.isObject(text)) {
 72 		delayOnChange = true;
 73 		value = text.value;
 74 		if (typeof value === 'object') {
 75 			value = value.value;
 76 		}
 77 		text = text.text || text.key;
 78 	}
 79 /* 	value = value || text; */
 80 	var option = new Option(text, value, defaultSelected, selected);
 81 	
 82 	BTM.log("Adding Option ('" + text + "':'" + value + "')", element);
 83 	try {
 84 		element.add(option, null);
 85 	}
 86 	catch(e) {	//Explorer doesn't like the second argument
 87 		element.add(option);
 88 	}
 89 	
 90 	if (!delayOnChange) {
 91 		BTM.Event.fire(element, 'change');
 92 	}
 93 	
 94 	return element;
 95 };
 96 
 97 /**
 98  * Worker function for copying and moving Options between Select elements
 99  * @private
100  * @param {HTMLElement|String} element element ID or element reference to copy/move the Option from
101  * @param {HTMLElement|String} destination element ID or element reference to copy/move the Option to
102  * @param {Boolean} [autoSort=true] flag to automatically sort the Options in the destination Select element
103  * @param {Boolean} [moveMode=false] flag to move an Option rather than copying it
104  * @returns {HTMLElement} the original (source) element
105  * @see BTM.Form.Select.copySelectedOptions
106  * @see BTM.Form.Select.copyAllOptions
107  * @see BTM.Form.Select.moveSelectedOptions
108  * @see BTM.Form.Select.moveAllOptions
109  * @see BTM.Form.Select.removeSelectedOptions
110  */
111 BTM.Form.Select.optionWorker = function optionWorker(element, destination, autoSort, moveMode) {
112 	element = BTM.$(element);
113 	destination = BTM.$(destination);
114 
115 	moveMode = moveMode || false;
116 	autoSort = autoSort || true;
117 	
118 	for (var i = 0; i < element.options.length; i++) {
119 		if (element.options[i].selected) {
120 			if(destination) {
121 				BTM.Form.Select.addOption(destination, element.options[i].text, element.options[i].value);
122 			}
123 			
124 			if (moveMode) {
125 				BTM.log("Removing Option ", element.options[i], element);
126 				element.remove(i);
127 			}
128 		}
129 	}
130 	
131 	if (autoSort) {
132 		BTM.Form.Select.sort(destination);
133 	}
134 	
135 	if (moveMode) {
136 		BTM.Event.fire(element, 'change');
137 	}
138 	if (destination) {
139 		BTM.Event.fire(destination, 'change');
140 	}
141 	
142 	return element;
143 };
144 
145 /**
146  * Select all Options in a Select element
147  * @param {HTMLElement|String} element element ID or element reference to copy/move the Option from
148  * @returns {HTMLElement} the original element
149  */
150 BTM.Form.Select.selectAll = function selectAll(element) {
151 	element = BTM.$(element);
152 	
153 	for (var i = 0; i < element.options.length; i++) {
154 		element.options[i].selected = 'selected';
155 	}
156 	
157 	return element;
158 };
159 
160 /**
161  * Copy the selected Options from one Select element to another
162  * @param {HTMLElement|String} element element ID or element reference to copy the Options from
163  * @param {HTMLElement|String} destination element ID or element reference to copy the Options to
164  * @param {Boolean} [autoSort=true] flag to automatically sort the Options in the destination Select element
165  * @returns {HTMLElement} the original (source) element
166  */
167 BTM.Form.Select.copySelectedOptions = function copySelectedOptions(element, destination, autoSort) {
168 	return BTM.Form.Select.optionWorker(element, destination, autoSort, false);
169 };
170 
171 /**
172  * Copy all Options from one Select element to another
173  * @param {HTMLElement|String} element element ID or element reference to copy the Options from
174  * @param {HTMLElement|String} destination element ID or element reference to copy the Options to
175  * @param {Boolean} [autoSort=true] flag to automatically sort the Options in the destination Select element
176  * @returns {HTMLElement} the original (source) element
177  */
178 BTM.Form.Select.copyAllOptions = function copyAllOptions(element, destination, autoSort) {
179 	BTM.Form.Select.selectAll(element);
180 	return BTM.Form.Select.optionWorker(element, destination, autoSort, false);
181 };
182 
183 /**
184  * Move the selected Options from one Select element to another
185  * @param {HTMLElement|String} element element ID or element reference to move the Option from
186  * @param {HTMLElement|String} destination element ID or element reference to move the Option to
187  * @param {Boolean} [autoSort=true] flag to automatically sort the Options in the destination Select element
188  * @returns {HTMLElement} the original (source) element
189  */		
190 BTM.Form.Select.moveSelectedOptions = function moveSelectedOptions(element, destination, autoSort) {
191 	return BTM.Form.Select.optionWorker(element, destination, autoSort, true);
192 };
193 
194 /**
195  * Move all Options from one Select element to another
196  * @param {HTMLElement|String} element element ID or element reference to copy the Options from
197  * @param {HTMLElement|String} destination element ID or element reference to copy the Options to
198  * @param {Boolean} [autoSort=true] flag to automatically sort the Options in the destination Select element
199  * @returns {HTMLElement} the original (source) element
200  */
201 BTM.Form.Select.moveAllOptions = function moveAllOptions(element, destination, autoSort) {
202 	BTM.Form.Select.selectAll(element);
203 	return BTM.Form.Select.optionWorker(element, destination, autoSort, true);
204 };
205 
206 /**
207  * Remove the selected Options from a Select element
208  * @param {HTMLElement|String} element element ID or element reference to remmove the Options from
209  * @returns {HTMLElement} the original (source) element
210  */		
211 BTM.Form.Select.removeSelectedOptions = function removeSelectedOptions(element) {
212 	return BTM.Form.Select.optionWorker(element, false, false, true);
213 };
214 
215 /**
216  * Remove all Options from a Select element
217  * @param {HTMLElement|String} element element ID or element reference to remmove the Options from
218  * @returns {HTMLElement} the original (source) element
219  */
220 BTM.Form.Select.removeAllOptions = function removeAllOptions(element) {
221 	element = BTM.$(element);
222 	element.options.length = 0;
223 	BTM.Event.fire(element, 'change');
224 };
225 
226 
227 /**
228  * Sort Options in a Select element
229  * @param {HTMLElement|String} element element ID or element reference to sort the Options of
230  * @returns {HTMLElement} the original element
231  */
232 BTM.Form.Select.sort = function sort(element) {	
233 	element = BTM.$(element);
234 	
235 	var opts = [];
236 	if (element.options.length > 0) {
237 		for (var i = 0; i <element.options.length; i++) {
238 			opts.push(new Option( element.options[i].text, element.options[i].value, element.options[i].defaultSelected, element.options[i].selected));
239 		}
240 		
241 		BTM.Form.Select.removeAllOptions(element, false);
242 		
243 		opts = opts.sort(
244 			function (a,b) {
245 				if ((a.text.toLowerCase()+"") < (b.text.toLowerCase()+"")) {
246 					return -1;
247 				}
248 				if ((a.text.toLowerCase()+"") > (b.text.toLowerCase()+"")) {
249 					return 1;
250 				}
251 				return 0;
252 			}
253 		);
254 	
255 		opts.forEach(function (item) {
256 			BTM.Form.Select.addOption(element, item.text, item.value, item.defaultSelected, item.selected);
257 		});
258 	}
259 	return element;
260 };
261 
262 /**
263  * Automatically updates the Options available in each "child" Select when a "Parent" Select is changed
264  * @class Dependant Drop List object
265  * @param {HTMLElement[]|String[]} elements Array of element IDs or element references to bind to
266  * @param {Object} [options] options for the Dependant Drop List
267  * @param {Boolean} [options.sort=true] flag to sort the Options in each Destination Select after populating it
268  * @todo
269  * Accept more default selected Options for child Select elements.
270  */
271 BTM.Form.Select.Dependant = function Dependant(elements, options) {
272 	function initData(index, filter) {
273 		index = index || 0;
274 		
275 		var element = this.selectElements[index];
276 		var data = {};
277 
278 		for (var i = 0; i < element.options.length; i++) {
279 			if (Object.isUndefined(filter) || BTM.DOM.hasClass(element.options[i], filter)) {
280 				var subData = index+1 < this.selectElements.length ? initData.call(this, index+1, element.options[i].value) : false;
281 
282 				data[element.options[i].text] = {
283 					'value':element.options[i].value,
284 					'defaultSelected' : element.options[i].selected,
285 					'children':subData
286 				};
287 			}
288 		}
289 		
290 		return data;
291 	}
292 
293 	/**
294 	 * Display the relevant Options for each Select starting at specified index
295 	 * @param {Number} [index=0] the Select element to start from (starting at 0)
296 	 */
297 	this.display = function display(index) {
298 		index = index || 0;			
299 		if (index+1 < this.selectElements.length) {
300 			this.selectElements[index+1].disabled = false;
301 			BTM.Form.Select.removeAllOptions(this.selectElements[index+1]);
302 			
303 			var data = this.data;
304 			
305 			for (var i = 1; i <= index; i++) {
306 				var levelData = {};
307 				var selected = BTM.Form.getValue(this.selectElements[i-1]);
308 					Object.forEach(data, function (entry, name) {
309 					if (selected.inArray(entry.value)) {
310 						Object.update(levelData, entry.children);
311 					}
312 				});
313 				data = levelData;
314 			}
315 			
316 			for (var j = 0; j < this.selectElements[index].options.length; j++) {
317 				if (this.selectElements[index].options[j].selected) {
318 					var entries = data[this.selectElements[index].options[j].text].children || false;
319 					for (var k in entries) {
320 						if (entries.hasOwnProperty(k)) {
321 							BTM.Form.Select.addOption(this.selectElements[index+1], k, entries[k].value, entries[k].defaultSelected, false);
322 						}
323 					}
324 				}
325 			}
326 			
327 			this.selectElements[index+1].selectedIndex = -1;
328 			if (this.selectElements[index+1].options.length === 0) {
329 				this.selectElements[index+1].disabled = true;
330 			}
331 			else if (this.options.sort) {
332 				BTM.Form.Select.sort(this.selectElements[index+1]);
333 			}
334 
335 			BTM.Event.fire(this.selectElements[index+1], 'change');
336 		}
337 		
338 	};
339 
340 	function observe(element, index) {
341 		BTM.observe(element, 'change', this.display.bind(this, index));
342 	}
343 
344 	/**
345 	 * The Select elements
346 	 * @type {Array}
347 	 */
348 	this.selectElements = elements.map(BTM.$);
349 	
350 	/**
351 	 * Options Object
352 	 * @type {Object}
353 	 */
354 	this.options = Object.update({'sort':true}, options);
355 
356 	/**
357 	 * The data extracted from the Select elements
358 	 * @type {Object}
359 	 */
360 	this.data = initData.call(this);
361 	
362 	this.selectElements.forEach(observe, this);
363 	
364 	this.display();
365 };
366 
367 /**
368  * @namespace Form Text field functionality
369  */
370 BTM.Form.Text = {};
371 
372 /**
373  * Convenience method to check if an input field is empty
374  * @param {HTMLElement|String} element element ID or element reference to check
375  * @returns {Boolean} true if element is empty, false if not
376  */
377 BTM.Form.Text.empty = function empty(element) {
378 	element = BTM.$(element);
379 	return element.value.length === 0;
380 };
381 
382 /**
383  * Convenience Function to create a new Placeholder object
384  * @param {HTMLElement|String} element element ID or element reference to create a Placeholder for
385  * @returns {BTM.Form.Text.Placeholder} new Placeholder object
386  * @see BTM.Form.Text.Placeholder
387  */
388 BTM.Form.Text.makePlaceholder = function makePlaceholder(element) {
389 	return new BTM.Form.Text.Placeholder(element);
390 };
391 
392 BTM.Mapping.add('input[type=text], textarea', BTM.Form.Text.makePlaceholder);
393 
394 /**
395  * @class Text field Placeholders
396  * @param {HTMLElement} element element ID or element reference to initialise Placeholder on
397  * @param {String} [placeholderText] overide the default Placeholder text (read from title attribute) for an Input
398  * @todo
399  * Add onsubmit handler for forms.
400  */
401 BTM.Form.Text.Placeholder = function Placeholder(element, placeholderText) {
402 	/**
403 	 * The Input element
404 	 * @type HTMLElement
405 	 */
406 	this.element = BTM.$(element);
407 	
408 	this.element.Placeholder = this;
409 	
410 	/**
411 	 * The placeholder text (false if none)
412 	 * @type String|Boolean
413 	 */
414 	this.placeholderText = placeholderText || (BTM.DOM.hasAttribute(this.element, 'title') ? this.element.getAttribute('title') : false);
415 	
416 	/**
417 	 * Indicates if placeholder text is showing
418 	 * @type Boolean
419 	 */
420 	this.placeholderActive = false;
421 	
422 	/**
423 	 * Show the placeholder text
424 	 */
425 	this.show = function show() {
426 		if (BTM.Form.Text.empty(this.element)) {
427 			BTM.log("Showing Placeholder text", element);
428 			this.element.value = this.placeholderText;
429 			BTM.DOM.setStyle(this.element, 'color', '#999999');
430 			this.placeholderActive = true;
431 		}
432 		return element;
433 	};
434 	
435 	/**
436 	 * Hide the placeholder text
437 	 */
438 	this.hide = function hide() {
439 		if (this.placeholderActive) {
440 			BTM.log("Hiding Placeholder text", element);
441 			this.element.value = '';
442 			BTM.DOM.setStyle(this.element, 'color', '');
443 			this.placeholderActive = false;
444 		}
445 	};
446 		
447 	if (this.placeholderText) {
448 		BTM.log("Adding Placeholder properties and handlers", element);
449 
450 		BTM.observe(this.element, 'focus', this.hide.bind(this));
451 		BTM.observe(this.element, 'blur', this.show.bind(this));
452 		
453 		this.show();
454 	}
455 };
456 
457 /**
458  * @namespace Form Button functionality
459  */
460 BTM.Form.Button = {};
461 
462 /**
463  * Convenience Function to create a Branded Button
464  * @param {HTMLElement|String} element element ID or element reference to create a Branded Button from
465  * @returns {BTM.Form.Button.Branded} new Branded Button object
466  * @see BTM.Form.Button.Branded
467  */
468 BTM.Form.Button.makeBranded = function makeBranded(element) {
469 	return new BTM.Form.Button.Branded(element);
470 };
471 
472 BTM.Mapping.add('button, input[type=button], input[type=submit], input[type=reset], input[type=image]', BTM.Form.Button.makeBranded);
473 
474 /**
475  * @class Branded Button class
476  * @param {HTMLElement|String} element element ID or element reference to create a Branded Button from
477  */
478 BTM.Form.Button.Branded = function Branded(element) {
479 	this.baseClassName = 'btm-button';
480 	
481 	/**
482 	 * The actual button element
483 	 * @type {HTMLElement}
484 	 */
485 	this.button = BTM.$(element);
486 	
487 	if (BTM.DOM.hasClass(this.button.parentNode.parentNode, this.baseClassName)) {
488 		return false;
489 	}
490 	
491 	/**
492 	 * The disabled state of the Branded Button
493 	 * @type {Boolean}
494 	 */
495 	this.disabled = this.button.getAttribute('disabled') === 'disabled';
496 	
497 	if (this.button.tagName.toLowerCase() === 'input') {
498 		var oldOnClick = this.button.onclick || false;
499 		this.buttonIcon = BTM.DOM.getAttribute(this.button, 'type') === 'image' ? BTM.DOM.getAttribute(this.button, ['src', 'alt']) : false;
500 		this.button = BTM.DOM.morph(this.button, 'button');
501 		if (this.buttonIcon) {
502 			BTM.DOM.setAttribute(this.button, 'type', 'submit');
503 			this.buttonIcon = BTM.DOM.createElement('img',this.buttonIcon);
504 			if(BTM.Browser.engine === 'Trident' && BTM.Browser.engineVersion <= 6 && /\.png$/i.test(this.buttonIcon.src)) {
505 				BTM.Compatibility.MSIE.Six.fixPNG(this.buttonIcon);
506 			}
507 			
508 			this.button.insertBefore(this.buttonIcon, this.button.firstChild);
509 		}
510 	}
511 	
512 	/**
513 	 * The outer-most element of the Branded Button
514 	 * @type {HTMLElement}
515 	 */
516 	this.element = BTM.DOM.wrap(BTM.DOM.wrap(this.button, 'span'),'span', {'class':this.baseClassName});
517 	BTM.DOM.addClass(this.element, BTM.DOM.getAttribute(this.button, 'class'));
518 	
519 	if (oldOnClick) {
520 			BTM.observe(this.button, 'click', oldOnClick.bind(this.element));
521 	}
522 	
523 	BTM.DOM.makeUnselectable(this.element);
524 	
525 	/**
526 	 * Disable the button
527 	 */
528 	this.disable = function disable() {
529 		BTM.DOM.addClass(this.element, 'disabled');					
530 		this.button.disabled = true;
531 		this.disabled = true;
532 	};
533 
534 	/**
535 	 * Enable the button
536 	 */
537 	this.enable = function enable() {
538 		BTM.DOM.removeClass(this.element, 'disabled');
539 		this.button.disabled = false;
540 		this.disabled = false;
541 	};
542 };
543 
544 /**
545  * @namespace Form Checkbox functionality
546  */
547 BTM.Form.Checkbox = {};
548 
549 /**
550  * Add a "Select All" handler to a checkbox
551  * @param {HTMLElement|String} element element ID or element reference to the "Select All" checkbox
552  * @param {String} selector the CSS selector string representing the elements to be checked/unchecked
553  * @param {HTMLElement|String} context element ID or element reference to the context in which to apply the selector when finding checkboxes
554  */
555 BTM.Form.Checkbox.initSelectAll = function initSelectAll(element, selector, context) {
556 	element = BTM.$(element);
557 	context = context || element.form;
558 	
559 	BTM.observe(element, 'click', BTM.Form.Checkbox.selectAll.curry(element, selector, context));
560 };
561 
562 /**
563  * Toggle multiple checkboxes to match the state of a single checkbox
564  * @param {HTMLElement|String} element element ID or element reference to the "Select All" checkbox
565  * @param {String} selector the CSS selector string representing the elements to be checked/unchecked
566  * @param {HTMLElement|String} context element ID or element reference to the context in which to apply the selector when finding checkboxes
567  */
568 BTM.Form.Checkbox.selectAll = function selectAll(element, selector, context) {
569 	element = BTM.$(element);
570 	context = context || element.form;
571 	var checks = BTM.$$(selector, context);
572 	
573 	var func = element.checked ? BTM.Form.Checkbox.check : BTM.Form.Checkbox.unCheck;
574 	checks.forEach(func);
575 };
576 
577 /**
578  * Toggle a checkbox (simulates the element being clicked
579  * @param {HTMLElement|String} element element ID or element reference to the checkbox
580  * @param {Boolean} [force=false] force the checkbox to be toggled, ignoring if it is disabled or not
581  * @returns {HTMLElement} reference to the checkbox
582  */
583 BTM.Form.Checkbox.toggle = function toggle(element, force) {
584 	element = BTM.$(element);
585 	force = force || false;
586 	if (!element.disabled || force) {
587 		element.checked = !element.checked;
588 	}
589 
590 	return element;	
591 };
592 
593 /**
594  * Set a checkbox to checked
595  * @param {HTMLElement|String} element element ID or element reference to the checkbox
596  * @returns {HTMLElement} reference to the checkbox
597  */
598 BTM.Form.Checkbox.check = function check(element) {
599 	element = BTM.$(element);
600 	element.checked = true;
601 	return element;
602 };
603 
604 /**
605  * Set a checkbox to unchecked
606  * @param {HTMLElement|String} element element ID or element reference to the checkbox
607  * @returns {HTMLElement} reference to the checkbox
608  */
609 BTM.Form.Checkbox.unCheck = function unCheck(element) {
610 	element = BTM.$(element);
611 	element.checked = false;
612 	return element;
613 };
614 
615 /**
616  * Check if a checkbox is checked
617  * @param {HTMLElement|String} element element ID or element reference to the checkbox
618  * @returns {Boolean} the checked state of the element
619  */
620 BTM.Form.Checkbox.isChecked = function isChecked(element) {
621 	element = BTM.$(element);
622 	return element.checked;
623 };
624 
625 
626 /**
627  * @class Basic Form field checker class
628  * @param {HTMLElement|String} element element ID or element reference to the form to be validated
629  * @param {Array} [rules] the validation rules to check
630  */
631 BTM.Form.FieldRules = function FieldRules(form, rules) {
632 	
633 	this.classPrefix = 'require-';
634 	this.classPrefixRegex = new RegExp('^' + this.classPrefix);
635 	
636 	/**
637 	 * Initialize the FieldRules
638 	 * @param {HTMLElement|String} form elemend ID or reference to the Form element
639 	 * @param {Object[]} [rules] Validation rules to be applied
640 	 */
641 	this.init = function init(form, rules) {
642 		this.form = BTM.$(form);
643 		this.rules = this.readRules().concat(rules);
644 	};
645 	
646 	/**
647 	 * Common field validation rules
648 	 * Currently defined types are: 'number', 'number-or-space', 'not-number', 'empty', 'empty-or-space', 'not-empty', 'not-empty-or-space', 'alphanumeric', 'alphanumeric-or-space', 'alpha-and-numeric', 'email', 'checked', 'not-checked', 'one-checked', 'all-checked', 'none-checked', 'one-selected', 'none-selected'
649 	 * @name BTM.Form.FieldRules#commonTypes
650 	 */
651 	this.commonTypes = {
652 		'number' : {
653 			'require' : /^[\d]+$/,
654 			'message' : 'Must be a number'
655 		},
656 		'number-or-space' : {
657 			'require' : /^[\d\s]+$/,
658 			'message' : 'Must be a number or a space'
659 		},
660 		'not-number' : {
661 			'require' : /^[^\d]+$/,
662 			'message' : 'Must not be a number'
663 		},
664 		'empty' : {
665 			'require' : /^$/,
666 			'message' : 'Must be empty'
667 		},
668 		'empty-or-space' : {
669 			'require' : /^\s*$/,
670 			'message' : 'Must be empty or space only'
671 		},
672 		'not-empty' : {
673 			'require' : /^(.|\s)+$/,
674 			'message' : 'Must not be empty'
675 		},
676 		'not-empty-or-space' : {
677 			'require' : /^[^\s]+$/,
678 			'message' : 'Must not be empty or space only'
679 		},
680 		'alphanumeric' : {
681 			'require' : /^[\w]+$/,
682 			'message' : 'Must be AlphaNumeric (letters and/or numbers)'
683 		},
684 		'alphanumeric-or-space' : {
685 			'require' : /^[\w\s]+$/,
686 			'message' : 'Must be AlphaNumeric (letters/numbers) or space'
687 		},
688 		'alpha-and-numeric' : {
689 			'require' : /[\d]+[a-z]+|[a-z]+[\d]+/gi,
690 			'message' : 'Must contain letters and numbers'
691 		},
692 		'email' : {
693 			'require' : /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i,
694 			'message' : 'Must be a valid Email address'
695 		},
696 		'checked' : {
697 			'require' : function(element) {
698 					return BTM.Form.Checkbox.isChecked(element);
699 				},
700 			'message' : 'Must be checked'
701 		},
702 		'not-checked' : {
703 			'require' : function(element) {
704 					return !BTM.Form.Checkbox.isChecked(element);
705 				},
706 			'message' : 'Must not be checked'
707 		},		
708 		'one-checked' : {
709 			'require' : function(element) {
710 					var checks = Object.toArray(element.form[element.name]);
711 					return checks.some(BTM.Form.Checkbox.isChecked);
712 				},
713 			'message' : 'Must have one checked'
714 		},
715 		'all-checked' : {
716 			'require' : function(element) {
717 					var checks = Object.toArray(element.form[element.name]);
718 					return checks.every(BTM.Form.Checkbox.isChecked);
719 				},
720 			'message' : 'Must all be checked'
721 		},
722 		'none-checked' : {
723 			'require' : function(element) {
724 					var checks = Object.toArray(element.form[element.name]);
725 					return !checks.some(BTM.Form.Checkbox.isChecked);
726 				},
727 			'message' : 'Must have none checked'
728 		},
729 		'one-selected' : {
730 			'require' : function(element) {
731 					return element.selectedIndex !== -1;
732 				},
733 			'message' : 'Must have one selected'
734 		},
735 		'none-selected' : {
736 			'require' : function(element) {
737 					return element.selectedIndex === -1;
738 				},
739 			'message' : 'Must have none selected'
740 		}
741 	};
742 	
743 	
744 	
745 	/**
746 	 * Run the validation checks
747 	 */
748 	this.runChecks = function runChecks() {
749 		BTM.log("Running FieldRules checks for '" + this.form + "'");
750 		this.result = this.rules.map(this.checkRule, this);
751 		if (!this.result.every(function(res){return res.result;})) {
752 			return false;
753 		}
754 		return true;
755 	};
756 	
757 	/**
758 	 * Read the rules from Form elements
759 	 * @returns {Array} the rules collected from the Form elements
760 	 */
761 	this.readRules = function readRules() {
762 		var elements = this.form.elements;
763 		var rules = [];
764 		for (var i = 0; i < elements.length; i++) {
765 			var classes = BTM.DOM.getAttribute(elements[i], 'class');
766 			if (this.classPrefixRegex.test(classes)) {
767 				classes = classes.split(' ').filter(function (c) {
768 					return this.classPrefixRegex.test(c);
769 				}, this);
770 				
771 				if(classes.length > 0) {
772 					var els = /checked/.test(classes[0]) ? '[name=' + elements[i].name + ']' : '#' + BTM.DOM.identify(elements[i]);
773 					rules.push(Object.update({
774 						'elements' : els
775 					}, this.commonTypes[classes[0].replace(this.classPrefix, '')]));
776 				}
777 			}
778 		}
779 		return rules;
780 	};
781 	
782 	/**
783 	 * Check an individual Validation rule
784 	 */
785 	this.checkRule = function checkRule(rule) {
786 		var elements = BTM.$$(rule.elements);
787 		var condition = rule.condition || false;
788 		var require = rule.require;
789 		var values = elements.map(BTM.Form.getValue);
790 		var returnVal = true;
791 		var message = rule.message || false;
792 		
793 		if(!condition || condition.map(this.checkRule, this).every(function(res){return res.result;})) {
794 			if (Object.isString(require)) {
795 				if (this.commonTypes.hasOwnProperty(require)) {
796 					message = this.commonTypes[require].message;
797 					require = this.commonTypes[require].require;
798 				}
799 				else {
800 					require = new RegExp(require,'gi');
801 				}
802 			}
803 		
804 			if (Object.isRegExp(require)) {
805 				returnVal = values.every(function(val) {
806 					return require.test(val);
807 				});
808 			}
809 			else if (Object.isFunction(require)) {
810 				returnVal = elements.every(require);
811 			}
812 		}
813 		
814 		var result = Object.merge(rule, {
815 			'elements':elements,
816 			'condition':condition,
817 			'require': require,
818 			'values':values,
819 			'message' : message,
820 			'result':returnVal
821 		});
822 		
823 		return result;
824 	};
825 	
826 	if (!Object.isUndefined(form)) {
827 		this.init(form, rules);
828 	}	
829 };
830 
831 /**
832  * Convenience Function to create a Form Validator
833  * @param {HTMLElement|String} element element ID or element reference to Form to be validated
834  * @returns {BTM.Form.Validator} new Form Validator object
835  * @see BTM.Form.Validator
836  */
837 BTM.Form.makeValidator = function makeValidator(element) {
838 	return new BTM.Form.Validator(element);
839 };
840 
841 BTM.Mapping.add('form.btm-validation', BTM.Form.makeValidator);
842 
843 /**
844  * @class Form validation Class
845  * @augments BTM.Form.FieldRules
846  * @param {HTMLElement|String} element element ID or element reference to the form to be validated
847  * @param {Array} [rules] the validation rules to check
848  * @param {Function} [errorHandler] custom callback to run on each defined rule that evaluates as true
849  * @param {Function} [successHandler] custom callback to run on each defined rule that evaluates as false	 
850  */
851 BTM.Form.Validator = function Validator(form, rules, errorHandler, successHandler) {
852 	this.inherits(BTM.Form.FieldRules);
853 
854 	if (!Object.isUndefined(form)) {
855 		this.init(form, rules);
856 	}	
857 
858 
859 	/**
860 	 * Custom error/success handlers
861 	 * @type Object
862 	 */
863 	this.customHandlers = {
864 		'onError' : errorHandler || false,
865 		'onSuccess' : successHandler || false
866 	};
867 
868 	/**
869 	 * Default onError handler
870 	 */
871 	this.onError = function onError(rule) {
872 		if (!rule.result) {
873 			if (this.customHandlers.onError) {
874 				this.customHandlers.onError(rule);
875 			}
876 			else {
877 				rule.elements.forEach(function(el) {
878 					BTM.DOM.setStyle(el, 'background-color', 'red');
879 					BTM.DOM.addClass(el, 'error');
880 				});	
881 			}		
882 		}
883 	};
884 	
885 	/**
886 	 * Default onSuccess handler
887 	 */
888 	this.onSuccess = function onSuccess(rule) {
889 		if (rule.result) {
890 			if (this.customHandlers.onSuccess) {
891 				this.customHandlers.onSuccess(rule);
892 			}
893 			else {
894 				rule.elements.forEach(function(el) {
895 					BTM.DOM.setStyle(el, 'background-color', '');
896 					BTM.DOM.removeClass(el, 'error');
897 				});	
898 			}		
899 		}
900 	};
901 
902 	/**
903 	 * Run the Validation rules
904 	 */
905 	this.validate = function validate(event) {
906 		var res = this.runChecks();
907 		this.result.forEach(this.onError, this);
908 		this.result.forEach(this.onSuccess, this);
909 
910 		if (!res) {
911 			if (event) {
912 				BTM.Event.cancelEvent(event);
913 			}
914 			
915 			return false;
916 		}
917 		return true;
918 	};
919 	
920 	this.form.validate = this.validate.bind(this);
921 	BTM.observe(this.form, 'submit', this.validate.bindAsEventListener(this));
922 	
923 };
924 BTM.Form.Validator.inherits(BTM.Form.FieldRules);