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