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);