public/lib/semantic/components/form.js   F
last analyzed

Complexity

Total Complexity 344
Complexity/F 2.8

Size

Lines of Code 1696
Function Count 123

Duplication

Duplicated Lines 1696
Ratio 100 %

Importance

Changes 0
Metric Value
cc 0
eloc 1119
nc 0
dl 1696
loc 1696
rs 1.124
c 0
b 0
f 0
wmc 344
mnd 5
bc 296
fnc 123
bpm 2.4065
cpm 2.7967
noi 45

33 Functions

Rating   Name   Duplication   Size   Complexity  
B $.fn.form.settings.rules.integer 30 30 7
A $.fn.form.settings.rules.decimal 3 3 1
A $.fn.form.settings.onInvalid 1 1 1
A $.fn.form.settings.onValid 1 1 1
A $.fn.form.settings.rules.minLength 6 6 2
A $.fn.form.settings.templates.prompt 6 6 1
A $.fn.form.settings.rules.exactCount 9 9 3
A $.fn.form.settings.rules.contains 5 5 1
A $.fn.form.settings.rules.email 3 3 1
A $.fn.form.settings.rules.is 11 11 3
A $.fn.form.settings.rules.maxCount 9 9 3
A $.fn.form.settings.rules.number 3 3 1
B $.fn.form.settings.rules.different 23 23 6
A $.fn.form.settings.rules.containsExactly 5 5 1
B $.fn.form 1182 1182 2
C $.fn.form.settings.rules.creditCard 106 106 9
A $.fn.form.settings.rules.not 11 11 3
A $.fn.form.settings.rules.regExp 21 21 5
A $.fn.form.settings.templates.error 10 10 1
A $.fn.form.settings.rules.isExactly 3 3 1
A $.fn.form.settings.onFailure 1 1 1
A $.fn.form.settings.rules.maxLength 6 6 2
A $.fn.form.settings.rules.url 3 3 1
A $.fn.form.settings.rules.checked 3 3 1
A $.fn.form.settings.onSuccess 1 1 1
A $.fn.form.settings.rules.minCount 9 9 3
A $.fn.form.settings.rules.exactLength 6 6 2
A $.fn.form.settings.rules.length 6 6 2
A $.fn.form.settings.rules.doesntContain 5 5 1
B $.fn.form.settings.rules.match 22 22 6
A $.fn.form.settings.rules.doesntContainExactly 5 5 1
A $.fn.form.settings.rules.empty 3 3 1
A $.fn.form.settings.rules.notExactly 3 3 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complexity

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like public/lib/semantic/components/form.js often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
/*!
2
 * # Semantic UI 2.2.11 - Form Validation
3
 * http://github.com/semantic-org/semantic-ui/
4
 *
5
 *
6
 * Released under the MIT license
7
 * http://opensource.org/licenses/MIT
8
 *
9
 */
