1/** 2 * Live Form Validation for Nette Forms 2.4 3 * 4 * @author Robert Pösel, zakrava, Radek JeždÃk, MartyIX, David Grudl 5 * @version 1.9.0-dev 6 * @url https://github.com/Robyer/nette-live-form-validation/ 7 */ 8 9var LiveForm = { 10 options: { 11 // CSS class of control's parent where error/valid class should be added; or "false" to use control directly 12 showMessageClassOnParent: 'form-group', 13 14 // CSS class of control's parent where error/valid message should be added (fallback to direct parent if not found); or "false" to use control's direct parent 15 messageParentClass: false, 16 17 // CSS class for an invalid control 18 controlErrorClass: 'has-error', 19 20 // CSS class for a valid control 21 controlValidClass: 'has-success', 22 23 // CSS class for an error message 24 messageErrorClass: 'help-block text-danger', 25 26 // control with this CSS class will show error/valid message even when control itself is hidden (useful for controls which are hidden and wrapped into special component) 27 enableHiddenMessageClass: 'show-hidden-error', 28 29 // control with this CSS class will have disabled live validation 30 disableLiveValidationClass: 'no-live-validation', 31 32 // control with this CSS class will not show valid message 33 disableShowValidClass: 'no-show-valid', 34 35 // tag that will hold the error/valid message 36 messageTag: 'span', 37 38 // message element id = control id + this postfix 39 messageIdPostfix: '_message', 40 41 // show this html before error message itself 42 messageErrorPrefix: ' <i class="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></i> ', 43 44 // show all errors when submitting form; or use "false" to show only first error 45 showAllErrors: true, 46 47 // show message when valid 48 showValid: false, 49 50 // delay in ms before validating on keyup/keydown; or use "false" to disable it 51 wait: false, 52 53 // vertical screen offset in px to scroll after focusing element with error (useful when using fixed navbar menu which may otherwise obscure the element in focus); or use "false" for default behavior 54 focusScreenOffsetY: false 55 }, 56 57 forms: { } 58}; 59 60LiveForm.setOptions = function(userOptions) { 61 for (var prop in userOptions) { 62 if (Object.prototype.hasOwnProperty.call(this.options, prop)) { 63 this.options[prop] = userOptions[prop]; 64 } 65 } 66} 67 68// Allow setting options before loading the script just by creating global LiveFormOptions object with options. 69if (typeof window.LiveFormOptions !== 'undefined') { 70 LiveForm.setOptions(window.LiveFormOptions); 71} 72 73LiveForm.isSpecialKey = function(k) { 74 // http://stackoverflow.com/questions/7770561/jquery-javascript-reject-control-keys-on-keydown-event 75 return (k == 20 /* Caps lock */ 76 || k == 16 /* Shift */ 77 || k == 9 /* Tab */ 78 || k == 27 /* Escape Key */ 79 || k == 17 /* Control Key */ 80 || k == 91 /* Windows Command Key */ 81 || k == 19 /* Pause Break */ 82 || k == 18 /* Alt Key */ 83 || k == 93 /* Right Click Point Key */ 84 || (k >= 35 && k <= 40) /* Home, End, Arrow Keys */ 85 || k == 45 /* Insert Key */ 86 || (k >= 33 && k <= 34) /*Page Down, Page Up */ 87 || (k >= 112 && k <= 123) /* F1 - F12 */ 88 || (k >= 144 && k <= 145)); /* Num Lock, Scroll Lock */ 89} 90 91/** 92 * Handlers for all the events that trigger validation 93 * YOU CAN CHANGE these handlers (ie. to use jQuery events instead) 94 */ 95LiveForm.setupHandlers = function(el) { 96 if (this.hasClass(el, this.options.disableLiveValidationClass)) 97 return; 98 99 // Check if element was already initialized 100 if (el.getAttribute("data-lfv-initialized")) 101 return; 102 103 // Remember we initialized this element so we won't do it again 104 el.setAttribute('data-lfv-initialized', 'true'); 105 106 var handler = function(event) { 107 event = event || window.event; 108 Nette.validateControl(event.target ? event.target : event.srcElement); 109 }; 110 111 var self = this; 112 113 Nette.addEvent(el, "change", handler); 114 Nette.addEvent(el, "blur", handler); 115 Nette.addEvent(el, "keydown", function (event) { 116 if (!self.isSpecialKey(event.which) && (self.options.wait === false || self.options.wait >= 200)) { 117 // Hide validation span tag. 118 self.removeClass(self.getGroupElement(this), self.options.controlErrorClass); 119 self.removeClass(self.getGroupElement(this), self.options.controlValidClass); 120 121 var messageEl = self.getMessageElement(this); 122 messageEl.innerHTML = ''; 123 messageEl.className = ''; 124 125 // Cancel timeout to run validation handler 126 if (self.timeout) { 127 clearTimeout(self.timeout); 128 } 129 } 130 }); 131 Nette.addEvent(el, "keyup", function(event) { 132 if (self.options.wait !== false) { 133 event = event || window.event; 134 if (event.keyCode !== 9) { 135 if (self.timeout) clearTimeout(self.timeout); 136 self.timeout = setTimeout(function() { 137 handler(event); 138 }, self.options.wait); 139 } 140 } 141 }); 142}; 143 144LiveForm.processServerErrors = function(el) { 145 var messageEl = this.getMessageElement(el); 146 var parentEl = this.getMessageParent(el); // This is parent element which contain the error elements 147 148 var errors = []; 149 150 // Find existing error elements by class (from server-validation) 151 var errorEls = parentEl.getElementsByClassName(this.options.messageErrorClass); 152 for (var i = errorEls.length - 1; i > -1; i--) { 153 // Don't touch our main message element 154 if (errorEls[i] == messageEl) 155 continue; 156 157 // Remove only direct children 158 var errorParent = errorEls[i].parentNode; 159 if (errorParent == parentEl) { 160 errors.push(errorEls[i].outerHTML); 161 errorParent.removeChild(errorEls[i]); 162 } 163 } 164 165 // Wrap all server errors into one element 166 if (errors.length > 0) { 167 messageEl.innerHTML = errors.join(""); 168 } 169}; 170 171LiveForm.addError = function(el, message) { 172 // Ignore elements with disabled live validation 173 if (this.hasClass(el, this.options.disableLiveValidationClass)) 174 return; 175 176 var groupEl = this.getGroupElement(el); 177 this.setFormProperty(el.form, "hasError", true); 178 this.addClass(groupEl, this.options.controlErrorClass); 179 180 if (this.options.showValid) { 181 this.removeClass(groupEl, this.options.controlValidClass); 182 } 183 184 if (!message) { 185 message = ' '; 186 } else { 187 message = this.options.messageErrorPrefix + message; 188 } 189 190 var messageEl = this.getMessageElement(el); 191 messageEl.innerHTML = message; 192 messageEl.className = this.options.messageErrorClass; 193}; 194 195LiveForm.removeError = function(el) { 196 // We don't want to remove any errors during onLoadValidation 197 if (this.getFormProperty(el.form, "onLoadValidation")) 198 return; 199 200 var groupEl = this.getGroupElement(el); 201 this.removeClass(groupEl, this.options.controlErrorClass); 202 203 var id = el.getAttribute('data-lfv-message-id'); 204 if (id) { 205 var messageEl = this.getMessageElement(el); 206 messageEl.innerHTML = ''; 207 messageEl.className = ''; 208 } 209 210 if (this.options.showValid) { 211 if (this.showValid(el)) 212 this.addClass(groupEl, this.options.controlValidClass); 213 else 214 this.removeClass(groupEl, this.options.controlValidClass); 215 } 216}; 217 218LiveForm.showValid = function(el) { 219 if (el.type) { 220 var type = el.type.toLowerCase(); 221 if (type == 'checkbox' || type == 'radio') { 222 return false;
223 } 224 } 225 226 var rules = Nette.parseJSON(el.getAttribute('data-nette-rules')); 227 if (rules.length == 0) { 228 return false; 229 } 230 231 if (Nette.getEffectiveValue(el) == '') { 232 return false; 233 } 234 235 if (this.hasClass(el, this.options.disableShowValidClass)) { 236 return false; 237 } 238 239 return true; 240}; 241 242LiveForm.getGroupElement = function(el) { 243 if (this.options.showMessageClassOnParent === false) 244 return el; 245 246 var groupEl = el; 247 248 while (!this.hasClass(groupEl, this.options.showMessageClassOnParent)) { 249 groupEl = groupEl.parentNode; 250 251 if (groupEl === null) { 252 return el; 253 } 254 } 255 256 return groupEl; 257} 258 259LiveForm.getMessageId = function(el) { 260 var tmp = el.id + this.options.messageIdPostfix; 261 262 // For elements without ID, or multi elements (with same name), we must generate whole ID ourselves 263 if (el.name && (!el.id || !el.form.elements[el.name].tagName)) { 264 // Strip possible [] from name 265 var name = el.name.match(/\[\]$/) ? el.name.match(/(.*)\[\]$/)[1] : el.name; 266 // Generate new ID based on form ID, element name and messageIdPostfix from options 267 tmp = (el.form.id ? el.form.id : 'frm') + '-' + name + this.options.messageIdPostfix; 268 } 269 270 // We want unique ID which doesn't exist yet 271 var id = tmp, 272 i = 0; 273 while (document.getElementById(id)) { 274 id = id + '_' + ++i; 275 } 276 277 return id; 278} 279 280LiveForm.getMessageElement = function(el) { 281 // For multi elements (with same name) work only with first element attributes 282 if (el.name && el.name.match(/\[\]$/)) { 283 el = el.form.elements[el.name].tagName ? el : el.form.elements[el.name][0]; 284 } 285 286 var id = el.getAttribute('data-lfv-message-id'); 287 if (!id) { 288 // ID is not specified yet, let's create a new one 289 id = this.getMessageId(el); 290 291 // Remember this id for next use 292 el.setAttribute('data-lfv-message-id', id); 293 } 294 295 var messageEl = document.getElementById(id); 296 if (!messageEl) { 297 // Message element doesn't exist, lets create a new one 298 messageEl = document.createElement(this.options.messageTag); 299 messageEl.id = id; 300 if (el.style.display == 'none' && !this.hasClass(el, this.options.enableHiddenMessageClass)) { 301 messageEl.style.display = 'none'; 302 } 303 304 var parentEl = this.getMessageParent(el); 305 if (parentEl) { 306 parentEl.appendChild(messageEl); 307 } 308 } 309 310 return messageEl; 311}; 312 313LiveForm.getMessageParent = function(el) { 314 var parentEl = el.parentNode; 315 var parentFound = false; 316 317 if (this.options.messageParentClass !== false) { 318 parentFound = true; 319 while (!this.hasClass(parentEl, this.options.messageParentClass)) { 320 parentEl = parentEl.parentNode; 321 322 if (parentEl === null) { 323 // We didn't found wanted parent, so use element's direct parent 324 parentEl = el.parentNode; 325 parentFound = false; 326 break; 327 } 328 } 329 } 330 331 // Don't append error message to radio/checkbox input's label, but along label 332 if (el.type) { 333 var type = el.type.toLowerCase(); 334 if ((type == 'checkbox' || type == 'radio') && parentEl.tagName == 'LABEL') { 335 parentEl = parentEl.parentNode; 336 } 337 } 338 339 // For multi elements (with same name) use parent's parent as parent (if wanted one is not found) 340 if (!parentFound && el.name && !el.form.elements[el.name].tagName) { 341 parentEl = parentEl.parentNode; 342 } 343 344 return parentEl; 345} 346 347LiveForm.addClass = function(el, className) { 348 if (!el.className) { 349 el.className = className; 350 } else if (!this.hasClass(el, className)) { 351 el.className += ' ' + className; 352 } 353}; 354 355LiveForm.hasClass = function(el, className) { 356 if (el.className) 357 return el.className.match(new RegExp('(\\s|^)' + className + '(\\s|$)')); 358 return false; 359}; 360 361LiveForm.removeClass = function(el, className) { 362 if (this.hasClass(el, className)) { 363 var reg = new RegExp('(\\s|^)'+ className + '(\\s|$)'); 364 var m = el.className.match(reg); 365 el.className = el.className.replace(reg, (m[1] == ' ' && m[2] == ' ') ? ' ' : ''); 366 } 367}; 368 369LiveForm.getFormProperty = function(form, propertyName) { 370 if (form == null || this.forms[form.id] == null) 371 return false; 372 373 return this.forms[form.id][propertyName]; 374}; 375 376LiveForm.setFormProperty = function(form, propertyName, value) { 377 if (form == null) 378 return; 379 380 if (this.forms[form.id] == null) 381 this.forms[form.id] = {}; 382 383 this.forms[form.id][propertyName] = value; 384}; 385
386//////////////////////////// modified netteForms.js /////////////////////////////////// 387 388/** 389 * NetteForms - simple form validation. 390 * 391 * This file is part of the Nette Framework (https://nette.org) 392 * Copyright (c) 2004 David Grudl (https://davidgrudl.com) 393 */ 394 395(function(global, factory) { 396 if (!global.JSON) { 397 return; 398 } 399 400 if (typeof define === 'function' && define.amd) { 401 define(function() { 402 return factory(global); 403 }); 404 } else if (typeof module === 'object' && typeof module.exports === 'object') { 405 module.exports = factory(global); 406 } else { 407 var init = !global.Nette || !global.Nette.noInit; 408 global.Nette = factory(global); 409 if (init) { 410 global.Nette.initOnLoad(); 411 } 412 } 413 414}(typeof window !== 'undefined' ? window : this, function(window) { 415 416'use strict'; 417 418var Nette = {}; 419 420// LiveForm: original netteForms.js code 421// Nette.formErrors = []; 422Nette.version = '2.4'; 423 424 425/** 426 * Attaches a handler to an event for the element. 427 */ 428Nette.addEvent = function(element, on, callback) { 429 if (element.addEventListener) { 430 element.addEventListener(on, callback); 431 } else if (on === 'DOMContentLoaded') { 432 element.attachEvent('onreadystatechange', function() { 433 if (element.readyState === 'complete') { 434 callback.call(this); 435 } 436 }); 437 } else { 438 element.attachEvent('on' + on, getHandler(callback)); 439 } 440}; 441 442 443function getHandler(callback) { 444 return function(e) { 445 return callback.call(this, e); 446 }; 447} 448 449 450/** 451 * Returns the value of form element. 452 */ 453Nette.getValue = function(elem) { 454 var i; 455 if (!elem) { 456 return null; 457 458 } else if (!elem.tagName) { // RadioNodeList, HTMLCollection, array 459 return elem[0] ? Nette.getValue(elem[0]) : null; 460 461 } else if (elem.type === 'radio') { 462 var elements = elem.form.elements; // prevents problem with name 'item' or 'namedItem' 463 for (i = 0; i < elements.length; i++) { 464 if (elements[i].name === elem.name && elements[i].checked) { 465 return elements[i].value; 466 } 467 } 468 return null; 469 470 } else if (elem.type === 'file') { 471 return elem.files || elem.value; 472 473 } else if (elem.tagName.toLowerCase() === 'select') { 474 var index = elem.selectedIndex, 475 options = elem.options, 476 values = []; 477 478 if (elem.type === 'select-one') { 479 return index < 0 ? null : options[index].value; 480 } 481 482 for (i = 0; i < options.length; i++) { 483 if (options[i].selected) { 484 values.push(options[i].value); 485 } 486 } 487 return values; 488 489 } else if (elem.name && elem.name.match(/\[\]$/)) { // multiple elements [] 490 var elements = elem.form.elements[elem.name].tagName ? [elem] : elem.form.elements[elem.name], 491 values = []; 492 493 for (i = 0; i < elements.length; i++) { 494 // LiveForm: original netteForms.js code 495 /*if (elements[i].type !== 'checkbox' || elements[i].checked) { 496 values.push(elements[i].value); 497 }*/ 498 // LiveForm: addition 499 var value = elements[i].value; 500 if (elements[i].type === 'checkbox' && elements[i].checked) { 501 values.push(value); 502 } else if (elements[i].type !== 'checkbox' && value !== '') { 503 values.push(value); 504 } 505 } 506 return values; 507 508 } else if (elem.type === 'checkbox') { 509 return elem.checked; 510 511 } else if (elem.tagName.toLowerCase() === 'textarea') { 512 return elem.value.replace("\r", ''); 513 514 } else { 515 return elem.value.replace("\r", '').replace(/^\s+|\s+$/g, ''); 516 } 517}; 518 519 520/** 521 * Returns the effective value of form element. 522 */ 523Nette.getEffectiveValue = function(elem) { 524 var val = Nette.getValue(elem); 525 if (elem.getAttribute) { 526 if (val === elem.getAttribute('data-nette-empty-value')) { 527 val = ''; 528 } 529 } 530 return val; 531}; 532 533 534/** 535 * Validates form element against given rules. 536 */ 537Nette.validateControl = function(elem, rules, onlyCheck, value, emptyOptional) { 538 // LiveForm: addition 539 // Fix for CheckboxList - validation rules are present always only on first input 540 if (elem.name && elem.name.match(/\[\]$/) && elem.type.toLowerCase() == 'checkbox') { 541 elem = elem.form.elements[elem.name].tagName ? elem : elem.form.elements[elem.name][0]; 542 } 543 544 elem = elem.tagName ? elem : elem[0]; // RadioNodeList 545 rules = rules || Nette.parseJSON(elem.getAttribute('data-nette-rules')); 546 value = value === undefined ? {value: Nette.getEffectiveValue(elem)} : value; 547 548 for (var id = 0, len = rules.length; id < len; id++) { 549 var rule = rules[id], 550 op = rule.op.match(/(~)?([^?]+)/), 551 curElem = rule.control ? elem.form.elements.namedItem(rule.control) : elem; 552 553 rule.neg = op[1]; 554 rule.op = op[2]; 555 rule.condition = !!rule.rules; 556 557 if (!curElem) { 558 continue; 559 } else if (rule.op === 'optional') { 560 emptyOptional = !Nette.validateRule(elem, ':filled', null, value); 561 continue; 562 } else if (emptyOptional && !rule.condition && rule.op !== ':filled') { 563 continue; 564 } 565 566 curElem = curElem.tagName ? curElem : curElem[0]; // RadioNodeList 567 var curValue = elem === curElem ? value : {value: Nette.getEffectiveValue(curElem)}, 568 success = Nette.validateRule(curElem, rule.op, rule.arg, curValue); 569 570 if (success === null) { 571 continue; 572 } else if (rule.neg) { 573 success = !success; 574 } 575 576 if (rule.condition && success) { 577 if (!Nette.validateControl(elem, rule.rules, onlyCheck, value, rule.op === ':blank' ? false : emptyOptional)) { 578 return false;
579 } 580 } else if (!rule.condition && !success) { 581 if (Nette.isDisabled(curElem)) { 582 continue; 583 } 584 if (!onlyCheck) { 585 var arr = Nette.isArray(rule.arg) ? rule.arg : [rule.arg], 586 message = rule.msg.replace(/%(value|\d+)/g, function(foo, m) { 587 return Nette.getValue(m === 'value' ? curElem : elem.form.elements.namedItem(arr[m].control)); 588 }); 589 Nette.addError(curElem, message); 590 } 591 return false; 592 } 593 } 594 595 if (elem.type === 'number' && !elem.validity.valid) { 596 if (!onlyCheck) { 597 Nette.addError(elem, 'Please enter a valid value.'); 598 } 599 return false; 600 } 601 602 // LiveForm: addition 603 if (!onlyCheck) { 604 LiveForm.removeError(elem); 605 } 606 607 return true; 608}; 609 610 611/** 612 * Validates whole form. 613 */ 614Nette.validateForm = function(sender, onlyCheck) { 615 var form = sender.form || sender, 616 scope = false; 617 618 // LiveForm: addition 619 LiveForm.setFormProperty(form, "hasError", false); 620 621 // LiveForm: original netteForms.js code 622 // Nette.formErrors = []; 623 624 if (form['nette-submittedBy'] && form['nette-submittedBy'].getAttribute('formnovalidate') !== null) { 625 var scopeArr = Nette.parseJSON(form['nette-submittedBy'].getAttribute('data-nette-validation-scope')); 626 if (scopeArr.length) { 627 scope = new RegExp('^(' + scopeArr.join('-|') + '-)'); 628 } else { 629 // LiveForm: original netteForms.js code 630 // Nette.showFormErrors(form, []); 631 return true; 632 } 633 } 634 635 var radios = {}, i, elem; 636 // LiveForm: addition 637 var success = true; 638 639 for (i = 0; i < form.elements.length; i++) { 640 elem = form.elements[i]; 641 642 if (elem.tagName && !(elem.tagName.toLowerCase() in {input: 1, select: 1, textarea: 1, button: 1})) { 643 continue; 644 645 } else if (elem.type === 'radio') { 646 if (radios[elem.name]) { 647 continue; 648 } 649 radios[elem.name] = true; 650 } 651 652 if ((scope && !elem.name.replace(/]\[|\[|]|$/g, '-').match(scope)) || Nette.isDisabled(elem)) { 653 continue; 654 } 655 656 // LiveForm: addition 657 success = Nette.validateControl(elem) && success; 658 if (!success && !LiveForm.options.showAllErrors) { 659 break; 660 } 661 // LiveForm: original netteForms.js code 662 /*if (!Nette.validateControl(elem, null, onlyCheck) && !Nette.formErrors.length) { 663 return false; 664 }*/ 665 } 666 // LiveForm: change 667 return success; 668 669 // LiveForm: original netteForms.js code 670 /*var success = !Nette.formErrors.length; 671 Nette.showFormErrors(form, Nette.formErrors); 672 return success;*/ 673}; 674 675 676/** 677 * Check if input is disabled. 678 */ 679Nette.isDisabled = function(elem) { 680 if (elem.type === 'radio') { 681 for (var i = 0, elements = elem.form.elements; i < elements.length; i++) { 682 if (elements[i].name === elem.name && !elements[i].disabled) { 683 return false; 684 } 685 } 686 return true; 687 } 688 return elem.disabled; 689}; 690 691// LiveForm: change 692/** 693 * Display error message. 694 */ 695Nette.addError = function(elem, message) { 696 // LiveForm: addition 697 var noLiveValidation = LiveForm.hasClass(elem, LiveForm.options.disableLiveValidationClass); 698 // User explicitly disabled live-validation so we want to show simple alerts 699 if (noLiveValidation) { 700 // notify errors for elements with disabled live validation (but only errors and not during onLoadValidation) 701 if (message && !LiveForm.getFormProperty(elem.form, "hasError") && !LiveForm.getFormProperty(elem.form, "onLoadValidation")) { 702 alert(message); 703 } 704 } 705 if (elem.focus && !LiveForm.getFormProperty(elem.form, "hasError")) { 706 if (!LiveForm.focusing) { 707 LiveForm.focusing = true; 708 elem.focus(); 709 setTimeout(function() { 710 LiveForm.focusing = false; 711 712 // Scroll by defined offset (if enabled) 713 // NOTE: We use it with setTimetout because IE9 doesn't always catch instant scrollTo request 714 var focusOffsetY = LiveForm.options.focusScreenOffsetY; 715 if (focusOffsetY !== false && elem.getBoundingClientRect().top < focusOffsetY) { 716 window.scrollBy(0, elem.getBoundingClientRect().top - focusOffsetY); 717 } 718 }, 10); 719 } 720 } 721 if (!noLiveValidation) { 722 LiveForm.addError(elem, message); 723 } 724}; 725 726 727// LiveForm: original netteForms.js code 728/** 729 * Adds error message to the queue. 730 */ 731/*Nette.addError = function(elem, message) { 732 Nette.formErrors.push({ 733 element: elem, 734 message: message 735 }); 736};*/ 737 738 739// LiveForm: original netteForms.js code 740/** 741 * Display error messages. 742 */ 743/*Nette.showFormErrors = function(form, errors) { 744 var messages = [], 745 focusElem; 746 747 for (var i = 0; i < errors.length; i++) { 748 var elem = errors[i].element, 749 message = errors[i].message; 750 751 if (!Nette.inArray(messages, message)) { 752 messages.push(message); 753 754 if (!focusElem && elem.focus) { 755 focusElem = elem; 756 } 757 } 758 } 759 760 if (messages.length) { 761 alert(messages.join('\n')); 762 763 if (focusElem) { 764 focusElem.focus(); 765 } 766 } 767};*/ 768 769 770/** 771 * Expand rule argument. 772 */ 773Nette.expandRuleArgument = function(form, arg) { 774 if (arg && arg.control) { 775 var control = form.elements.namedItem(arg.control), 776 value = {value: Nette.getEffectiveValue(control)}; 777 Nette.validateControl(control, null, true, value); 778 arg = value.value; 779 } 780 return arg; 781}; 782 783 784/** 785 * Validates single rule. 786 */ 787Nette.validateRule = function(elem, op, arg, value) { 788 value = value === undefined ? {value: Nette.getEffectiveValue(elem)} : value; 789 790 if (op.charAt(0) === ':') { 791 op = op.substr(1); 792 } 793 op = op.replace('::', '_'); 794 op = op.replace(/\\/g, ''); 795 796 var arr = Nette.isArray(arg) ? arg.slice(0) : [arg]; 797 for (var i = 0, len = arr.length; i < len; i++) { 798 arr[i] = Nette.expandRuleArgument(elem.form, arr[i]); 799 } 800 return Nette.validators[op] 801 ? Nette.validators[op](elem, Nette.isArray(arg) ? arr : arr[0], value.value, value) 802 : null; 803}; 804 805 806Nette.validators = { 807 filled: function(elem, arg, val) { 808 if (elem.type === 'number' && elem.validity.badInput) { 809 return true; 810 } 811 return val !== '' && val !== false && val !== null 812 && (!Nette.isArray(val) || !!val.length) 813 && (!window.FileList || !(val instanceof window.FileList) || val.length); 814 }, 815 816 blank: function(elem, arg, val) { 817 return !Nette.validators.filled(elem, arg, val); 818 }, 819 820 valid: function(elem, arg, val) { 821 return Nette.validateControl(elem, null, true); 822 }, 823 824 equal: function(elem, arg, val) { 825 if (arg === undefined) { 826 return null; 827 } 828 829 function toString(val) { 830 if (typeof val === 'number' || typeof val === 'string') { 831 return '' + val; 832 } else { 833 return val === true ? '1' : ''; 834 } 835 } 836 837 val = Nette.isArray(val) ? val : [val]; 838 arg = Nette.isArray(arg) ? arg : [arg]; 839 loop: 840 for (var i1 = 0, len1 = val.length; i1 < len1; i1++) { 841 for (var i2 = 0, len2 = arg.length; i2 < len2; i2++) { 842 if (toString(val[i1]) === toString(arg[i2])) { 843 continue loop; 844 } 845 } 846 return false;
847 } 848 return true; 849 }, 850 851 notEqual: function(elem, arg, val) { 852 return arg === undefined ? null : !Nette.validators.equal(elem, arg, val); 853 }, 854 855 minLength: function(elem, arg, val) { 856 if (elem.type === 'number') { 857 if (elem.validity.tooShort) { 858 return false 859 } else if (elem.validity.badInput) { 860 return null; 861 } 862 } 863 return val.length >= arg; 864 }, 865 866 maxLength: function(elem, arg, val) { 867 if (elem.type === 'number') { 868 if (elem.validity.tooLong) { 869 return false 870 } else if (elem.validity.badInput) { 871 return null; 872 } 873 } 874 return val.length <= arg; 875 }, 876 877 length: function(elem, arg, val) { 878 if (elem.type === 'number') { 879 if (elem.validity.tooShort || elem.validity.tooLong) { 880 return false 881 } else if (elem.validity.badInput) { 882 return null; 883 } 884 } 885 arg = Nette.isArray(arg) ? arg : [arg, arg]; 886 return (arg[0] === null || val.length >= arg[0]) && (arg[1] === null || val.length <= arg[1]); 887 }, 888 889 email: function(elem, arg, val) { 890 return (/^("([ !#-[\]-~]|\\[ -~])+"|[-a-z0-9!#$%&'*+\/=?^_`{|}~]+(\.[-a-z0-9!#$%&'*+\/=?^_`{|}~]+)*)@([0-9a-z\u00C0-\u02FF\u0370-\u1EFF]([-0-9a-z\u00C0-\u02FF\u0370-\u1EFF]{0,61}[0-9a-z\u00C0-\u02FF\u0370-\u1EFF])?\.)+[a-z\u00C0-\u02FF\u0370-\u1EFF]([-0-9a-z\u00C0-\u02FF\u0370-\u1EFF]{0,17}[a-z\u00C0-\u02FF\u0370-\u1EFF])?$/i).test(val); 891 }, 892 893 url: function(elem, arg, val, value) { 894 if (!(/^[a-z\d+.-]+:/).test(val)) { 895 val = 'http://' + val; 896 } 897 if ((/^https?:\/\/((([-_0-9a-z\u00C0-\u02FF\u0370-\u1EFF]+\.)*[0-9a-z\u00C0-\u02FF\u0370-\u1EFF]([-0-9a-z\u00C0-\u02FF\u0370-\u1EFF]{0,61}[0-9a-z\u00C0-\u02FF\u0370-\u1EFF])?\.)?[a-z\u00C0-\u02FF\u0370-\u1EFF]([-0-9a-z\u00C0-\u02FF\u0370-\u1EFF]{0,17}[a-z\u00C0-\u02FF\u0370-\u1EFF])?|\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}|\[[0-9a-f:]{3,39}\])(:\d{1,5})?(\/\S*)?$/i).test(val)) { 898 value.value = val; 899 return true; 900 } 901 return false; 902 }, 903 904 regexp: function(elem, arg, val) { 905 var parts = typeof arg === 'string' ? arg.match(/^\/(.*)\/([imu]*)$/) : false; 906 try { 907 return parts && (new RegExp(parts[1], parts[2].replace('u', ''))).test(val); 908 } catch (e) {} 909 }, 910 911 pattern: function(elem, arg, val) { 912 try { 913 return typeof arg === 'string' ? (new RegExp('^(?:' + arg + ')$')).test(val) : null; 914 } catch (e) {} 915 }, 916 917 integer: function(elem, arg, val) { 918 if (elem.type === 'number' && elem.validity.badInput) { 919 return false; 920 } 921 return (/^-?[0-9]+$/).test(val); 922 }, 923 924 'float': function(elem, arg, val, value) { 925 if (elem.type === 'number' && elem.validity.badInput) { 926 return false; 927 } 928 val = val.replace(' ', '').replace(',', '.'); 929 if ((/^-?[0-9]*[.,]?[0-9]+$/).test(val)) { 930 value.value = val; 931 return true; 932 } 933 return false; 934 }, 935 936 min: function(elem, arg, val) { 937 if (elem.type === 'number') { 938 if (elem.validity.rangeUnderflow) { 939 return false 940 } else if (elem.validity.badInput) { 941 return null; 942 } 943 } 944 return Nette.validators.range(elem, [arg, null], val); 945 }, 946 947 max: function(elem, arg, val) { 948 if (elem.type === 'number') { 949 if (elem.validity.rangeOverflow) { 950 return false 951 } else if (elem.validity.badInput) { 952 return null; 953 } 954 } 955 return Nette.validators.range(elem, [null, arg], val); 956 }, 957 958 range: function(elem, arg, val) { 959 if (elem.type === 'number') { 960 if ((elem.validity.rangeUnderflow && arg[0] !== null) || (elem.validity.rangeOverflow && arg[1] !== null)) { 961 return false 962 } else if (elem.validity.badInput) { 963 return null; 964 } 965 } 966 return Nette.isArray(arg) ? 967 ((arg[0] === null || parseFloat(val) >= arg[0]) && (arg[1] === null || parseFloat(val) <= arg[1])) : null; 968 }, 969 970 submitted: function(elem, arg, val) { 971 return elem.form['nette-submittedBy'] === elem; 972 }, 973 974 fileSize: function(elem, arg, val) { 975 if (window.FileList) { 976 for (var i = 0; i < val.length; i++) { 977 if (val[i].size > arg) { 978 return false; 979 } 980 } 981 } 982 return true; 983 }, 984 985 image: function (elem, arg, val) { 986 if (window.FileList && val instanceof window.FileList) { 987 for (var i = 0; i < val.length; i++) { 988 var type = val[i].type; 989 if (type && type !== 'image/gif' && type !== 'image/png' && type !== '
989image/jpeg') { 990 return false; 991 } 992 } 993 } 994 return true; 995 } 996}; 997 998 999/** 1000 * Process all toggles in form. 1001 */ 1002Nette.toggleForm = function(form, elem) { 1003 var i; 1004 Nette.toggles = {}; 1005 for (i = 0; i < form.elements.length; i++) { 1006 if (form.elements[i].tagName.toLowerCase() in {input: 1, select: 1, textarea: 1, button: 1}) { 1007 Nette.toggleControl(form.elements[i], null, null, !elem); 1008 } 1009 } 1010 1011 for (i in Nette.toggles) { 1012 Nette.toggle(i, Nette.toggles[i], elem); 1013 } 1014}; 1015 1016 1017/** 1018 * Process toggles on form element. 1019 */ 1020Nette.toggleControl = function(elem, rules, success, firsttime, value) { 1021 rules = rules || Nette.parseJSON(elem.getAttribute('data-nette-rules')); 1022 value = value === undefined ? {value: Nette.getEffectiveValue(elem)} : value; 1023 1024 var has = false, 1025 handled = [], 1026 handler = function () { 1027 Nette.toggleForm(elem.form, elem); 1028 }, 1029 curSuccess; 1030 1031 for (var id = 0, len = rules.length; id < len; id++) { 1032 var rule = rules[id], 1033 op = rule.op.match(/(~)?([^?]+)/), 1034 curElem = rule.control ? elem.form.elements.namedItem(rule.control) : elem; 1035 1036 if (!curElem) { 1037 continue; 1038 } 1039 1040 curSuccess = success; 1041 if (success !== false) { 1042 rule.neg = op[1]; 1043 rule.op = op[2]; 1044 var curValue = elem === curElem ? value : {value: Nette.getEffectiveValue(curElem)}; 1045 curSuccess = Nette.validateRule(curElem, rule.op, rule.arg, curValue); 1046 if (curSuccess === null) { 1047 continue; 1048 1049 } else if (rule.neg) { 1050 curSuccess = !curSuccess; 1051 } 1052 if (!rule.rules) { 1053 success = curSuccess; 1054 } 1055 } 1056 1057 if ((rule.rules && Nette.toggleControl(elem, rule.rules, curSuccess, firsttime, value)) || rule.toggle) { 1058 has = true; 1059 if (firsttime) { 1060 var oldIE = !document.addEventListener, // IE < 9 1061 name = curElem.tagName ? curElem.name : curElem[0].name, 1062 els = curElem.tagName ? curElem.form.elements : curElem; 1063 1064 for (var i = 0; i < els.length; i++) { 1065 if (els[i].name === name && !Nette.inArray(handled, els[i])) { 1066 Nette.addEvent(els[i], oldIE && els[i].type in {checkbox: 1, radio: 1} ? 'click' : 'change', handler); 1067 handled.push(els[i]); 1068 } 1069 } 1070 } 1071 for (var id2 in rule.toggle || []) { 1072 if (Object.prototype.hasOwnProperty.call(rule.toggle, id2)) { 1073 Nette.toggles[id2] = Nette.toggles[id2] || (rule.toggle[id2] ? curSuccess : !curSuccess); 1074 } 1075 } 1076 } 1077 } 1078 return has; 1079}; 1080 1081 1082Nette.parseJSON = function(s) { 1083 return (s || '').substr(0, 3) === '{op' 1084 ? eval('[' + s + ']') // backward compatibility with Nette 2.0.x 1085 : JSON.parse(s || '[]'); 1086}; 1087 1088 1089/** 1090 * Displays or hides HTML element. 1091 */ 1092Nette.toggle = function(id, visible, srcElement) { 1093 var elem = document.getElementById(id); 1094 if (elem) { 1095 elem.style.display = visible ? '' : 'none'; 1096 } 1097}; 1098 1099 1100/** 1101 * Setup handlers. 1102 */ 1103Nette.initForm = function(form) { 1104 form.noValidate = 'novalidate'; 1105 1106 // LiveForm: addition 1107 LiveForm.forms[form.id] = { 1108 hasError: false, 1109 onLoadValidation: false 1110 }; 1111 1112 Nette.addEvent(form, 'submit', function(e) { 1113 if (!Nette.validateForm(form)) { 1114 if (e && e.stopPropagation) { 1115 e.stopPropagation(); 1116 e.preventDefault(); 1117 } else if (window.event) { 1118 event.cancelBubble = true; 1119 event.returnValue = false; 1120 } 1121 } 1122 }); 1123 1124 Nette.toggleForm(form); 1125 1126 // LiveForm: addition 1127 for (var i = 0; i < form.elements.length; i++) { 1128 LiveForm.setupHandlers(form.elements[i]); 1129 LiveForm.processServerErrors(form.elements[i]); 1130 } 1131}; 1132 1133 1134/** 1135 * @private 1136 */ 1137Nette.initOnLoad = function() { 1138 Nette.addEvent(document, 'DOMContentLoaded', function() { 1139 for (var i = 0; i < document.forms.length; i++) { 1140 var form = document.forms[i]; 1141 for (var j = 0; j < form.elements.length; j++) { 1142 if (form.elements[j].getAttribute('data-nette-rules')) { 1143 Nette.initForm(form); 1144 1145 // LiveForm: addition 1146 if (LiveForm.hasClass(form, 'validate-on-load')) { 1147 // This is not so nice way, but I don't want to spoil validateForm, validateControl and other methods with another parameter 1148 LiveForm.setFormProperty(form, "onLoadValidation", true); 1149 Nette.validateForm(form); 1150 LiveForm.setFormProperty(form, "onLoadValidation", false); 1151 } 1152 1153 break; 1154 } 1155 } 1156 } 1157 1158 Nette.addEvent(document.body, 'click', function(e) { 1159 var target = e.target || e.srcElement; 1160 if (target.form && target.type in {submit: 1, image: 1}) { 1161 target.form['nette-submittedBy'] = target; 1162 } 1163 }); 1164 }); 1165}; 1166 1167 1168/** 1169 * Determines whether the argument is an array.
1170 */ 1171Nette.isArray = function(arg) { 1172 return Object.prototype.toString.call(arg) === '[object Array]'; 1173}; 1174 1175 1176/** 1177 * Search for a specified value within an array. 1178 */ 1179Nette.inArray = function(arr, val) { 1180 if ([].indexOf) { 1181 return arr.indexOf(val) > -1; 1182 } else { 1183 for (var i = 0; i < arr.length; i++) { 1184 if (arr[i] === val) { 1185 return true; 1186 } 1187 } 1188 return false; 1189 } 1190}; 1191 1192 1193/** 1194 * Converts string to web safe characters [a-z0-9-] text. 1195 */ 1196Nette.webalize = function(s) { 1197 s = s.toLowerCase(); 1198 var res = '', i, ch; 1199 for (i = 0; i < s.length; i++) { 1200 ch = Nette.webalizeTable[s.charAt(i)]; 1201 res += ch ? ch : s.charAt(i); 1202 } 1203 return res.replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, ''); 1204}; 1205 1206Nette.webalizeTable = {\u00e1: 'a', \u00e4: 'a', \u010d: 'c', \u010f: 'd', \u00e9: 'e', \u011b: 'e', \u00ed: 'i', \u013e: 'l', \u0148: 'n', \u00f3: 'o', \u00f4: 'o', \u0159: 'r', \u0161: 's', \u0165: 't', \u00fa: 'u', \u016f: 'u', \u00fd: 'y', \u017e: 'z'}; 1207 1208return Nette; 1209}));
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.