1 | 'use strict';
|
2 |
|
3 | import $ from 'jquery';
|
4 | import { Plugin } from './foundation.plugin';
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 | class Abide extends Plugin {
|
12 | |
13 |
|
14 |
|
15 |
|
16 |
|
17 |
|
18 |
|
19 |
|
20 | _setup(element, options = {}) {
|
21 | this.$element = element;
|
22 | this.options = $.extend({}, Abide.defaults, this.$element.data(), options);
|
23 |
|
24 | this.className = 'Abide';
|
25 | this._init();
|
26 | }
|
27 |
|
28 | |
29 |
|
30 |
|
31 |
|
32 | _init() {
|
33 | this.$inputs = this.$element.find('input, textarea, select');
|
34 |
|
35 | this._events();
|
36 | }
|
37 |
|
38 | |
39 |
|
40 |
|
41 |
|
42 | _events() {
|
43 | this.$element.off('.abide')
|
44 | .on('reset.zf.abide', () => {
|
45 | this.resetForm();
|
46 | })
|
47 | .on('submit.zf.abide', () => {
|
48 | return this.validateForm();
|
49 | });
|
50 |
|
51 | if (this.options.validateOn === 'fieldChange') {
|
52 | this.$inputs
|
53 | .off('change.zf.abide')
|
54 | .on('change.zf.abide', (e) => {
|
55 | this.validateInput($(e.target));
|
56 | });
|
57 | }
|
58 |
|
59 | if (this.options.liveValidate) {
|
60 | this.$inputs
|
61 | .off('input.zf.abide')
|
62 | .on('input.zf.abide', (e) => {
|
63 | this.validateInput($(e.target));
|
64 | });
|
65 | }
|
66 |
|
67 | if (this.options.validateOnBlur) {
|
68 | this.$inputs
|
69 | .off('blur.zf.abide')
|
70 | .on('blur.zf.abide', (e) => {
|
71 | this.validateInput($(e.target));
|
72 | });
|
73 | }
|
74 | }
|
75 |
|
76 | |
77 |
|
78 |
|
79 |
|
80 | _reflow() {
|
81 | this._init();
|
82 | }
|
83 |
|
84 | |
85 |
|
86 |
|
87 |
|
88 |
|
89 | requiredCheck($el) {
|
90 | if (!$el.attr('required')) return true;
|
91 |
|
92 | var isGood = true;
|
93 |
|
94 | switch ($el[0].type) {
|
95 | case 'checkbox':
|
96 | isGood = $el[0].checked;
|
97 | break;
|
98 |
|
99 | case 'select':
|
100 | case 'select-one':
|
101 | case 'select-multiple':
|
102 | var opt = $el.find('option:selected');
|
103 | if (!opt.length || !opt.val()) isGood = false;
|
104 | break;
|
105 |
|
106 | default:
|
107 | if(!$el.val() || !$el.val().length) isGood = false;
|
108 | }
|
109 |
|
110 | return isGood;
|
111 | }
|
112 |
|
113 | |
114 |
|
115 |
|
116 |
|
117 |
|
118 |
|
119 |
|
120 |
|
121 |
|
122 |
|
123 |
|
124 |
|
125 | findFormError($el) {
|
126 | var id = $el[0].id;
|
127 | var $error = $el.siblings(this.options.formErrorSelector);
|
128 |
|
129 | if (!$error.length) {
|
130 | $error = $el.parent().find(this.options.formErrorSelector);
|
131 | }
|
132 |
|
133 | $error = $error.add(this.$element.find(`[data-form-error-for="${id}"]`));
|
134 |
|
135 | return $error;
|
136 | }
|
137 |
|
138 | |
139 |
|
140 |
|
141 |
|
142 |
|
143 |
|
144 |
|
145 |
|
146 | findLabel($el) {
|
147 | var id = $el[0].id;
|
148 | var $label = this.$element.find(`label[for="${id}"]`);
|
149 |
|
150 | if (!$label.length) {
|
151 | return $el.closest('label');
|
152 | }
|
153 |
|
154 | return $label;
|
155 | }
|
156 |
|
157 | |
158 |
|
159 |
|
160 |
|
161 |
|
162 |
|
163 |
|
164 |
|
165 | findRadioLabels($els) {
|
166 | var labels = $els.map((i, el) => {
|
167 | var id = el.id;
|
168 | var $label = this.$element.find(`label[for="${id}"]`);
|
169 |
|
170 | if (!$label.length) {
|
171 | $label = $(el).closest('label');
|
172 | }
|
173 | return $label[0];
|
174 | });
|
175 |
|
176 | return $(labels);
|
177 | }
|
178 |
|
179 | |
180 |
|
181 |
|
182 |
|
183 | addErrorClasses($el) {
|
184 | var $label = this.findLabel($el);
|
185 | var $formError = this.findFormError($el);
|
186 |
|
187 | if ($label.length) {
|
188 | $label.addClass(this.options.labelErrorClass);
|
189 | }
|
190 |
|
191 | if ($formError.length) {
|
192 | $formError.addClass(this.options.formErrorClass);
|
193 | }
|
194 |
|
195 | $el.addClass(this.options.inputErrorClass).attr('data-invalid', '');
|
196 | }
|
197 |
|
198 | |
199 |
|
200 |
|
201 |
|
202 |
|
203 |
|
204 | removeRadioErrorClasses(groupName) {
|
205 | var $els = this.$element.find(`:radio[name="${groupName}"]`);
|
206 | var $labels = this.findRadioLabels($els);
|
207 | var $formErrors = this.findFormError($els);
|
208 |
|
209 | if ($labels.length) {
|
210 | $labels.removeClass(this.options.labelErrorClass);
|
211 | }
|
212 |
|
213 | if ($formErrors.length) {
|
214 | $formErrors.removeClass(this.options.formErrorClass);
|
215 | }
|
216 |
|
217 | $els.removeClass(this.options.inputErrorClass).removeAttr('data-invalid');
|
218 |
|
219 | }
|
220 |
|
221 | |
222 |
|
223 |
|
224 |
|
225 | removeErrorClasses($el) {
|
226 |
|
227 | if($el[0].type == 'radio') {
|
228 | return this.removeRadioErrorClasses($el.attr('name'));
|
229 | }
|
230 |
|
231 | var $label = this.findLabel($el);
|
232 | var $formError = this.findFormError($el);
|
233 |
|
234 | if ($label.length) {
|
235 | $label.removeClass(this.options.labelErrorClass);
|
236 | }
|
237 |
|
238 | if ($formError.length) {
|
239 | $formError.removeClass(this.options.formErrorClass);
|
240 | }
|
241 |
|
242 | $el.removeClass(this.options.inputErrorClass).removeAttr('data-invalid');
|
243 | }
|
244 |
|
245 | |
246 |
|
247 |
|
248 |
|
249 |
|
250 |
|
251 |
|
252 |
|
253 | validateInput($el) {
|
254 | var clearRequire = this.requiredCheck($el),
|
255 | validated = false,
|
256 | customValidator = true,
|
257 | validator = $el.attr('data-validator'),
|
258 | equalTo = true;
|
259 |
|
260 |
|
261 | if ($el.is('[data-abide-ignore]') || $el.is('[type="hidden"]') || $el.is('[disabled]')) {
|
262 | return true;
|
263 | }
|
264 |
|
265 | switch ($el[0].type) {
|
266 | case 'radio':
|
267 | validated = this.validateRadio($el.attr('name'));
|
268 | break;
|
269 |
|
270 | case 'checkbox':
|
271 | validated = clearRequire;
|
272 | break;
|
273 |
|
274 | case 'select':
|
275 | case 'select-one':
|
276 | case 'select-multiple':
|
277 | validated = clearRequire;
|
278 | break;
|
279 |
|
280 | default:
|
281 | validated = this.validateText($el);
|
282 | }
|
283 |
|
284 | if (validator) {
|
285 | customValidator = this.matchValidation($el, validator, $el.attr('required'));
|
286 | }
|
287 |
|
288 | if ($el.attr('data-equalto')) {
|
289 | equalTo = this.options.validators.equalTo($el);
|
290 | }
|
291 |
|
292 |
|
293 | var goodToGo = [clearRequire, validated, customValidator, equalTo].indexOf(false) === -1;
|
294 | var message = (goodToGo ? 'valid' : 'invalid') + '.zf.abide';
|
295 |
|
296 | if (goodToGo) {
|
297 |
|
298 | const dependentElements = this.$element.find(`[data-equalto="${$el.attr('id')}"]`);
|
299 | if (dependentElements.length) {
|
300 | let _this = this;
|
301 | dependentElements.each(function() {
|
302 | if ($(this).val()) {
|
303 | _this.validateInput($(this));
|
304 | }
|
305 | });
|
306 | }
|
307 | }
|
308 |
|
309 | this[goodToGo ? 'removeErrorClasses' : 'addErrorClasses']($el);
|
310 |
|
311 | |
312 |
|
313 |
|
314 |
|
315 |
|
316 |
|
317 | $el.trigger(message, [$el]);
|
318 |
|
319 | return goodToGo;
|
320 | }
|
321 |
|
322 | |
323 |
|
324 |
|
325 |
|
326 |
|
327 |
|
328 | validateForm() {
|
329 | var acc = [];
|
330 | var _this = this;
|
331 |
|
332 | this.$inputs.each(function() {
|
333 | acc.push(_this.validateInput($(this)));
|
334 | });
|
335 |
|
336 | var noError = acc.indexOf(false) === -1;
|
337 |
|
338 | this.$element.find('[data-abide-error]').css('display', (noError ? 'none' : 'block'));
|
339 |
|
340 | |
341 |
|
342 |
|
343 |
|
344 |
|
345 |
|
346 | this.$element.trigger((noError ? 'formvalid' : 'forminvalid') + '.zf.abide', [this.$element]);
|
347 |
|
348 | return noError;
|
349 | }
|
350 |
|
351 | |
352 |
|
353 |
|
354 |
|
355 |
|
356 |
|
357 | validateText($el, pattern) {
|
358 |
|
359 | pattern = (pattern || $el.attr('pattern') || $el.attr('type'));
|
360 | var inputText = $el.val();
|
361 | var valid = false;
|
362 |
|
363 | if (inputText.length) {
|
364 |
|
365 | if (this.options.patterns.hasOwnProperty(pattern)) {
|
366 | valid = this.options.patterns[pattern].test(inputText);
|
367 | }
|
368 |
|
369 | else if (pattern !== $el.attr('type')) {
|
370 | valid = new RegExp(pattern).test(inputText);
|
371 | }
|
372 | else {
|
373 | valid = true;
|
374 | }
|
375 | }
|
376 |
|
377 | else if (!$el.prop('required')) {
|
378 | valid = true;
|
379 | }
|
380 |
|
381 | return valid;
|
382 | }
|
383 |
|
384 | |
385 |
|
386 |
|
387 |
|
388 |
|
389 | validateRadio(groupName) {
|
390 |
|
391 |
|
392 | var $group = this.$element.find(`:radio[name="${groupName}"]`);
|
393 | var valid = false, required = false;
|
394 |
|
395 |
|
396 | $group.each((i, e) => {
|
397 | if ($(e).attr('required')) {
|
398 | required = true;
|
399 | }
|
400 | });
|
401 | if(!required) valid=true;
|
402 |
|
403 | if (!valid) {
|
404 |
|
405 | $group.each((i, e) => {
|
406 | if ($(e).prop('checked')) {
|
407 | valid = true;
|
408 | }
|
409 | });
|
410 | };
|
411 |
|
412 | return valid;
|
413 | }
|
414 |
|
415 | |
416 |
|
417 |
|
418 |
|
419 |
|
420 |
|
421 |
|
422 | matchValidation($el, validators, required) {
|
423 | required = required ? true : false;
|
424 |
|
425 | var clear = validators.split(' ').map((v) => {
|
426 | return this.options.validators[v]($el, required, $el.parent());
|
427 | });
|
428 | return clear.indexOf(false) === -1;
|
429 | }
|
430 |
|
431 | |
432 |
|
433 |
|
434 |
|
435 | resetForm() {
|
436 | var $form = this.$element,
|
437 | opts = this.options;
|
438 |
|
439 | $(`.${opts.labelErrorClass}`, $form).not('small').removeClass(opts.labelErrorClass);
|
440 | $(`.${opts.inputErrorClass}`, $form).not('small').removeClass(opts.inputErrorClass);
|
441 | $(`${opts.formErrorSelector}.${opts.formErrorClass}`).removeClass(opts.formErrorClass);
|
442 | $form.find('[data-abide-error]').css('display', 'none');
|
443 | $(':input', $form).not(':button, :submit, :reset, :hidden, :radio, :checkbox, [data-abide-ignore]').val('').removeAttr('data-invalid');
|
444 | $(':input:radio', $form).not('[data-abide-ignore]').prop('checked',false).removeAttr('data-invalid');
|
445 | $(':input:checkbox', $form).not('[data-abide-ignore]').prop('checked',false).removeAttr('data-invalid');
|
446 | |
447 |
|
448 |
|
449 |
|
450 | $form.trigger('formreset.zf.abide', [$form]);
|
451 | }
|
452 |
|
453 | |
454 |
|
455 |
|
456 |
|
457 | _destroy() {
|
458 | var _this = this;
|
459 | this.$element
|
460 | .off('.abide')
|
461 | .find('[data-abide-error]')
|
462 | .css('display', 'none');
|
463 |
|
464 | this.$inputs
|
465 | .off('.abide')
|
466 | .each(function() {
|
467 | _this.removeErrorClasses($(this));
|
468 | });
|
469 | }
|
470 | }
|
471 |
|
472 |
|
473 |
|
474 |
|
475 | Abide.defaults = {
|
476 | |
477 |
|
478 |
|
479 |
|
480 |
|
481 |
|
482 |
|
483 | validateOn: 'fieldChange',
|
484 |
|
485 | |
486 |
|
487 |
|
488 |
|
489 |
|
490 |
|
491 | labelErrorClass: 'is-invalid-label',
|
492 |
|
493 | |
494 |
|
495 |
|
496 |
|
497 |
|
498 |
|
499 | inputErrorClass: 'is-invalid-input',
|
500 |
|
501 | |
502 |
|
503 |
|
504 |
|
505 |
|
506 |
|
507 | formErrorSelector: '.form-error',
|
508 |
|
509 | |
510 |
|
511 |
|
512 |
|
513 |
|
514 |
|
515 | formErrorClass: 'is-visible',
|
516 |
|
517 | |
518 |
|
519 |
|
520 |
|
521 |
|
522 |
|
523 | liveValidate: false,
|
524 |
|
525 | |
526 |
|
527 |
|
528 |
|
529 |
|
530 |
|
531 | validateOnBlur: false,
|
532 |
|
533 | patterns: {
|
534 | alpha : /^[a-zA-Z]+$/,
|
535 | alpha_numeric : /^[a-zA-Z0-9]+$/,
|
536 | integer : /^[-+]?\d+$/,
|
537 | number : /^[-+]?\d*(?:[\.\,]\d+)?$/,
|
538 |
|
539 |
|
540 | card : /^(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|(?:222[1-9]|2[3-6][0-9]{2}|27[0-1][0-9]|2720)[0-9]{12}|6(?:011|5[0-9][0-9])[0-9]{12}|3[47][0-9]{13}|3(?:0[0-5]|[68][0-9])[0-9]{11}|(?:2131|1800|35\d{3})\d{11})$/,
|
541 | cvv : /^([0-9]){3,4}$/,
|
542 |
|
543 |
|
544 | email : /^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+$/,
|
545 |
|
546 | url : /^(https?|ftp|file|ssh):\/\/(((([a-zA-Z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|((([a-zA-Z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-zA-Z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-zA-Z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-zA-Z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-zA-Z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-zA-Z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-zA-Z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-zA-Z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?)(:\d*)?)(\/((([a-zA-Z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-zA-Z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([a-zA-Z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(\#((([a-zA-Z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?$/,
|
547 |
|
548 | domain : /^([a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,8}$/,
|
549 |
|
550 | datetime : /^([0-2][0-9]{3})\-([0-1][0-9])\-([0-3][0-9])T([0-5][0-9])\:([0-5][0-9])\:([0-5][0-9])(Z|([\-\+]([0-1][0-9])\:00))$/,
|
551 |
|
552 | date : /(?:19|20)[0-9]{2}-(?:(?:0[1-9]|1[0-2])-(?:0[1-9]|1[0-9]|2[0-9])|(?:(?!02)(?:0[1-9]|1[0-2])-(?:30))|(?:(?:0[13578]|1[02])-31))$/,
|
553 |
|
554 | time : /^(0[0-9]|1[0-9]|2[0-3])(:[0-5][0-9]){2}$/,
|
555 | dateISO : /^\d{4}[\/\-]\d{1,2}[\/\-]\d{1,2}$/,
|
556 |
|
557 | month_day_year : /^(0[1-9]|1[012])[- \/.](0[1-9]|[12][0-9]|3[01])[- \/.]\d{4}$/,
|
558 |
|
559 | day_month_year : /^(0[1-9]|[12][0-9]|3[01])[- \/.](0[1-9]|1[012])[- \/.]\d{4}$/,
|
560 |
|
561 |
|
562 | color : /^#?([a-fA-F0-9]{6}|[a-fA-F0-9]{3})$/,
|
563 |
|
564 |
|
565 | website: {
|
566 | test: (text) => {
|
567 | return Abide.defaults.patterns['domain'].test(text) || Abide.defaults.patterns['url'].test(text);
|
568 | }
|
569 | }
|
570 | },
|
571 |
|
572 | |
573 |
|
574 |
|
575 |
|
576 |
|
577 |
|
578 |
|
579 |
|
580 | validators: {
|
581 | equalTo: function (el, required, parent) {
|
582 | return $(`#${el.attr('data-equalto')}`).val() === el.val();
|
583 | }
|
584 | }
|
585 | }
|
586 |
|
587 | export {Abide};
|