10
11 View Code Duplication
;(function ($, window, document, undefined) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
12
13
"use strict";
14
15
window = (typeof window != 'undefined' && window.Math == Math)
0 ignored issues
show
Unused Code introduced by
The assignment to variable window seems to be never used. Consider removing it.
Loading history...
16
  ? window
17
  : (typeof self != 'undefined' && self.Math == Math)
0 ignored issues
show
Bug introduced by
The variable self seems to be never declared. If this is a global, consider adding a /** global: self */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
18
    ? self
19
    : Function('return this')()
0 ignored issues
show
Performance Best Practice introduced by
Using new Function() to create a function is slow and difficult to debug. Such functions do not create a closure. Consider using another way to define your function.
Loading history...
20
;
21
22
$.fn.form = function(parameters) {
23
  var
24
    $allModules      = $(this),
25
    moduleSelector   = $allModules.selector || '',
26
27
    time             = new Date().getTime(),
28
    performance      = [],
29
30
    query            = arguments[0],
31
    legacyParameters = arguments[1],
32
    methodInvoked    = (typeof query == 'string'),
33
    queryArguments   = [].slice.call(arguments, 1),
34
    returnedValue
35
  ;
36
  $allModules
37
    .each(function() {
38
      var
39
        $module     = $(this),
40
        element     = this,
41
42
        formErrors  = [],
43
        keyHeldDown = false,
44
45
        // set at run-time
46
        $field,
47
        $group,
48
        $message,
49
        $prompt,
50
        $submit,
51
        $clear,
52
        $reset,
53
54
        settings,
55
        validation,
56
57
        metadata,
58
        selector,
59
        className,
60
        regExp,
61
        error,
62
63
        namespace,
64
        moduleNamespace,
65
        eventNamespace,
66
67
        instance,
68
        module
69
      ;
70
71
      module      = {
72
73
        initialize: function() {
74
75
          // settings grabbed at run time
76
          module.get.settings();
77
          if(methodInvoked) {
78
            if(instance === undefined) {
79
              module.instantiate();
80
            }
81
            module.invoke(query);
82
          }
83
          else {
84
            if(instance !== undefined) {
85
              instance.invoke('destroy');
86
            }
87
            module.verbose('Initializing form validation', $module, settings);
88
            module.bindEvents();
89
            module.set.defaults();
90
            module.instantiate();
91
          }
92
        },
93
94
        instantiate: function() {
95
          module.verbose('Storing instance of module', module);
96
          instance = module;
97
          $module
98
            .data(moduleNamespace, module)
99
          ;
100
        },
101
102
        destroy: function() {
103
          module.verbose('Destroying previous module', instance);
104
          module.removeEvents();
105
          $module
106
            .removeData(moduleNamespace)
107
          ;
108
        },
109
110
        refresh: function() {
111
          module.verbose('Refreshing selector cache');
112
          $field      = $module.find(selector.field);
113
          $group      = $module.find(selector.group);
114
          $message    = $module.find(selector.message);
115
          $prompt     = $module.find(selector.prompt);
0 ignored issues
show
Unused Code introduced by
The variable $prompt seems to be never used. Consider removing it.
Loading history...
116
117
          $submit     = $module.find(selector.submit);
118
          $clear      = $module.find(selector.clear);
0 ignored issues
show
Unused Code introduced by
The variable $clear seems to be never used. Consider removing it.
Loading history...
119
          $reset      = $module.find(selector.reset);
0 ignored issues
show
Unused Code introduced by
The variable $reset seems to be never used. Consider removing it.
Loading history...
120
        },
121
122
        submit: function() {
123
          module.verbose('Submitting form', $module);
124
          $module
125
            .submit()
126
          ;
127
        },
128
129
        attachEvents: function(selector, action) {
130
          action = action || 'submit';
131
          $(selector)
132
            .on('click' + eventNamespace, function(event) {
133
              module[action]();
134
              event.preventDefault();
135
            })
136
          ;
137
        },
138
139
        bindEvents: function() {
140
          module.verbose('Attaching form events');
141
          $module
142
            .on('submit' + eventNamespace, module.validate.form)
143
            .on('blur'   + eventNamespace, selector.field, module.event.field.blur)
144
            .on('click'  + eventNamespace, selector.submit, module.submit)
145
            .on('click'  + eventNamespace, selector.reset, module.reset)
146
            .on('click'  + eventNamespace, selector.clear, module.clear)
147
          ;
148
          if(settings.keyboardShortcuts) {
149
            $module
150
              .on('keydown' + eventNamespace, selector.field, module.event.field.keydown)
151
            ;
152
          }
153
          $field
154
            .each(function() {
155
              var
156
                $input     = $(this),
157
                type       = $input.prop('type'),
158
                inputEvent = module.get.changeEvent(type, $input)
159
              ;
160
              $(this)
161
                .on(inputEvent + eventNamespace, module.event.field.change)
162
              ;
163
            })
164
          ;
165
        },
166
167
        clear: function() {
168
          $field
169
            .each(function () {
170
              var
171
                $field       = $(this),
172
                $element     = $field.parent(),
173
                $fieldGroup  = $field.closest($group),
174
                $prompt      = $fieldGroup.find(selector.prompt),
175
                defaultValue = $field.data(metadata.defaultValue) || '',
176
                isCheckbox   = $element.is(selector.uiCheckbox),
177
                isDropdown   = $element.is(selector.uiDropdown),
178
                isErrored    = $fieldGroup.hasClass(className.error)
179
              ;
180
              if(isErrored) {
181
                module.verbose('Resetting error on field', $fieldGroup);
182
                $fieldGroup.removeClass(className.error);
183
                $prompt.remove();
184
              }
185
              if(isDropdown) {
186
                module.verbose('Resetting dropdown value', $element, defaultValue);
187
                $element.dropdown('clear');
188
              }
189
              else if(isCheckbox) {
190
                $field.prop('checked', false);
191
              }
192
              else {
193
                module.verbose('Resetting field value', $field, defaultValue);
194
                $field.val('');
195
              }
196
            })
197
          ;
198
        },
199
200
        reset: function() {
201
          $field
202
            .each(function () {
203
              var
204
                $field       = $(this),
205
                $element     = $field.parent(),
206
                $fieldGroup  = $field.closest($group),
207
                $prompt      = $fieldGroup.find(selector.prompt),
208
                defaultValue = $field.data(metadata.defaultValue),
209
                isCheckbox   = $element.is(selector.uiCheckbox),
210
                isDropdown   = $element.is(selector.uiDropdown),
211
                isErrored    = $fieldGroup.hasClass(className.error)
212
              ;
213
              if(defaultValue === undefined) {
214
                return;
215
              }
216
              if(isErrored) {
217
                module.verbose('Resetting error on field', $fieldGroup);
218
                $fieldGroup.removeClass(className.error);
219
                $prompt.remove();
220
              }
221
              if(isDropdown) {
222
                module.verbose('Resetting dropdown value', $element, defaultValue);
223
                $element.dropdown('restore defaults');
224
              }
225
              else if(isCheckbox) {
226
                module.verbose('Resetting checkbox value', $element, defaultValue);
227
                $field.prop('checked', defaultValue);
228
              }
229
              else {
230
                module.verbose('Resetting field value', $field, defaultValue);
231
                $field.val(defaultValue);
232
              }
233
            })
234
          ;
235
        },
236
237
        determine: {
238
          isValid: function() {
239
            var
240
              allValid = true
241
            ;
242
            $.each(validation, function(fieldName, field) {
243
              if( !( module.validate.field(field, fieldName, true) ) ) {
244
                allValid = false;
245
              }
246
            });
247
            return allValid;
248
          }
249
        },
250
251
        is: {
252
          bracketedRule: function(rule) {
253
            return (rule.type && rule.type.match(settings.regExp.bracket));
254
          },
255
          shorthandFields: function(fields) {
256
            var
257
              fieldKeys = Object.keys(fields),
258
              firstRule = fields[fieldKeys[0]]
259
            ;
260
            return module.is.shorthandRules(firstRule);
261
          },
262
          // duck type rule test
263
          shorthandRules: function(rules) {
264
            return (typeof rules == 'string' || $.isArray(rules));
265
          },
266
          empty: function($field) {
267
            if(!$field || $field.length === 0) {
268
              return true;
269
            }
270
            else if($field.is('input[type="checkbox"]')) {
271
              return !$field.is(':checked');
272
            }
273
            else {
274
              return module.is.blank($field);
275
            }
276
          },
277
          blank: function($field) {
278
            return $.trim($field.val()) === '';
279
          },
280
          valid: function(field) {
281
            var
282
              allValid = true
283
            ;
284
            if(field) {
285
              module.verbose('Checking if field is valid', field);
286
              return module.validate.field(validation[field], field, false);
287
            }
288
            else {
0 ignored issues
show
Comprehensibility introduced by
else is not necessary here since all if branches return, consider removing it to reduce nesting and make code more readable.
Loading history...
289
              module.verbose('Checking if form is valid');
290
              $.each(validation, function(fieldName, field) {
0 ignored issues
show
Unused Code introduced by
The parameter field is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
291
                if( !module.is.valid(fieldName) ) {
292
                  allValid = false;
293
                }
294
              });
295
              return allValid;
296
            }
297
          }
298
        },
299
300
        removeEvents: function() {
301
          $module
302
            .off(eventNamespace)
303
          ;
304
          $field
305
            .off(eventNamespace)
306
          ;
307
          $submit
308
            .off(eventNamespace)
309
          ;
310
          $field
311
            .off(eventNamespace)
312
          ;
313
        },
314
315
        event: {
316
          field: {
317
            keydown: function(event) {
318
              var
319
                $field       = $(this),
320
                key          = event.which,
321
                isInput      = $field.is(selector.input),
322
                isCheckbox   = $field.is(selector.checkbox),
323
                isInDropdown = ($field.closest(selector.uiDropdown).length > 0),
324
                keyCode      = {
325
                  enter  : 13,
326
                  escape : 27
327
                }
328
              ;
329
              if( key == keyCode.escape) {
330
                module.verbose('Escape key pressed blurring field');
331
                $field
332
                  .blur()
333
                ;
334
              }
335
              if(!event.ctrlKey && key == keyCode.enter && isInput && !isInDropdown && !isCheckbox) {
336
                if(!keyHeldDown) {
337
                  $field
338
                    .one('keyup' + eventNamespace, module.event.field.keyup)
339
                  ;
340
                  module.submit();
341
                  module.debug('Enter pressed on input submitting form');
342
                }
343
                keyHeldDown = true;
344
              }
345
            },
346
            keyup: function() {
347
              keyHeldDown = false;
348
            },
349
            blur: function(event) {
0 ignored issues
show
Unused Code introduced by
The parameter event is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
350
              var
351
                $field          = $(this),
352
                $fieldGroup     = $field.closest($group),
353
                validationRules = module.get.validation($field)
354
              ;
355
              if( $fieldGroup.hasClass(className.error) ) {
356
                module.debug('Revalidating field', $field, validationRules);
357
                if(validationRules) {
358
                  module.validate.field( validationRules );
359
                }
360
              }
361
              else if(settings.on == 'blur' || settings.on == 'change') {
362
                if(validationRules) {
363
                  module.validate.field( validationRules );
364
                }
365
              }
366
            },
367
            change: function(event) {
0 ignored issues
show
Unused Code introduced by
The parameter event is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
368
              var
369
                $field      = $(this),
370
                $fieldGroup = $field.closest($group),
371
                validationRules = module.get.validation($field)
372
              ;
373
              if(validationRules && (settings.on == 'change' || ( $fieldGroup.hasClass(className.error) && settings.revalidate) )) {
374
                clearTimeout(module.timer);
375
                module.timer = setTimeout(function() {
376
                  module.debug('Revalidating field', $field,  module.get.validation($field));
377
                  module.validate.field( validationRules );
378
                }, settings.delay);
379
              }
380
            }
381
          }
382
383
        },
384
385
        get: {
386
          ancillaryValue: function(rule) {
387
            if(!rule.type || (!rule.value && !module.is.bracketedRule(rule))) {
388
              return false;
389
            }
390
            return (rule.value !== undefined)
391
              ? rule.value
392
              : rule.type.match(settings.regExp.bracket)[1] + ''
393
            ;
394
          },
395
          ruleName: function(rule) {
396
            if( module.is.bracketedRule(rule) ) {
397
              return rule.type.replace(rule.type.match(settings.regExp.bracket)[0], '');
398
            }
399
            return rule.type;
400
          },
401
          changeEvent: function(type, $input) {
402
            if(type == 'checkbox' || type == 'radio' || type == 'hidden' || $input.is('select')) {
403
              return 'change';
404
            }
405
            else {
0 ignored issues
show
Comprehensibility introduced by
else is not necessary here since all if branches return, consider removing it to reduce nesting and make code more readable.
Loading history...
406
              return module.get.inputEvent();
407
            }
408
          },
409
          inputEvent: function() {
410
            return (document.createElement('input').oninput !== undefined)
411
              ? 'input'
412
              : (document.createElement('input').onpropertychange !== undefined)
413
                ? 'propertychange'
414
                : 'keyup'
415
            ;
416
          },
417
          fieldsFromShorthand: function(fields) {
418
            var
419
              fullFields = {}
420
            ;
421
            $.each(fields, function(name, rules) {
422
              if(typeof rules == 'string') {
423
                rules = [rules];
424
              }
425
              fullFields[name] = {
426
                rules: []
427
              };
428
              $.each(rules, function(index, rule) {
429
                fullFields[name].rules.push({ type: rule });
430
              });
431
            });
432
            return fullFields;
433
          },
434
          prompt: function(rule, field) {
435
            var
436
              ruleName      = module.get.ruleName(rule),
437
              ancillary     = module.get.ancillaryValue(rule),
438
              prompt        = rule.prompt || settings.prompt[ruleName] || settings.text.unspecifiedRule,
439
              requiresValue = (prompt.search('{value}') !== -1),
440
              requiresName  = (prompt.search('{name}') !== -1),
441
              $label,
442
              $field,
443
              name
444
            ;
445
            if(requiresName || requiresValue) {
446
              $field = module.get.field(field.identifier);
447
            }
448
            if(requiresValue) {
449
              prompt = prompt.replace('{value}', $field.val());
0 ignored issues
show
Bug introduced by
The variable $field does not seem to be initialized in case requiresName || requiresValue on line 445 is false. Are you sure this can never be the case?
Loading history...
450
            }
451
            if(requiresName) {
452
              $label = $field.closest(selector.group).find('label').eq(0);
453
              name = ($label.length == 1)
0 ignored issues
show
Best Practice introduced by
Comparing $label.length to 1 using the == operator is not safe. Consider using === instead.
Loading history...
454
                ? $label.text()
455
                : $field.prop('placeholder') || settings.text.unspecifiedField
456
              ;
457
              prompt = prompt.replace('{name}', name);
458
            }
459
            prompt = prompt.replace('{identifier}', field.identifier);
460
            prompt = prompt.replace('{ruleValue}', ancillary);
461
            if(!rule.prompt) {
462
              module.verbose('Using default validation prompt for type', prompt, ruleName);
463
            }
464
            return prompt;
465
          },
466
          settings: function() {
467
            if($.isPlainObject(parameters)) {
468
              var
469
                keys     = Object.keys(parameters),
470
                isLegacySettings = (keys.length > 0)
471
                  ? (parameters[keys[0]].identifier !== undefined && parameters[keys[0]].rules !== undefined)
472
                  : false,
473
                ruleKeys
0 ignored issues
show
Unused Code introduced by
The variable ruleKeys seems to be never used. Consider removing it.
Loading history...
474
              ;
475
              if(isLegacySettings) {
476
                // 1.x (ducktyped)
477
                settings   = $.extend(true, {}, $.fn.form.settings, legacyParameters);
478
                validation = $.extend({}, $.fn.form.settings.defaults, parameters);
479
                module.error(settings.error.oldSyntax, element);
480
                module.verbose('Extending settings from legacy parameters', validation, settings);
481
              }
482
              else {
483
                // 2.x
484
                if(parameters.fields && module.is.shorthandFields(parameters.fields)) {
485
                  parameters.fields = module.get.fieldsFromShorthand(parameters.fields);
486
                }
487
                settings   = $.extend(true, {}, $.fn.form.settings, parameters);
488
                validation = $.extend({}, $.fn.form.settings.defaults, settings.fields);
489
                module.verbose('Extending settings', validation, settings);
490
              }
491
            }
492
            else {
493
              settings   = $.fn.form.settings;
494
              validation = $.fn.form.settings.defaults;
495
              module.verbose('Using default form validation', validation, settings);
496
            }
497
498
            // shorthand
499
            namespace       = settings.namespace;
500
            metadata        = settings.metadata;
501
            selector        = settings.selector;
502
            className       = settings.className;
503
            regExp          = settings.regExp;
504
            error           = settings.error;
505
            moduleNamespace = 'module-' + namespace;
506
            eventNamespace  = '.' + namespace;
507
508
            // grab instance
509
            instance = $module.data(moduleNamespace);
510
511
            // refresh selector cache
512
            module.refresh();
513
          },
514
          field: function(identifier) {
515
            module.verbose('Finding field with identifier', identifier);
516
            identifier = module.escape.string(identifier);
517
            if($field.filter('#' + identifier).length > 0 ) {
518
              return $field.filter('#' + identifier);
519
            }
520
            else if( $field.filter('[name="' + identifier +'"]').length > 0 ) {
521
              return $field.filter('[name="' + identifier +'"]');
522
            }
523
            else if( $field.filter('[name="' + identifier +'[]"]').length > 0 ) {
524
              return $field.filter('[name="' + identifier +'[]"]');
525
            }
526
            else if( $field.filter('[data-' + metadata.validate + '="'+ identifier +'"]').length > 0 ) {
527
              return $field.filter('[data-' + metadata.validate + '="'+ identifier +'"]');
528
            }
529
            return $('<input/>');
530
          },
531
          fields: function(fields) {
532
            var
533
              $fields = $()
534
            ;
535
            $.each(fields, function(index, name) {
536
              $fields = $fields.add( module.get.field(name) );
537
            });
538
            return $fields;
539
          },
540
          validation: function($field) {
541
            var
542
              fieldValidation,
543
              identifier
544
            ;
545
            if(!validation) {
546
              return false;
547
            }
548
            $.each(validation, function(fieldName, field) {
549
              identifier = field.identifier || fieldName;
550
              if( module.get.field(identifier)[0] == $field[0] ) {
551
                field.identifier = identifier;
552
                fieldValidation = field;
553
              }
554
            });
555
            return fieldValidation || false;
556
          },
557
          value: function (field) {
558
            var
559
              fields = [],
560
              results
561
            ;
562
            fields.push(field);
563
            results = module.get.values.call(element, fields);
564
            return results[field];
565
          },
566
          values: function (fields) {
567
            var
568
              $fields = $.isArray(fields)
569
                ? module.get.fields(fields)
570
                : $field,
571
              values = {}
572
            ;
573
            $fields.each(function(index, field) {
574
              var
575
                $field     = $(field),
576
                type       = $field.prop('type'),
0 ignored issues
show
Unused Code introduced by
The variable type seems to be never used. Consider removing it.
Loading history...
577
                name       = $field.prop('name'),
578
                value      = $field.val(),
579
                isCheckbox = $field.is(selector.checkbox),
580
                isRadio    = $field.is(selector.radio),
581
                isMultiple = (name.indexOf('[]') !== -1),
582
                isChecked  = (isCheckbox)
583
                  ? $field.is(':checked')
584
                  : false
585
              ;
586
              if(name) {
587
                if(isMultiple) {
588
                  name = name.replace('[]', '');
589
                  if(!values[name]) {
590
                    values[name] = [];
591
                  }
592
                  if(isCheckbox) {
593
                    if(isChecked) {
594
                      values[name].push(value || true);
595
                    }
596
                    else {
597
                      values[name].push(false);
598
                    }
599
                  }
600
                  else {
601
                    values[name].push(value);
602
                  }
603
                }
604
                else {
605
                  if(isRadio) {
606
                    if(values[name] === undefined) {
607
                      values[name] = (isChecked)
608
                        ? true
609
                        : false
610
                      ;
611
                    }
612
                  }
613
                  else if(isCheckbox) {
614
                    if(isChecked) {
615
                      values[name] = value || true;
616
                    }
617
                    else {
618
                      values[name] = false;
619
                    }
620
                  }
621
                  else {
622
                    values[name] = value;
623
                  }
624
                }
625
              }
626
            });
627
            return values;
628
          }
629
        },
630
631
        has: {
632
633
          field: function(identifier) {
634
            module.verbose('Checking for existence of a field with identifier', identifier);
635
            identifier = module.escape.string(identifier);
636
            if(typeof identifier !== 'string') {
637
              module.error(error.identifier, identifier);
638
            }
639
            if($field.filter('#' + identifier).length > 0 ) {
640
              return true;
641
            }
642
            else if( $field.filter('[name="' + identifier +'"]').length > 0 ) {
643
              return true;
644
            }
645
            else if( $field.filter('[data-' + metadata.validate + '="'+ identifier +'"]').length > 0 ) {
646
              return true;
647
            }
648
            return false;
649
          }
650
651
        },
652
653
        escape: {
654
          string: function(text) {
655
            text =  String(text);
656
            return text.replace(regExp.escape, '\\$&');
657
          }
658
        },
659
660
        add: {
661
          // alias
662
          rule: function(name, rules) {
663
            module.add.field(name, rules);
664
          },
665
          field: function(name, rules) {
666
            var
667
              newValidation = {}
668
            ;
669
            if(module.is.shorthandRules(rules)) {
670
              rules = $.isArray(rules)
671
                ? rules
672
                : [rules]
673
              ;
674
              newValidation[name] = {
675
                rules: []
676
              };
677
              $.each(rules, function(index, rule) {
678
                newValidation[name].rules.push({ type: rule });
679
              });
680
            }
681
            else {
682
              newValidation[name] = rules;
683
            }
684
            validation = $.extend({}, validation, newValidation);
685
            module.debug('Adding rules', newValidation, validation);
686
          },
687
          fields: function(fields) {
688
            var
689
              newValidation
690
            ;
691
            if(fields && module.is.shorthandFields(fields)) {
692
              newValidation = module.get.fieldsFromShorthand(fields);
693
            }
694
            else {
695
              newValidation = fields;
696
            }
697
            validation = $.extend({}, validation, newValidation);
698
          },
699
          prompt: function(identifier, errors) {
700
            var
701
              $field       = module.get.field(identifier),
702
              $fieldGroup  = $field.closest($group),
703
              $prompt      = $fieldGroup.children(selector.prompt),
704
              promptExists = ($prompt.length !== 0)
705
            ;
706
            errors = (typeof errors == 'string')
707
              ? [errors]
708
              : errors
709
            ;
710
            module.verbose('Adding field error state', identifier);
711
            $fieldGroup
712
              .addClass(className.error)
713
            ;
714
            if(settings.inline) {
715
              if(!promptExists) {
716
                $prompt = settings.templates.prompt(errors);
717
                $prompt
718
                  .appendTo($fieldGroup)
719
                ;
720
              }
721
              $prompt
722
                .html(errors[0])
723
              ;
724
              if(!promptExists) {
725
                if(settings.transition && $.fn.transition !== undefined && $module.transition('is supported')) {
726
                  module.verbose('Displaying error with css transition', settings.transition);
727
                  $prompt.transition(settings.transition + ' in', settings.duration);
728
                }
729
                else {
730
                  module.verbose('Displaying error with fallback javascript animation');
731
                  $prompt
732
                    .fadeIn(settings.duration)
733
                  ;
734
                }
735
              }
736
              else {
737
                module.verbose('Inline errors are disabled, no inline error added', identifier);
738
              }
739
            }
740
          },
741
          errors: function(errors) {
742
            module.debug('Adding form error messages', errors);
743
            module.set.error();
744
            $message
745
              .html( settings.templates.error(errors) )
746
            ;
747
          }
748
        },
749
750
        remove: {
751
          rule: function(field, rule) {
752
            var
753
              rules = $.isArray(rule)
754
                ? rule
755
                : [rule]
756
            ;
757
            if(rule == undefined) {
0 ignored issues
show
Best Practice introduced by
Comparing rule to undefined using the == operator is not safe. Consider using === instead.
Loading history...
758
              module.debug('Removed all rules');
759
              validation[field].rules = [];
760
              return;
761
            }
762
            if(validation[field] == undefined || !$.isArray(validation[field].rules)) {
0 ignored issues
show
Best Practice introduced by
Comparing validation.field to undefined using the == operator is not safe. Consider using === instead.
Loading history...
763
              return;
764
            }
765
            $.each(validation[field].rules, function(index, rule) {
766
              if(rules.indexOf(rule.type) !== -1) {
767
                module.debug('Removed rule', rule.type);
768
                validation[field].rules.splice(index, 1);
769
              }
770
            });
771
          },
772
          field: function(field) {
773
            var
774
              fields = $.isArray(field)
775
                ? field
776
                : [field]
777
            ;
778
            $.each(fields, function(index, field) {
779
              module.remove.rule(field);
780
            });
781
          },
782
          // alias
783
          rules: function(field, rules) {
784
            if($.isArray(field)) {
785
              $.each(fields, function(index, field) {
0 ignored issues
show
Bug introduced by
The variable fields seems to be never declared. If this is a global, consider adding a /** global: fields */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
786
                module.remove.rule(field, rules);
787
              });
788
            }
789
            else {
790
              module.remove.rule(field, rules);
791
            }
792
          },
793
          fields: function(fields) {
794
            module.remove.field(fields);
795
          },
796
          prompt: function(identifier) {
797
            var
798
              $field      = module.get.field(identifier),
799
              $fieldGroup = $field.closest($group),
800
              $prompt     = $fieldGroup.children(selector.prompt)
801
            ;
802
            $fieldGroup
803
              .removeClass(className.error)
804
            ;
805
            if(settings.inline && $prompt.is(':visible')) {
806
              module.verbose('Removing prompt for field', identifier);
807
              if(settings.transition && $.fn.transition !== undefined && $module.transition('is supported')) {
808
                $prompt.transition(settings.transition + ' out', settings.duration, function() {
809
                  $prompt.remove();
810
                });
811
              }
812
              else {
813
                $prompt
814
                  .fadeOut(settings.duration, function(){
815
                    $prompt.remove();
816
                  })
817
                ;
818
              }
819
            }
820
          }
821
        },
822
823
        set: {
824
          success: function() {
825
            $module
826
              .removeClass(className.error)
827
              .addClass(className.success)
828
            ;
829
          },
830
          defaults: function () {
831
            $field
832
              .each(function () {
833
                var
834
                  $field     = $(this),
835
                  isCheckbox = ($field.filter(selector.checkbox).length > 0),
836
                  value      = (isCheckbox)
837
                    ? $field.is(':checked')
838
                    : $field.val()
839
                ;
840
                $field.data(metadata.defaultValue, value);
841
              })
842
            ;
843
          },
844
          error: function() {
845
            $module
846
              .removeClass(className.success)
847
              .addClass(className.error)
848
            ;
849
          },
850
          value: function (field, value) {
851
            var
852
              fields = {}
853
            ;
854
            fields[field] = value;
855
            return module.set.values.call(element, fields);
856
          },
857
          values: function (fields) {
858
            if($.isEmptyObject(fields)) {
859
              return;
860
            }
861
            $.each(fields, function(key, value) {
862
              var
863
                $field      = module.get.field(key),
864
                $element    = $field.parent(),
865
                isMultiple  = $.isArray(value),
866
                isCheckbox  = $element.is(selector.uiCheckbox),
867
                isDropdown  = $element.is(selector.uiDropdown),
868
                isRadio     = ($field.is(selector.radio) && isCheckbox),
869
                fieldExists = ($field.length > 0),
870
                $multipleField
871
              ;
872
              if(fieldExists) {
873
                if(isMultiple && isCheckbox) {
874
                  module.verbose('Selecting multiple', value, $field);
875
                  $element.checkbox('uncheck');
876
                  $.each(value, function(index, value) {
877
                    $multipleField = $field.filter('[value="' + value + '"]');
878
                    $element       = $multipleField.parent();
879
                    if($multipleField.length > 0) {
880
                      $element.checkbox('check');
881
                    }
882
                  });
883
                }
884
                else if(isRadio) {
885
                  module.verbose('Selecting radio value', value, $field);
886
                  $field.filter('[value="' + value + '"]')
887
                    .parent(selector.uiCheckbox)
888
                      .checkbox('check')
889
                  ;
890
                }
891
                else if(isCheckbox) {
892
                  module.verbose('Setting checkbox value', value, $element);
893
                  if(value === true) {
894
                    $element.checkbox('check');
895
                  }
896
                  else {
897
                    $element.checkbox('uncheck');
898
                  }
899
                }
900
                else if(isDropdown) {
901
                  module.verbose('Setting dropdown value', value, $element);
902
                  $element.dropdown('set selected', value);
903
                }
904
                else {
905
                  module.verbose('Setting field value', value, $field);
906
                  $field.val(value);
907
                }
908
              }
909
            });
910
          }
911
        },
912
913
        validate: {
914
915
          form: function(event, ignoreCallbacks) {
916
            var
917
              values = module.get.values(),
918
              apiRequest
0 ignored issues
show
Unused Code introduced by
The variable apiRequest seems to be never used. Consider removing it.
Loading history...
919
            ;
920
921
            // input keydown event will fire submit repeatedly by browser default
922
            if(keyHeldDown) {
923
              return false;
924
            }
925
926
            // reset errors
927
            formErrors = [];
928
            if( module.determine.isValid() ) {
929
              module.debug('Form has no validation errors, submitting');
930
              module.set.success();
931
              if(ignoreCallbacks !== true) {
0 ignored issues
show
Complexity Best Practice introduced by
There is no return statement if ignoreCallbacks !== true is false. Are you sure this is correct? If so, consider adding return; explicitly.

This check looks for functions where a return statement is found in some execution paths, but not in all.

Consider this little piece of code

function isBig(a) {
    if (a > 5000) {
        return "yes";
    }
}

console.log(isBig(5001)); //returns yes
console.log(isBig(42)); //returns undefined

The function isBig will only return a specific value when its parameter is bigger than 5000. In any other case, it will implicitly return undefined.

This behaviour may not be what you had intended. In any case, you can add a return undefined to the other execution path to make the return value explicit.

Loading history...
932
                return settings.onSuccess.call(element, event, values);
933
              }
934
            }
935
            else {
936
              module.debug('Form has errors');
937
              module.set.error();
938
              if(!settings.inline) {
939
                module.add.errors(formErrors);
940
              }
941
              // prevent ajax submit
942
              if($module.data('moduleApi') !== undefined) {
943
                event.stopImmediatePropagation();
944
              }
945
              if(ignoreCallbacks !== true) {
0 ignored issues
show
Complexity Best Practice introduced by
There is no return statement if ignoreCallbacks !== true is false. Are you sure this is correct? If so, consider adding return; explicitly.

This check looks for functions where a return statement is found in some execution paths, but not in all.

Consider this little piece of code

function isBig(a) {
    if (a > 5000) {
        return "yes";
    }
}

console.log(isBig(5001)); //returns yes
console.log(isBig(42)); //returns undefined

The function isBig will only return a specific value when its parameter is bigger than 5000. In any other case, it will implicitly return undefined.

This behaviour may not be what you had intended. In any case, you can add a return undefined to the other execution path to make the return value explicit.

Loading history...
946
                return settings.onFailure.call(element, formErrors, values);
947
              }
948
            }
949
          },
950
951
          // takes a validation object and returns whether field passes validation
952
          field: function(field, fieldName, showErrors) {
953
            showErrors = (showErrors !== undefined)
954
              ? showErrors
955
              : true
956
            ;
957
            if(typeof field == 'string') {
958
              module.verbose('Validating field', field);
959
              fieldName = field;
960
              field     = validation[field];
961
            }
962
            var
963
              identifier    = field.identifier || fieldName,
964
              $field        = module.get.field(identifier),
965
              $dependsField = (field.depends)
966
                ? module.get.field(field.depends)
967
                : false,
968
              fieldValid  = true,
969
              fieldErrors = []
970
            ;
971
            if(!field.identifier) {
972
              module.debug('Using field name as identifier', identifier);
973
              field.identifier = identifier;
974
            }
975
            if($field.prop('disabled')) {
976
              module.debug('Field is disabled. Skipping', identifier);
977
              fieldValid = true;
978
            }
979
            else if(field.optional && module.is.blank($field)){
980
              module.debug('Field is optional and blank. Skipping', identifier);
981
              fieldValid = true;
982
            }
983
            else if(field.depends && module.is.empty($dependsField)) {
984
              module.debug('Field depends on another value that is not present or empty. Skipping', $dependsField);
985
              fieldValid = true;
986
            }
987
            else if(field.rules !== undefined) {
988
              $.each(field.rules, function(index, rule) {
989
                if( module.has.field(identifier) && !( module.validate.rule(field, rule) ) ) {
990
                  module.debug('Field is invalid', identifier, rule.type);
991
                  fieldErrors.push(module.get.prompt(rule, field));
992
                  fieldValid = false;
993
                }
994
              });
995
            }
996
            if(fieldValid) {
997
              if(showErrors) {
998
                module.remove.prompt(identifier, fieldErrors);
999
                settings.onValid.call($field);
1000
              }
1001
            }
1002
            else {
1003
              if(showErrors) {
1004
                formErrors = formErrors.concat(fieldErrors);
1005
                module.add.prompt(identifier, fieldErrors);
1006
                settings.onInvalid.call($field, fieldErrors);
1007
              }
1008
              return false;
1009
            }
1010
            return true;
1011
          },
1012
1013
          // takes validation rule and returns whether field passes rule
1014
          rule: function(field, rule) {
1015
            var
1016
              $field       = module.get.field(field.identifier),
1017
              type         = rule.type,
0 ignored issues
show
Unused Code introduced by
The assignment to variable type seems to be never used. Consider removing it.
Loading history...
1018
              value        = $field.val(),
1019
              isValid      = true,
0 ignored issues
show
Unused Code introduced by
The variable isValid seems to be never used. Consider removing it.
Loading history...
1020
              ancillary    = module.get.ancillaryValue(rule),
1021
              ruleName     = module.get.ruleName(rule),
1022
              ruleFunction = settings.rules[ruleName]
1023
            ;
1024
            if( !$.isFunction(ruleFunction) ) {
1025
              module.error(error.noRule, ruleName);
1026
              return;
0 ignored issues
show
Comprehensibility Best Practice introduced by
Are you sure this return statement is not missing an argument? If this is intended, consider adding an explicit undefined like return undefined;.
Loading history...
1027
            }
1028
            // cast to string avoiding encoding special values
1029
            value = (value === undefined || value === '' || value === null)
1030
              ? ''
1031
              : $.trim(value + '')
1032
            ;
1033
            return ruleFunction.call($field, value, ancillary);
1034
          }
1035
        },
1036
1037
        setting: function(name, value) {
1038
          if( $.isPlainObject(name) ) {
1039
            $.extend(true, settings, name);
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
1040
          }
1041
          else if(value !== undefined) {
1042
            settings[name] = value;
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
1043
          }
1044
          else {
1045
            return settings[name];
1046
          }
1047
        },
1048
        internal: function(name, value) {
1049
          if( $.isPlainObject(name) ) {
1050
            $.extend(true, module, name);
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
1051
          }
1052
          else if(value !== undefined) {
1053
            module[name] = value;
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
1054
          }
1055
          else {
1056
            return module[name];
1057
          }
1058
        },
1059
        debug: function() {
1060
          if(!settings.silent && settings.debug) {
1061
            if(settings.performance) {
1062
              module.performance.log(arguments);
1063
            }
1064
            else {
1065
              module.debug = Function.prototype.bind.call(console.info, console, settings.name + ':');
1066
              module.debug.apply(console, arguments);
1067
            }
1068
          }
1069
        },
1070
        verbose: function() {
1071
          if(!settings.silent && settings.verbose && settings.debug) {
1072
            if(settings.performance) {
1073
              module.performance.log(arguments);
1074
            }
1075
            else {
1076
              module.verbose = Function.prototype.bind.call(console.info, console, settings.name + ':');
1077
              module.verbose.apply(console, arguments);
1078
            }
1079
          }
1080
        },
1081
        error: function() {
1082
          if(!settings.silent) {
1083
            module.error = Function.prototype.bind.call(console.error, console, settings.name + ':');
1084
            module.error.apply(console, arguments);
1085
          }
1086
        },
1087
        performance: {
1088
          log: function(message) {
1089
            var
1090
              currentTime,
1091
              executionTime,
1092
              previousTime
1093
            ;
1094
            if(settings.performance) {
1095
              currentTime   = new Date().getTime();
1096
              previousTime  = time || currentTime;
1097
              executionTime = currentTime - previousTime;
1098
              time          = currentTime;
1099
              performance.push({
1100
                'Name'           : message[0],
1101
                'Arguments'      : [].slice.call(message, 1) || '',
1102
                'Element'        : element,
1103
                'Execution Time' : executionTime
1104
              });
1105
            }
1106
            clearTimeout(module.performance.timer);
1107
            module.performance.timer = setTimeout(module.performance.display, 500);
1108
          },
1109
          display: function() {
1110
            var
1111
              title = settings.name + ':',
1112
              totalTime = 0
1113
            ;
1114
            time = false;
1115
            clearTimeout(module.performance.timer);
1116
            $.each(performance, function(index, data) {
1117
              totalTime += data['Execution Time'];
1118
            });
1119
            title += ' ' + totalTime + 'ms';
1120
            if(moduleSelector) {
1121
              title += ' \'' + moduleSelector + '\'';
1122
            }
1123
            if($allModules.length > 1) {
1124
              title += ' ' + '(' + $allModules.length + ')';
1125
            }
1126
            if( (console.group !== undefined || console.table !== undefined) && performance.length > 0) {
1127
              console.groupCollapsed(title);
1128
              if(console.table) {
1129
                console.table(performance);
1130
              }
1131
              else {
1132
                $.each(performance, function(index, data) {
1133
                  console.log(data['Name'] + ': ' + data['Execution Time']+'ms');
0 ignored issues
show
Debugging Code introduced by
console.log looks like debug code. Are you sure you do not want to remove it?
Loading history...
1134
                });
1135
              }
1136
              console.groupEnd();
1137
            }
1138
            performance = [];
1139
          }
1140
        },
1141
        invoke: function(query, passedArguments, context) {
1142
          var
1143
            object = instance,
1144
            maxDepth,
1145
            found,
1146
            response
1147
          ;
1148
          passedArguments = passedArguments || queryArguments;
1149
          context         = element         || context;
1150
          if(typeof query == 'string' && object !== undefined) {
1151
            query    = query.split(/[\. ]/);
1152
            maxDepth = query.length - 1;
1153
            $.each(query, function(depth, value) {
1154
              var camelCaseValue = (depth != maxDepth)
1155
                ? value + query[depth + 1].charAt(0).toUpperCase() + query[depth + 1].slice(1)
1156
                : query
1157
              ;
1158
              if( $.isPlainObject( object[camelCaseValue] ) && (depth != maxDepth) ) {
1159
                object = object[camelCaseValue];
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
1160
              }
1161
              else if( object[camelCaseValue] !== undefined ) {
1162
                found = object[camelCaseValue];
1163
                return false;
1164
              }
1165
              else if( $.isPlainObject( object[value] ) && (depth != maxDepth) ) {
1166
                object = object[value];
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
1167
              }
1168
              else if( object[value] !== undefined ) {
1169
                found = object[value];
1170
                return false;
1171
              }
1172
              else {
1173
                return false;
1174
              }
1175
            });
1176
          }
1177
          if( $.isFunction( found ) ) {
1178
            response = found.apply(context, passedArguments);
1179
          }
1180
          else if(found !== undefined) {
1181
            response = found;
1182
          }
1183
          if($.isArray(returnedValue)) {
1184
            returnedValue.push(response);
0 ignored issues
show
Bug introduced by
The variable response does not seem to be initialized in case found !== undefined on line 1180 is false. Are you sure the function push handles undefined variables?
Loading history...
1185
          }
1186
          else if(returnedValue !== undefined) {
1187
            returnedValue = [returnedValue, response];
1188
          }
1189
          else if(response !== undefined) {
1190
            returnedValue = response;
1191
          }
1192
          return found;
1193
        }
1194
      };
1195
      module.initialize();
1196
    })
1197
  ;
1198
1199
  return (returnedValue !== undefined)
1200
    ? returnedValue
1201
    : this
1202
  ;
1203
};
1204
1205
$.fn.form.settings = {
1206
1207
  name              : 'Form',
1208
  namespace         : 'form',
1209
1210
  debug             : false,
1211
  verbose           : false,
1212
  performance       : true,
1213
1214
  fields            : false,
1215
1216
  keyboardShortcuts : true,
1217
  on                : 'submit',
1218
  inline            : false,
1219
1220
  delay             : 200,
1221
  revalidate        : true,
1222
1223
  transition        : 'scale',
1224
  duration          : 200,
1225
1226
  onValid           : function() {},
1227
  onInvalid         : function() {},
1228
  onSuccess         : function() { return true; },
1229
  onFailure         : function() { return false; },
1230
1231
  metadata : {
1232
    defaultValue : 'default',
1233
    validate     : 'validate'
1234
  },
1235
1236
  regExp: {
1237
    htmlID  : /^[a-zA-Z][\w:.-]*$/g,
1238
    bracket : /\[(.*)\]/i,
1239
    decimal : /^\d+\.?\d*$/,
1240
    email   : /^[a-z0-9!#$%&'*+\/=?^_`{|}~.-]+@[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/i,
1241
    escape  : /[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g,
1242
    flags   : /^\/(.*)\/(.*)?/,
1243
    integer : /^\-?\d+$/,
1244
    number  : /^\-?\d*(\.\d+)?$/,
1245
    url     : /(https?:\/\/(?:www\.|(?!www))[^\s\.]+\.[^\s]{2,}|www\.[^\s]+\.[^\s]{2,})/i
1246
  },
1247
1248
  text: {
1249
    unspecifiedRule  : 'Please enter a valid value',
1250
    unspecifiedField : 'This field'
1251
  },
1252
1253
  prompt: {
1254
    empty                : '{name} must have a value',
1255
    checked              : '{name} must be checked',
1256
    email                : '{name} must be a valid e-mail',
1257
    url                  : '{name} must be a valid url',
1258
    regExp               : '{name} is not formatted correctly',
1259
    integer              : '{name} must be an integer',
1260
    decimal              : '{name} must be a decimal number',
1261
    number               : '{name} must be set to a number',
1262
    is                   : '{name} must be "{ruleValue}"',
1263
    isExactly            : '{name} must be exactly "{ruleValue}"',
1264
    not                  : '{name} cannot be set to "{ruleValue}"',
1265
    notExactly           : '{name} cannot be set to exactly "{ruleValue}"',
1266
    contain              : '{name} cannot contain "{ruleValue}"',
1267
    containExactly       : '{name} cannot contain exactly "{ruleValue}"',
1268
    doesntContain        : '{name} must contain  "{ruleValue}"',
1269
    doesntContainExactly : '{name} must contain exactly "{ruleValue}"',
1270
    minLength            : '{name} must be at least {ruleValue} characters',
1271
    length               : '{name} must be at least {ruleValue} characters',
1272
    exactLength          : '{name} must be exactly {ruleValue} characters',
1273
    maxLength            : '{name} cannot be longer than {ruleValue} characters',
1274
    match                : '{name} must match {ruleValue} field',
1275
    different            : '{name} must have a different value than {ruleValue} field',
1276
    creditCard           : '{name} must be a valid credit card number',
1277
    minCount             : '{name} must have at least {ruleValue} choices',
1278
    exactCount           : '{name} must have exactly {ruleValue} choices',
1279
    maxCount             : '{name} must have {ruleValue} or less choices'
1280
  },
1281
1282
  selector : {
1283
    checkbox   : 'input[type="checkbox"], input[type="radio"]',
1284
    clear      : '.clear',
1285
    field      : 'input, textarea, select',
1286
    group      : '.field',
1287
    input      : 'input',
1288
    message    : '.error.message',
1289
    prompt     : '.prompt.label',
1290
    radio      : 'input[type="radio"]',
1291
    reset      : '.reset:not([type="reset"])',
1292
    submit     : '.submit:not([type="submit"])',
1293
    uiCheckbox : '.ui.checkbox',
1294
    uiDropdown : '.ui.dropdown'
1295
  },
1296
1297
  className : {
1298
    error   : 'error',
1299
    label   : 'ui prompt label',
1300
    pressed : 'down',
1301
    success : 'success'
1302
  },
1303
1304
  error: {
1305
    identifier : 'You must specify a string identifier for each field',
1306
    method     : 'The method you called is not defined.',
1307
    noRule     : 'There is no rule matching the one you specified',
1308
    oldSyntax  : 'Starting in 2.0 forms now only take a single settings object. Validation settings converted to new syntax automatically.'
1309
  },
1310
1311
  templates: {
1312
1313
    // template that produces error message
1314
    error: function(errors) {
1315
      var
1316
        html = '<ul class="list">'
1317
      ;
1318
      $.each(errors, function(index, value) {
1319
        html += '<li>' + value + '</li>';
1320
      });
1321
      html += '</ul>';
1322
      return $(html);
1323
    },
1324
1325
    // template that produces label
1326
    prompt: function(errors) {
1327
      return $('<div/>')
1328
        .addClass('ui basic red pointing prompt label')
1329
        .html(errors[0])
1330
      ;
1331
    }
1332
  },
1333
1334
  rules: {
1335
1336
    // is not empty or blank string
1337
    empty: function(value) {
1338
      return !(value === undefined || '' === value || $.isArray(value) && value.length === 0);
1339
    },
1340
1341
    // checkbox checked
1342
    checked: function() {
1343
      return ($(this).filter(':checked').length > 0);
1344
    },
1345
1346
    // is most likely an email
1347
    email: function(value){
1348
      return $.fn.form.settings.regExp.email.test(value);
1349
    },
1350
1351
    // value is most likely url
1352
    url: function(value) {
1353
      return $.fn.form.settings.regExp.url.test(value);
1354
    },
1355
1356
    // matches specified regExp
1357
    regExp: function(value, regExp) {
1358
      if(regExp instanceof RegExp) {
1359
        return value.match(regExp);
1360
      }
1361
      var
1362
        regExpParts = regExp.match($.fn.form.settings.regExp.flags),
1363
        flags
1364
      ;
1365
      // regular expression specified as /baz/gi (flags)
1366
      if(regExpParts) {
1367
        regExp = (regExpParts.length >= 2)
1368
          ? regExpParts[1]
1369
          : regExp
1370
        ;
1371
        flags = (regExpParts.length >= 3)
1372
          ? regExpParts[2]
1373
          : ''
1374
        ;
1375
      }
1376
      return value.match( new RegExp(regExp, flags) );
0 ignored issues
show
Bug introduced by
The variable flags does not seem to be initialized in case regExpParts on line 1366 is false. Are you sure this can never be the case?
Loading history...
1377
    },
1378
1379
    // is valid integer or matches range
1380
    integer: function(value, range) {
1381
      var
1382
        intRegExp = $.fn.form.settings.regExp.integer,
1383
        min,
1384
        max,
1385
        parts
1386
      ;
1387
      if( !range || ['', '..'].indexOf(range) !== -1) {
0 ignored issues
show
Comprehensibility Documentation Best Practice introduced by
This code block is empty. Consider removing it or adding a comment to explain.
Loading history...
1388
        // do nothing
1389
      }
1390
      else if(range.indexOf('..') == -1) {
1391
        if(intRegExp.test(range)) {
1392
          min = max = range - 0;
1393
        }
1394
      }
1395
      else {
1396
        parts = range.split('..', 2);
1397
        if(intRegExp.test(parts[0])) {
1398
          min = parts[0] - 0;
1399
        }
1400
        if(intRegExp.test(parts[1])) {
1401
          max = parts[1] - 0;
1402
        }
1403
      }
1404
      return (
1405
        intRegExp.test(value) &&
1406
        (min === undefined || value >= min) &&
0 ignored issues
show
Bug introduced by
The variable min seems to not be initialized for all possible execution paths.
Loading history...
1407
        (max === undefined || value <= max)
0 ignored issues
show
Bug introduced by
The variable max seems to not be initialized for all possible execution paths.
Loading history...
1408
      );
1409
    },
1410
1411
    // is valid number (with decimal)
1412
    decimal: function(value) {
1413
      return $.fn.form.settings.regExp.decimal.test(value);
1414
    },
1415
1416
    // is valid number
1417
    number: function(value) {
1418
      return $.fn.form.settings.regExp.number.test(value);
1419
    },
1420
1421
    // is value (case insensitive)
1422
    is: function(value, text) {
1423
      text = (typeof text == 'string')
1424
        ? text.toLowerCase()
1425
        : text
1426
      ;
1427
      value = (typeof value == 'string')
1428
        ? value.toLowerCase()
1429
        : value
1430
      ;
1431
      return (value == text);
1432
    },
1433
1434
    // is value
1435
    isExactly: function(value, text) {
1436
      return (value == text);
1437
    },
1438
1439
    // value is not another value (case insensitive)
1440
    not: function(value, notValue) {
1441
      value = (typeof value == 'string')
1442
        ? value.toLowerCase()
1443
        : value
1444
      ;
1445
      notValue = (typeof notValue == 'string')
1446
        ? notValue.toLowerCase()
1447
        : notValue
1448
      ;
1449
      return (value != notValue);
1450
    },
1451
1452
    // value is not another value (case sensitive)
1453
    notExactly: function(value, notValue) {
1454
      return (value != notValue);
1455
    },
1456
1457
    // value contains text (insensitive)
1458
    contains: function(value, text) {
1459
      // escape regex characters
1460
      text = text.replace($.fn.form.settings.regExp.escape, "\\$&");
1461
      return (value.search( new RegExp(text, 'i') ) !== -1);
1462
    },
1463
1464
    // value contains text (case sensitive)
1465
    containsExactly: function(value, text) {
1466
      // escape regex characters
1467
      text = text.replace($.fn.form.settings.regExp.escape, "\\$&");
1468
      return (value.search( new RegExp(text) ) !== -1);
1469
    },
1470
1471
    // value contains text (insensitive)
1472
    doesntContain: function(value, text) {
1473
      // escape regex characters
1474
      text = text.replace($.fn.form.settings.regExp.escape, "\\$&");
1475
      return (value.search( new RegExp(text, 'i') ) === -1);
1476
    },
1477
1478
    // value contains text (case sensitive)
1479
    doesntContainExactly: function(value, text) {
1480
      // escape regex characters
1481
      text = text.replace($.fn.form.settings.regExp.escape, "\\$&");
1482
      return (value.search( new RegExp(text) ) === -1);
1483
    },
1484
1485
    // is at least string length
1486
    minLength: function(value, requiredLength) {
1487
      return (value !== undefined)
1488
        ? (value.length >= requiredLength)
1489
        : false
1490
      ;
1491
    },
1492
1493
    // see rls notes for 2.0.6 (this is a duplicate of minLength)
1494
    length: function(value, requiredLength) {
1495
      return (value !== undefined)
1496
        ? (value.length >= requiredLength)
1497
        : false
1498
      ;
1499
    },
1500
1501
    // is exactly length
1502
    exactLength: function(value, requiredLength) {
1503
      return (value !== undefined)
1504
        ? (value.length == requiredLength)
1505
        : false
1506
      ;
1507
    },
1508
1509
    // is less than length
1510
    maxLength: function(value, maxLength) {
1511
      return (value !== undefined)
1512
        ? (value.length <= maxLength)
1513
        : false
1514
      ;
1515
    },
1516
1517
    // matches another field
1518
    match: function(value, identifier) {
1519
      var
1520
        $form = $(this),
0 ignored issues
show
Unused Code introduced by
The variable $form seems to be never used. Consider removing it.
Loading history...
1521
        matchingValue
1522
      ;
1523
      if( $('[data-validate="'+ identifier +'"]').length > 0 ) {
1524
        matchingValue = $('[data-validate="'+ identifier +'"]').val();
1525
      }
1526
      else if($('#' + identifier).length > 0) {
1527
        matchingValue = $('#' + identifier).val();
1528
      }
1529
      else if($('[name="' + identifier +'"]').length > 0) {
1530
        matchingValue = $('[name="' + identifier + '"]').val();
1531
      }
1532
      else if( $('[name="' + identifier +'[]"]').length > 0 ) {
1533
        matchingValue = $('[name="' + identifier +'[]"]');
1534
      }
1535
      return (matchingValue !== undefined)
0 ignored issues
show
Bug introduced by
The variable matchingValue does not seem to be initialized in case $("[name="" + identifier + "[]"]").length > 0 on line 1532 is false. Are you sure this can never be the case?
Loading history...
1536
        ? ( value.toString() == matchingValue.toString() )
1537
        : false
1538
      ;
1539
    },
1540
1541
    // different than another field
1542
    different: function(value, identifier) {
1543
      // use either id or name of field
1544
      var
1545
        $form = $(this),
0 ignored issues
show
Unused Code introduced by
The variable $form seems to be never used. Consider removing it.
Loading history...
1546
        matchingValue
1547
      ;
1548
      if( $('[data-validate="'+ identifier +'"]').length > 0 ) {
1549
        matchingValue = $('[data-validate="'+ identifier +'"]').val();
1550
      }
1551
      else if($('#' + identifier).length > 0) {
1552
        matchingValue = $('#' + identifier).val();
1553
      }
1554
      else if($('[name="' + identifier +'"]').length > 0) {
1555
        matchingValue = $('[name="' + identifier + '"]').val();
1556
      }
1557
      else if( $('[name="' + identifier +'[]"]').length > 0 ) {
1558
        matchingValue = $('[name="' + identifier +'[]"]');
1559
      }
1560
      return (matchingValue !== undefined)
0 ignored issues
show
Bug introduced by
The variable matchingValue does not seem to be initialized in case $("[name="" + identifier + "[]"]").length > 0 on line 1557 is false. Are you sure this can never be the case?
Loading history...
1561
        ? ( value.toString() !== matchingValue.toString() )
1562
        : false
1563
      ;
1564
    },
1565
1566
    creditCard: function(cardNumber, cardTypes) {
1567
      var
1568
        cards = {
1569
          visa: {
1570
            pattern : /^4/,
1571
            length  : [16]
1572
          },
1573
          amex: {
1574
            pattern : /^3[47]/,
1575
            length  : [15]
1576
          },
1577
          mastercard: {
1578
            pattern : /^5[1-5]/,
1579
            length  : [16]
1580
          },
1581
          discover: {
1582
            pattern : /^(6011|622(12[6-9]|1[3-9][0-9]|[2-8][0-9]{2}|9[0-1][0-9]|92[0-5]|64[4-9])|65)/,
1583
            length  : [16]
1584
          },
1585
          unionPay: {
1586
            pattern : /^(62|88)/,
1587
            length  : [16, 17, 18, 19]
1588
          },
1589
          jcb: {
1590
            pattern : /^35(2[89]|[3-8][0-9])/,
1591
            length  : [16]
1592
          },
1593
          maestro: {
1594
            pattern : /^(5018|5020|5038|6304|6759|676[1-3])/,
1595
            length  : [12, 13, 14, 15, 16, 17, 18, 19]
1596
          },
1597
          dinersClub: {
1598
            pattern : /^(30[0-5]|^36)/,
1599
            length  : [14]
1600
          },
1601
          laser: {
1602
            pattern : /^(6304|670[69]|6771)/,
1603
            length  : [16, 17, 18, 19]
1604
          },
1605
          visaElectron: {
1606
            pattern : /^(4026|417500|4508|4844|491(3|7))/,
1607
            length  : [16]
1608
          }
1609
        },
1610
        valid         = {},
0 ignored issues
show
Unused Code introduced by
The assignment to variable valid seems to be never used. Consider removing it.
Loading history...
1611
        validCard     = false,
1612
        requiredTypes = (typeof cardTypes == 'string')
1613
          ? cardTypes.split(',')
1614
          : false,
1615
        unionPay,
1616
        validation
1617
      ;
1618
1619
      if(typeof cardNumber !== 'string' || cardNumber.length === 0) {
1620
        return;
0 ignored issues
show
Comprehensibility Best Practice introduced by
Are you sure this return statement is not missing an argument? If this is intended, consider adding an explicit undefined like return undefined;.
Loading history...
1621
      }
1622
1623
      // allow dashes in card
1624
      cardNumber = cardNumber.replace(/[\-]/g, '');
1625
1626
      // verify card types
1627
      if(requiredTypes) {
1628
        $.each(requiredTypes, function(index, type){
1629
          // verify each card type
1630
          validation = cards[type];
1631
          if(validation) {
1632
            valid = {
1633
              length  : ($.inArray(cardNumber.length, validation.length) !== -1),
1634
              pattern : (cardNumber.search(validation.pattern) !== -1)
1635
            };
1636
            if(valid.length && valid.pattern) {
1637
              validCard = true;
1638
            }
1639
          }
1640
        });
1641
1642
        if(!validCard) {
1643
          return false;
1644
        }
1645
      }
1646
1647
      // skip luhn for UnionPay
1648
      unionPay = {
1649
        number  : ($.inArray(cardNumber.length, cards.unionPay.length) !== -1),
1650
        pattern : (cardNumber.search(cards.unionPay.pattern) !== -1)
1651
      };
1652
      if(unionPay.number && unionPay.pattern) {
1653
        return true;
1654
      }
1655
1656
      // verify luhn, adapted from  <https://gist.github.com/2134376>
1657
      var
1658
        length        = cardNumber.length,
1659
        multiple      = 0,
1660
        producedValue = [
1661
          [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
1662
          [0, 2, 4, 6, 8, 1, 3, 5, 7, 9]
1663
        ],
1664
        sum           = 0
1665
      ;
1666
      while (length--) {
1667
        sum += producedValue[multiple][parseInt(cardNumber.charAt(length), 10)];
1668
        multiple ^= 1;
1669
      }
1670
      return (sum % 10 === 0 && sum > 0);
1671
    },
1672
1673
    minCount: function(value, minCount) {
1674
      if(minCount == 0) {
0 ignored issues
show
Best Practice introduced by
Comparing minCount to 0 using the == operator is not safe. Consider using === instead.
Loading history...
1675
        return true;
1676
      }
1677
      if(minCount == 1) {
1678
        return (value !== '');
1679
      }
1680
      return (value.split(',').length >= minCount);
1681
    },
1682
1683
    exactCount: function(value, exactCount) {
1684
      if(exactCount == 0) {
0 ignored issues
show
Best Practice introduced by
Comparing exactCount to 0 using the == operator is not safe. Consider using === instead.
Loading history...
1685
        return (value === '');
1686
      }
1687
      if(exactCount == 1) {
1688
        return (value !== '' && value.search(',') === -1);
1689
      }
1690
      return (value.split(',').length == exactCount);
1691
    },
1692
1693
    maxCount: function(value, maxCount) {
1694
      if(maxCount == 0) {
0 ignored issues
show
Best Practice introduced by
Comparing maxCount to 0 using the == operator is not safe. Consider using === instead.
Loading history...
1695
        return false;
1696
      }
1697
      if(maxCount == 1) {
1698
        return (value.search(',') === -1);
1699
      }
1700
      return (value.split(',').length <= maxCount);
1701
    }
1702
  }
1703
1704
};
1705
1706
})( jQuery, window, document );
1707