PluginProbe
Packeta / 1.6.1
Packeta v1.6.1
2.3.2 2.3.1 trunk 1.2.2 1.2.3 1.2.4 1.2.5 1.2.6 1.3.0 1.3.1 1.3.2 1.4 1.4.1 1.4.2 1.4.3 1.5.0 1.5.1 1.5.2 1.5.3 1.5.4 1.6.0 1.6.1 1.6.2 1.6.3 1.6.4 All 56 releases
packeta / public / libs / live-form-validation / live-form-validation.js

live-form-validation.js in Packeta 1.6.1, at public/libs/live-form-validation/live-form-validation.js

1,300 lines 43.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
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: '&nbsp;<i class="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></i>&nbsp;',
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 PacketeryLiveFormOptions 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 = '&nbsp;';
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 !== 'image/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 }));
1300