Code Duplication    Length = 1696-1696 lines in 2 locations

public/lib/semantic/semantic.js 1 location

@@ 509-2204 (lines=1696) @@
506
 *
507
 */
508
509
;(function ($, window, document, undefined) {
510
511
"use strict";
512
513
window = (typeof window != 'undefined' && window.Math == Math)
514
  ? window
515
  : (typeof self != 'undefined' && self.Math == Math)
516
    ? self
517
    : Function('return this')()
518
;
519
520
$.fn.form = function(parameters) {
521
  var
522
    $allModules      = $(this),
523
    moduleSelector   = $allModules.selector || '',
524
525
    time             = new Date().getTime(),
526
    performance      = [],
527
528
    query            = arguments[0],
529
    legacyParameters = arguments[1],
530
    methodInvoked    = (typeof query == 'string'),
531
    queryArguments   = [].slice.call(arguments, 1),
532
    returnedValue
533
  ;
534
  $allModules
535
    .each(function() {
536
      var
537
        $module     = $(this),
538
        element     = this,
539
540
        formErrors  = [],
541
        keyHeldDown = false,
542
543
        // set at run-time
544
        $field,
545
        $group,
546
        $message,
547
        $prompt,
548
        $submit,
549
        $clear,
550
        $reset,
551
552
        settings,
553
        validation,
554
555
        metadata,
556
        selector,
557
        className,
558
        regExp,
559
        error,
560
561
        namespace,
562
        moduleNamespace,
563
        eventNamespace,
564
565
        instance,
566
        module
567
      ;
568
569
      module      = {
570
571
        initialize: function() {
572
573
          // settings grabbed at run time
574
          module.get.settings();
575
          if(methodInvoked) {
576
            if(instance === undefined) {
577
              module.instantiate();
578
            }
579
            module.invoke(query);
580
          }
581
          else {
582
            if(instance !== undefined) {
583
              instance.invoke('destroy');
584
            }
585
            module.verbose('Initializing form validation', $module, settings);
586
            module.bindEvents();
587
            module.set.defaults();
588
            module.instantiate();
589
          }
590
        },
591
592
        instantiate: function() {
593
          module.verbose('Storing instance of module', module);
594
          instance = module;
595
          $module
596
            .data(moduleNamespace, module)
597
          ;
598
        },
599
600
        destroy: function() {
601
          module.verbose('Destroying previous module', instance);
602
          module.removeEvents();
603
          $module
604
            .removeData(moduleNamespace)
605
          ;
606
        },
607
608
        refresh: function() {
609
          module.verbose('Refreshing selector cache');
610
          $field      = $module.find(selector.field);
611
          $group      = $module.find(selector.group);
612
          $message    = $module.find(selector.message);
613
          $prompt     = $module.find(selector.prompt);
614
615
          $submit     = $module.find(selector.submit);
616
          $clear      = $module.find(selector.clear);
617
          $reset      = $module.find(selector.reset);
618
        },
619
620
        submit: function() {
621
          module.verbose('Submitting form', $module);
622
          $module
623
            .submit()
624
          ;
625
        },
626
627
        attachEvents: function(selector, action) {
628
          action = action || 'submit';
629
          $(selector)
630
            .on('click' + eventNamespace, function(event) {
631
              module[action]();
632
              event.preventDefault();
633
            })
634
          ;
635
        },
636
637
        bindEvents: function() {
638
          module.verbose('Attaching form events');
639
          $module
640
            .on('submit' + eventNamespace, module.validate.form)
641
            .on('blur'   + eventNamespace, selector.field, module.event.field.blur)
642
            .on('click'  + eventNamespace, selector.submit, module.submit)
643
            .on('click'  + eventNamespace, selector.reset, module.reset)
644
            .on('click'  + eventNamespace, selector.clear, module.clear)
645
          ;
646
          if(settings.keyboardShortcuts) {
647
            $module
648
              .on('keydown' + eventNamespace, selector.field, module.event.field.keydown)
649
            ;
650
          }
651
          $field
652
            .each(function() {
653
              var
654
                $input     = $(this),
655
                type       = $input.prop('type'),
656
                inputEvent = module.get.changeEvent(type, $input)
657
              ;
658
              $(this)
659
                .on(inputEvent + eventNamespace, module.event.field.change)
660
              ;
661
            })
662
          ;
663
        },
664
665
        clear: function() {
666
          $field
667
            .each(function () {
668
              var
669
                $field       = $(this),
670
                $element     = $field.parent(),
671
                $fieldGroup  = $field.closest($group),
672
                $prompt      = $fieldGroup.find(selector.prompt),
673
                defaultValue = $field.data(metadata.defaultValue) || '',
674
                isCheckbox   = $element.is(selector.uiCheckbox),
675
                isDropdown   = $element.is(selector.uiDropdown),
676
                isErrored    = $fieldGroup.hasClass(className.error)
677
              ;
678
              if(isErrored) {
679
                module.verbose('Resetting error on field', $fieldGroup);
680
                $fieldGroup.removeClass(className.error);
681
                $prompt.remove();
682
              }
683
              if(isDropdown) {
684
                module.verbose('Resetting dropdown value', $element, defaultValue);
685
                $element.dropdown('clear');
686
              }
687
              else if(isCheckbox) {
688
                $field.prop('checked', false);
689
              }
690
              else {
691
                module.verbose('Resetting field value', $field, defaultValue);
692
                $field.val('');
693
              }
694
            })
695
          ;
696
        },
697
698
        reset: function() {
699
          $field
700
            .each(function () {
701
              var
702
                $field       = $(this),
703
                $element     = $field.parent(),
704
                $fieldGroup  = $field.closest($group),
705
                $prompt      = $fieldGroup.find(selector.prompt),
706
                defaultValue = $field.data(metadata.defaultValue),
707
                isCheckbox   = $element.is(selector.uiCheckbox),
708
                isDropdown   = $element.is(selector.uiDropdown),
709
                isErrored    = $fieldGroup.hasClass(className.error)
710
              ;
711
              if(defaultValue === undefined) {
712
                return;
713
              }
714
              if(isErrored) {
715
                module.verbose('Resetting error on field', $fieldGroup);
716
                $fieldGroup.removeClass(className.error);
717
                $prompt.remove();
718
              }
719
              if(isDropdown) {
720
                module.verbose('Resetting dropdown value', $element, defaultValue);
721
                $element.dropdown('restore defaults');
722
              }
723
              else if(isCheckbox) {
724
                module.verbose('Resetting checkbox value', $element, defaultValue);
725
                $field.prop('checked', defaultValue);
726
              }
727
              else {
728
                module.verbose('Resetting field value', $field, defaultValue);
729
                $field.val(defaultValue);
730
              }
731
            })
732
          ;
733
        },
734
735
        determine: {
736
          isValid: function() {
737
            var
738
              allValid = true
739
            ;
740
            $.each(validation, function(fieldName, field) {
741
              if( !( module.validate.field(field, fieldName, true) ) ) {
742
                allValid = false;
743
              }
744
            });
745
            return allValid;
746
          }
747
        },
748
749
        is: {
750
          bracketedRule: function(rule) {
751
            return (rule.type && rule.type.match(settings.regExp.bracket));
752
          },
753
          shorthandFields: function(fields) {
754
            var
755
              fieldKeys = Object.keys(fields),
756
              firstRule = fields[fieldKeys[0]]
757
            ;
758
            return module.is.shorthandRules(firstRule);
759
          },
760
          // duck type rule test
761
          shorthandRules: function(rules) {
762
            return (typeof rules == 'string' || $.isArray(rules));
763
          },
764
          empty: function($field) {
765
            if(!$field || $field.length === 0) {
766
              return true;
767
            }
768
            else if($field.is('input[type="checkbox"]')) {
769
              return !$field.is(':checked');
770
            }
771
            else {
772
              return module.is.blank($field);
773
            }
774
          },
775
          blank: function($field) {
776
            return $.trim($field.val()) === '';
777
          },
778
          valid: function(field) {
779
            var
780
              allValid = true
781
            ;
782
            if(field) {
783
              module.verbose('Checking if field is valid', field);
784
              return module.validate.field(validation[field], field, false);
785
            }
786
            else {
787
              module.verbose('Checking if form is valid');
788
              $.each(validation, function(fieldName, field) {
789
                if( !module.is.valid(fieldName) ) {
790
                  allValid = false;
791
                }
792
              });
793
              return allValid;
794
            }
795
          }
796
        },
797
798
        removeEvents: function() {
799
          $module
800
            .off(eventNamespace)
801
          ;
802
          $field
803
            .off(eventNamespace)
804
          ;
805
          $submit
806
            .off(eventNamespace)
807
          ;
808
          $field
809
            .off(eventNamespace)
810
          ;
811
        },
812
813
        event: {
814
          field: {
815
            keydown: function(event) {
816
              var
817
                $field       = $(this),
818
                key          = event.which,
819
                isInput      = $field.is(selector.input),
820
                isCheckbox   = $field.is(selector.checkbox),
821
                isInDropdown = ($field.closest(selector.uiDropdown).length > 0),
822
                keyCode      = {
823
                  enter  : 13,
824
                  escape : 27
825
                }
826
              ;
827
              if( key == keyCode.escape) {
828
                module.verbose('Escape key pressed blurring field');
829
                $field
830
                  .blur()
831
                ;
832
              }
833
              if(!event.ctrlKey && key == keyCode.enter && isInput && !isInDropdown && !isCheckbox) {
834
                if(!keyHeldDown) {
835
                  $field
836
                    .one('keyup' + eventNamespace, module.event.field.keyup)
837
                  ;
838
                  module.submit();
839
                  module.debug('Enter pressed on input submitting form');
840
                }
841
                keyHeldDown = true;
842
              }
843
            },
844
            keyup: function() {
845
              keyHeldDown = false;
846
            },
847
            blur: function(event) {
848
              var
849
                $field          = $(this),
850
                $fieldGroup     = $field.closest($group),
851
                validationRules = module.get.validation($field)
852
              ;
853
              if( $fieldGroup.hasClass(className.error) ) {
854
                module.debug('Revalidating field', $field, validationRules);
855
                if(validationRules) {
856
                  module.validate.field( validationRules );
857
                }
858
              }
859
              else if(settings.on == 'blur' || settings.on == 'change') {
860
                if(validationRules) {
861
                  module.validate.field( validationRules );
862
                }
863
              }
864
            },
865
            change: function(event) {
866
              var
867
                $field      = $(this),
868
                $fieldGroup = $field.closest($group),
869
                validationRules = module.get.validation($field)
870
              ;
871
              if(validationRules && (settings.on == 'change' || ( $fieldGroup.hasClass(className.error) && settings.revalidate) )) {
872
                clearTimeout(module.timer);
873
                module.timer = setTimeout(function() {
874
                  module.debug('Revalidating field', $field,  module.get.validation($field));
875
                  module.validate.field( validationRules );
876
                }, settings.delay);
877
              }
878
            }
879
          }
880
881
        },
882
883
        get: {
884
          ancillaryValue: function(rule) {
885
            if(!rule.type || (!rule.value && !module.is.bracketedRule(rule))) {
886
              return false;
887
            }
888
            return (rule.value !== undefined)
889
              ? rule.value
890
              : rule.type.match(settings.regExp.bracket)[1] + ''
891
            ;
892
          },
893
          ruleName: function(rule) {
894
            if( module.is.bracketedRule(rule) ) {
895
              return rule.type.replace(rule.type.match(settings.regExp.bracket)[0], '');
896
            }
897
            return rule.type;
898
          },
899
          changeEvent: function(type, $input) {
900
            if(type == 'checkbox' || type == 'radio' || type == 'hidden' || $input.is('select')) {
901
              return 'change';
902
            }
903
            else {
904
              return module.get.inputEvent();
905
            }
906
          },
907
          inputEvent: function() {
908
            return (document.createElement('input').oninput !== undefined)
909
              ? 'input'
910
              : (document.createElement('input').onpropertychange !== undefined)
911
                ? 'propertychange'
912
                : 'keyup'
913
            ;
914
          },
915
          fieldsFromShorthand: function(fields) {
916
            var
917
              fullFields = {}
918
            ;
919
            $.each(fields, function(name, rules) {
920
              if(typeof rules == 'string') {
921
                rules = [rules];
922
              }
923
              fullFields[name] = {
924
                rules: []
925
              };
926
              $.each(rules, function(index, rule) {
927
                fullFields[name].rules.push({ type: rule });
928
              });
929
            });
930
            return fullFields;
931
          },
932
          prompt: function(rule, field) {
933
            var
934
              ruleName      = module.get.ruleName(rule),
935
              ancillary     = module.get.ancillaryValue(rule),
936
              prompt        = rule.prompt || settings.prompt[ruleName] || settings.text.unspecifiedRule,
937
              requiresValue = (prompt.search('{value}') !== -1),
938
              requiresName  = (prompt.search('{name}') !== -1),
939
              $label,
940
              $field,
941
              name
942
            ;
943
            if(requiresName || requiresValue) {
944
              $field = module.get.field(field.identifier);
945
            }
946
            if(requiresValue) {
947
              prompt = prompt.replace('{value}', $field.val());
948
            }
949
            if(requiresName) {
950
              $label = $field.closest(selector.group).find('label').eq(0);
951
              name = ($label.length == 1)
952
                ? $label.text()
953
                : $field.prop('placeholder') || settings.text.unspecifiedField
954
              ;
955
              prompt = prompt.replace('{name}', name);
956
            }
957
            prompt = prompt.replace('{identifier}', field.identifier);
958
            prompt = prompt.replace('{ruleValue}', ancillary);
959
            if(!rule.prompt) {
960
              module.verbose('Using default validation prompt for type', prompt, ruleName);
961
            }
962
            return prompt;
963
          },
964
          settings: function() {
965
            if($.isPlainObject(parameters)) {
966
              var
967
                keys     = Object.keys(parameters),
968
                isLegacySettings = (keys.length > 0)
969
                  ? (parameters[keys[0]].identifier !== undefined && parameters[keys[0]].rules !== undefined)
970
                  : false,
971
                ruleKeys
972
              ;
973
              if(isLegacySettings) {
974
                // 1.x (ducktyped)
975
                settings   = $.extend(true, {}, $.fn.form.settings, legacyParameters);
976
                validation = $.extend({}, $.fn.form.settings.defaults, parameters);
977
                module.error(settings.error.oldSyntax, element);
978
                module.verbose('Extending settings from legacy parameters', validation, settings);
979
              }
980
              else {
981
                // 2.x
982
                if(parameters.fields && module.is.shorthandFields(parameters.fields)) {
983
                  parameters.fields = module.get.fieldsFromShorthand(parameters.fields);
984
                }
985
                settings   = $.extend(true, {}, $.fn.form.settings, parameters);
986
                validation = $.extend({}, $.fn.form.settings.defaults, settings.fields);
987
                module.verbose('Extending settings', validation, settings);
988
              }
989
            }
990
            else {
991
              settings   = $.fn.form.settings;
992
              validation = $.fn.form.settings.defaults;
993
              module.verbose('Using default form validation', validation, settings);
994
            }
995
996
            // shorthand
997
            namespace       = settings.namespace;
998
            metadata        = settings.metadata;
999
            selector        = settings.selector;
1000
            className       = settings.className;
1001
            regExp          = settings.regExp;
1002
            error           = settings.error;
1003
            moduleNamespace = 'module-' + namespace;
1004
            eventNamespace  = '.' + namespace;
1005
1006
            // grab instance
1007
            instance = $module.data(moduleNamespace);
1008
1009
            // refresh selector cache
1010
            module.refresh();
1011
          },
1012
          field: function(identifier) {
1013
            module.verbose('Finding field with identifier', identifier);
1014
            identifier = module.escape.string(identifier);
1015
            if($field.filter('#' + identifier).length > 0 ) {
1016
              return $field.filter('#' + identifier);
1017
            }
1018
            else if( $field.filter('[name="' + identifier +'"]').length > 0 ) {
1019
              return $field.filter('[name="' + identifier +'"]');
1020
            }
1021
            else if( $field.filter('[name="' + identifier +'[]"]').length > 0 ) {
1022
              return $field.filter('[name="' + identifier +'[]"]');
1023
            }
1024
            else if( $field.filter('[data-' + metadata.validate + '="'+ identifier +'"]').length > 0 ) {
1025
              return $field.filter('[data-' + metadata.validate + '="'+ identifier +'"]');
1026
            }
1027
            return $('<input/>');
1028
          },
1029
          fields: function(fields) {
1030
            var
1031
              $fields = $()
1032
            ;
1033
            $.each(fields, function(index, name) {
1034
              $fields = $fields.add( module.get.field(name) );
1035
            });
1036
            return $fields;
1037
          },
1038
          validation: function($field) {
1039
            var
1040
              fieldValidation,
1041
              identifier
1042
            ;
1043
            if(!validation) {
1044
              return false;
1045
            }
1046
            $.each(validation, function(fieldName, field) {
1047
              identifier = field.identifier || fieldName;
1048
              if( module.get.field(identifier)[0] == $field[0] ) {
1049
                field.identifier = identifier;
1050
                fieldValidation = field;
1051
              }
1052
            });
1053
            return fieldValidation || false;
1054
          },
1055
          value: function (field) {
1056
            var
1057
              fields = [],
1058
              results
1059
            ;
1060
            fields.push(field);
1061
            results = module.get.values.call(element, fields);
1062
            return results[field];
1063
          },
1064
          values: function (fields) {
1065
            var
1066
              $fields = $.isArray(fields)
1067
                ? module.get.fields(fields)
1068
                : $field,
1069
              values = {}
1070
            ;
1071
            $fields.each(function(index, field) {
1072
              var
1073
                $field     = $(field),
1074
                type       = $field.prop('type'),
1075
                name       = $field.prop('name'),
1076
                value      = $field.val(),
1077
                isCheckbox = $field.is(selector.checkbox),
1078
                isRadio    = $field.is(selector.radio),
1079
                isMultiple = (name.indexOf('[]') !== -1),
1080
                isChecked  = (isCheckbox)
1081
                  ? $field.is(':checked')
1082
                  : false
1083
              ;
1084
              if(name) {
1085
                if(isMultiple) {
1086
                  name = name.replace('[]', '');
1087
                  if(!values[name]) {
1088
                    values[name] = [];
1089
                  }
1090
                  if(isCheckbox) {
1091
                    if(isChecked) {
1092
                      values[name].push(value || true);
1093
                    }
1094
                    else {
1095
                      values[name].push(false);
1096
                    }
1097
                  }
1098
                  else {
1099
                    values[name].push(value);
1100
                  }
1101
                }
1102
                else {
1103
                  if(isRadio) {
1104
                    if(values[name] === undefined) {
1105
                      values[name] = (isChecked)
1106
                        ? true
1107
                        : false
1108
                      ;
1109
                    }
1110
                  }
1111
                  else if(isCheckbox) {
1112
                    if(isChecked) {
1113
                      values[name] = value || true;
1114
                    }
1115
                    else {
1116
                      values[name] = false;
1117
                    }
1118
                  }
1119
                  else {
1120
                    values[name] = value;
1121
                  }
1122
                }
1123
              }
1124
            });
1125
            return values;
1126
          }
1127
        },
1128
1129
        has: {
1130
1131
          field: function(identifier) {
1132
            module.verbose('Checking for existence of a field with identifier', identifier);
1133
            identifier = module.escape.string(identifier);
1134
            if(typeof identifier !== 'string') {
1135
              module.error(error.identifier, identifier);
1136
            }
1137
            if($field.filter('#' + identifier).length > 0 ) {
1138
              return true;
1139
            }
1140
            else if( $field.filter('[name="' + identifier +'"]').length > 0 ) {
1141
              return true;
1142
            }
1143
            else if( $field.filter('[data-' + metadata.validate + '="'+ identifier +'"]').length > 0 ) {
1144
              return true;
1145
            }
1146
            return false;
1147
          }
1148
1149
        },
1150
1151
        escape: {
1152
          string: function(text) {
1153
            text =  String(text);
1154
            return text.replace(regExp.escape, '\\$&');
1155
          }
1156
        },
1157
1158
        add: {
1159
          // alias
1160
          rule: function(name, rules) {
1161
            module.add.field(name, rules);
1162
          },
1163
          field: function(name, rules) {
1164
            var
1165
              newValidation = {}
1166
            ;
1167
            if(module.is.shorthandRules(rules)) {
1168
              rules = $.isArray(rules)
1169
                ? rules
1170
                : [rules]
1171
              ;
1172
              newValidation[name] = {
1173
                rules: []
1174
              };
1175
              $.each(rules, function(index, rule) {
1176
                newValidation[name].rules.push({ type: rule });
1177
              });
1178
            }
1179
            else {
1180
              newValidation[name] = rules;
1181
            }
1182
            validation = $.extend({}, validation, newValidation);
1183
            module.debug('Adding rules', newValidation, validation);
1184
          },
1185
          fields: function(fields) {
1186
            var
1187
              newValidation
1188
            ;
1189
            if(fields && module.is.shorthandFields(fields)) {
1190
              newValidation = module.get.fieldsFromShorthand(fields);
1191
            }
1192
            else {
1193
              newValidation = fields;
1194
            }
1195
            validation = $.extend({}, validation, newValidation);
1196
          },
1197
          prompt: function(identifier, errors) {
1198
            var
1199
              $field       = module.get.field(identifier),
1200
              $fieldGroup  = $field.closest($group),
1201
              $prompt      = $fieldGroup.children(selector.prompt),
1202
              promptExists = ($prompt.length !== 0)
1203
            ;
1204
            errors = (typeof errors == 'string')
1205
              ? [errors]
1206
              : errors
1207
            ;
1208
            module.verbose('Adding field error state', identifier);
1209
            $fieldGroup
1210
              .addClass(className.error)
1211
            ;
1212
            if(settings.inline) {
1213
              if(!promptExists) {
1214
                $prompt = settings.templates.prompt(errors);
1215
                $prompt
1216
                  .appendTo($fieldGroup)
1217
                ;
1218
              }
1219
              $prompt
1220
                .html(errors[0])
1221
              ;
1222
              if(!promptExists) {
1223
                if(settings.transition && $.fn.transition !== undefined && $module.transition('is supported')) {
1224
                  module.verbose('Displaying error with css transition', settings.transition);
1225
                  $prompt.transition(settings.transition + ' in', settings.duration);
1226
                }
1227
                else {
1228
                  module.verbose('Displaying error with fallback javascript animation');
1229
                  $prompt
1230
                    .fadeIn(settings.duration)
1231
                  ;
1232
                }
1233
              }
1234
              else {
1235
                module.verbose('Inline errors are disabled, no inline error added', identifier);
1236
              }
1237
            }
1238
          },
1239
          errors: function(errors) {
1240
            module.debug('Adding form error messages', errors);
1241
            module.set.error();
1242
            $message
1243
              .html( settings.templates.error(errors) )
1244
            ;
1245
          }
1246
        },
1247
1248
        remove: {
1249
          rule: function(field, rule) {
1250
            var
1251
              rules = $.isArray(rule)
1252
                ? rule
1253
                : [rule]
1254
            ;
1255
            if(rule == undefined) {
1256
              module.debug('Removed all rules');
1257
              validation[field].rules = [];
1258
              return;
1259
            }
1260
            if(validation[field] == undefined || !$.isArray(validation[field].rules)) {
1261
              return;
1262
            }
1263
            $.each(validation[field].rules, function(index, rule) {
1264
              if(rules.indexOf(rule.type) !== -1) {
1265
                module.debug('Removed rule', rule.type);
1266
                validation[field].rules.splice(index, 1);
1267
              }
1268
            });
1269
          },
1270
          field: function(field) {
1271
            var
1272
              fields = $.isArray(field)
1273
                ? field
1274
                : [field]
1275
            ;
1276
            $.each(fields, function(index, field) {
1277
              module.remove.rule(field);
1278
            });
1279
          },
1280
          // alias
1281
          rules: function(field, rules) {
1282
            if($.isArray(field)) {
1283
              $.each(fields, function(index, field) {
1284
                module.remove.rule(field, rules);
1285
              });
1286
            }
1287
            else {
1288
              module.remove.rule(field, rules);
1289
            }
1290
          },
1291
          fields: function(fields) {
1292
            module.remove.field(fields);
1293
          },
1294
          prompt: function(identifier) {
1295
            var
1296
              $field      = module.get.field(identifier),
1297
              $fieldGroup = $field.closest($group),
1298
              $prompt     = $fieldGroup.children(selector.prompt)
1299
            ;
1300
            $fieldGroup
1301
              .removeClass(className.error)
1302
            ;
1303
            if(settings.inline && $prompt.is(':visible')) {
1304
              module.verbose('Removing prompt for field', identifier);
1305
              if(settings.transition && $.fn.transition !== undefined && $module.transition('is supported')) {
1306
                $prompt.transition(settings.transition + ' out', settings.duration, function() {
1307
                  $prompt.remove();
1308
                });
1309
              }
1310
              else {
1311
                $prompt
1312
                  .fadeOut(settings.duration, function(){
1313
                    $prompt.remove();
1314
                  })
1315
                ;
1316
              }
1317
            }
1318
          }
1319
        },
1320
1321
        set: {
1322
          success: function() {
1323
            $module
1324
              .removeClass(className.error)
1325
              .addClass(className.success)
1326
            ;
1327
          },
1328
          defaults: function () {
1329
            $field
1330
              .each(function () {
1331
                var
1332
                  $field     = $(this),
1333
                  isCheckbox = ($field.filter(selector.checkbox).length > 0),
1334
                  value      = (isCheckbox)
1335
                    ? $field.is(':checked')
1336
                    : $field.val()
1337
                ;
1338
                $field.data(metadata.defaultValue, value);
1339
              })
1340
            ;
1341
          },
1342
          error: function() {
1343
            $module
1344
              .removeClass(className.success)
1345
              .addClass(className.error)
1346
            ;
1347
          },
1348
          value: function (field, value) {
1349
            var
1350
              fields = {}
1351
            ;
1352
            fields[field] = value;
1353
            return module.set.values.call(element, fields);
1354
          },
1355
          values: function (fields) {
1356
            if($.isEmptyObject(fields)) {
1357
              return;
1358
            }
1359
            $.each(fields, function(key, value) {
1360
              var
1361
                $field      = module.get.field(key),
1362
                $element    = $field.parent(),
1363
                isMultiple  = $.isArray(value),
1364
                isCheckbox  = $element.is(selector.uiCheckbox),
1365
                isDropdown  = $element.is(selector.uiDropdown),
1366
                isRadio     = ($field.is(selector.radio) && isCheckbox),
1367
                fieldExists = ($field.length > 0),
1368
                $multipleField
1369
              ;
1370
              if(fieldExists) {
1371
                if(isMultiple && isCheckbox) {
1372
                  module.verbose('Selecting multiple', value, $field);
1373
                  $element.checkbox('uncheck');
1374
                  $.each(value, function(index, value) {
1375
                    $multipleField = $field.filter('[value="' + value + '"]');
1376
                    $element       = $multipleField.parent();
1377
                    if($multipleField.length > 0) {
1378
                      $element.checkbox('check');
1379
                    }
1380
                  });
1381
                }
1382
                else if(isRadio) {
1383
                  module.verbose('Selecting radio value', value, $field);
1384
                  $field.filter('[value="' + value + '"]')
1385
                    .parent(selector.uiCheckbox)
1386
                      .checkbox('check')
1387
                  ;
1388
                }
1389
                else if(isCheckbox) {
1390
                  module.verbose('Setting checkbox value', value, $element);
1391
                  if(value === true) {
1392
                    $element.checkbox('check');
1393
                  }
1394
                  else {
1395
                    $element.checkbox('uncheck');
1396
                  }
1397
                }
1398
                else if(isDropdown) {
1399
                  module.verbose('Setting dropdown value', value, $element);
1400
                  $element.dropdown('set selected', value);
1401
                }
1402
                else {
1403
                  module.verbose('Setting field value', value, $field);
1404
                  $field.val(value);
1405
                }
1406
              }
1407
            });
1408
          }
1409
        },
1410
1411
        validate: {
1412
1413
          form: function(event, ignoreCallbacks) {
1414
            var
1415
              values = module.get.values(),
1416
              apiRequest
1417
            ;
1418
1419
            // input keydown event will fire submit repeatedly by browser default
1420
            if(keyHeldDown) {
1421
              return false;
1422
            }
1423
1424
            // reset errors
1425
            formErrors = [];
1426
            if( module.determine.isValid() ) {
1427
              module.debug('Form has no validation errors, submitting');
1428
              module.set.success();
1429
              if(ignoreCallbacks !== true) {
1430
                return settings.onSuccess.call(element, event, values);
1431
              }
1432
            }
1433
            else {
1434
              module.debug('Form has errors');
1435
              module.set.error();
1436
              if(!settings.inline) {
1437
                module.add.errors(formErrors);
1438
              }
1439
              // prevent ajax submit
1440
              if($module.data('moduleApi') !== undefined) {
1441
                event.stopImmediatePropagation();
1442
              }
1443
              if(ignoreCallbacks !== true) {
1444
                return settings.onFailure.call(element, formErrors, values);
1445
              }
1446
            }
1447
          },
1448
1449
          // takes a validation object and returns whether field passes validation
1450
          field: function(field, fieldName, showErrors) {
1451
            showErrors = (showErrors !== undefined)
1452
              ? showErrors
1453
              : true
1454
            ;
1455
            if(typeof field == 'string') {
1456
              module.verbose('Validating field', field);
1457
              fieldName = field;
1458
              field     = validation[field];
1459
            }
1460
            var
1461
              identifier    = field.identifier || fieldName,
1462
              $field        = module.get.field(identifier),
1463
              $dependsField = (field.depends)
1464
                ? module.get.field(field.depends)
1465
                : false,
1466
              fieldValid  = true,
1467
              fieldErrors = []
1468
            ;
1469
            if(!field.identifier) {
1470
              module.debug('Using field name as identifier', identifier);
1471
              field.identifier = identifier;
1472
            }
1473
            if($field.prop('disabled')) {
1474
              module.debug('Field is disabled. Skipping', identifier);
1475
              fieldValid = true;
1476
            }
1477
            else if(field.optional && module.is.blank($field)){
1478
              module.debug('Field is optional and blank. Skipping', identifier);
1479
              fieldValid = true;
1480
            }
1481
            else if(field.depends && module.is.empty($dependsField)) {
1482
              module.debug('Field depends on another value that is not present or empty. Skipping', $dependsField);
1483
              fieldValid = true;
1484
            }
1485
            else if(field.rules !== undefined) {
1486
              $.each(field.rules, function(index, rule) {
1487
                if( module.has.field(identifier) && !( module.validate.rule(field, rule) ) ) {
1488
                  module.debug('Field is invalid', identifier, rule.type);
1489
                  fieldErrors.push(module.get.prompt(rule, field));
1490
                  fieldValid = false;
1491
                }
1492
              });
1493
            }
1494
            if(fieldValid) {
1495
              if(showErrors) {
1496
                module.remove.prompt(identifier, fieldErrors);
1497
                settings.onValid.call($field);
1498
              }
1499
            }
1500
            else {
1501
              if(showErrors) {
1502
                formErrors = formErrors.concat(fieldErrors);
1503
                module.add.prompt(identifier, fieldErrors);
1504
                settings.onInvalid.call($field, fieldErrors);
1505
              }
1506
              return false;
1507
            }
1508
            return true;
1509
          },
1510
1511
          // takes validation rule and returns whether field passes rule
1512
          rule: function(field, rule) {
1513
            var
1514
              $field       = module.get.field(field.identifier),
1515
              type         = rule.type,
1516
              value        = $field.val(),
1517
              isValid      = true,
1518
              ancillary    = module.get.ancillaryValue(rule),
1519
              ruleName     = module.get.ruleName(rule),
1520
              ruleFunction = settings.rules[ruleName]
1521
            ;
1522
            if( !$.isFunction(ruleFunction) ) {
1523
              module.error(error.noRule, ruleName);
1524
              return;
1525
            }
1526
            // cast to string avoiding encoding special values
1527
            value = (value === undefined || value === '' || value === null)
1528
              ? ''
1529
              : $.trim(value + '')
1530
            ;
1531
            return ruleFunction.call($field, value, ancillary);
1532
          }
1533
        },
1534
1535
        setting: function(name, value) {
1536
          if( $.isPlainObject(name) ) {
1537
            $.extend(true, settings, name);
1538
          }
1539
          else if(value !== undefined) {
1540
            settings[name] = value;
1541
          }
1542
          else {
1543
            return settings[name];
1544
          }
1545
        },
1546
        internal: function(name, value) {
1547
          if( $.isPlainObject(name) ) {
1548
            $.extend(true, module, name);
1549
          }
1550
          else if(value !== undefined) {
1551
            module[name] = value;
1552
          }
1553
          else {
1554
            return module[name];
1555
          }
1556
        },
1557
        debug: function() {
1558
          if(!settings.silent && settings.debug) {
1559
            if(settings.performance) {
1560
              module.performance.log(arguments);
1561
            }
1562
            else {
1563
              module.debug = Function.prototype.bind.call(console.info, console, settings.name + ':');
1564
              module.debug.apply(console, arguments);
1565
            }
1566
          }
1567
        },
1568
        verbose: function() {
1569
          if(!settings.silent && settings.verbose && settings.debug) {
1570
            if(settings.performance) {
1571
              module.performance.log(arguments);
1572
            }
1573
            else {
1574
              module.verbose = Function.prototype.bind.call(console.info, console, settings.name + ':');
1575
              module.verbose.apply(console, arguments);
1576
            }
1577
          }
1578
        },
1579
        error: function() {
1580
          if(!settings.silent) {
1581
            module.error = Function.prototype.bind.call(console.error, console, settings.name + ':');
1582
            module.error.apply(console, arguments);
1583
          }
1584
        },
1585
        performance: {
1586
          log: function(message) {
1587
            var
1588
              currentTime,
1589
              executionTime,
1590
              previousTime
1591
            ;
1592
            if(settings.performance) {
1593
              currentTime   = new Date().getTime();
1594
              previousTime  = time || currentTime;
1595
              executionTime = currentTime - previousTime;
1596
              time          = currentTime;
1597
              performance.push({
1598
                'Name'           : message[0],
1599
                'Arguments'      : [].slice.call(message, 1) || '',
1600
                'Element'        : element,
1601
                'Execution Time' : executionTime
1602
              });
1603
            }
1604
            clearTimeout(module.performance.timer);
1605
            module.performance.timer = setTimeout(module.performance.display, 500);
1606
          },
1607
          display: function() {
1608
            var
1609
              title = settings.name + ':',
1610
              totalTime = 0
1611
            ;
1612
            time = false;
1613
            clearTimeout(module.performance.timer);
1614
            $.each(performance, function(index, data) {
1615
              totalTime += data['Execution Time'];
1616
            });
1617
            title += ' ' + totalTime + 'ms';
1618
            if(moduleSelector) {
1619
              title += ' \'' + moduleSelector + '\'';
1620
            }
1621
            if($allModules.length > 1) {
1622
              title += ' ' + '(' + $allModules.length + ')';
1623
            }
1624
            if( (console.group !== undefined || console.table !== undefined) && performance.length > 0) {
1625
              console.groupCollapsed(title);
1626
              if(console.table) {
1627
                console.table(performance);
1628
              }
1629
              else {
1630
                $.each(performance, function(index, data) {
1631
                  console.log(data['Name'] + ': ' + data['Execution Time']+'ms');
1632
                });
1633
              }
1634
              console.groupEnd();
1635
            }
1636
            performance = [];
1637
          }
1638
        },
1639
        invoke: function(query, passedArguments, context) {
1640
          var
1641
            object = instance,
1642
            maxDepth,
1643
            found,
1644
            response
1645
          ;
1646
          passedArguments = passedArguments || queryArguments;
1647
          context         = element         || context;
1648
          if(typeof query == 'string' && object !== undefined) {
1649
            query    = query.split(/[\. ]/);
1650
            maxDepth = query.length - 1;
1651
            $.each(query, function(depth, value) {
1652
              var camelCaseValue = (depth != maxDepth)
1653
                ? value + query[depth + 1].charAt(0).toUpperCase() + query[depth + 1].slice(1)
1654
                : query
1655
              ;
1656
              if( $.isPlainObject( object[camelCaseValue] ) && (depth != maxDepth) ) {
1657
                object = object[camelCaseValue];
1658
              }
1659
              else if( object[camelCaseValue] !== undefined ) {
1660
                found = object[camelCaseValue];
1661
                return false;
1662
              }
1663
              else if( $.isPlainObject( object[value] ) && (depth != maxDepth) ) {
1664
                object = object[value];
1665
              }
1666
              else if( object[value] !== undefined ) {
1667
                found = object[value];
1668
                return false;
1669
              }
1670
              else {
1671
                return false;
1672
              }
1673
            });
1674
          }
1675
          if( $.isFunction( found ) ) {
1676
            response = found.apply(context, passedArguments);
1677
          }
1678
          else if(found !== undefined) {
1679
            response = found;
1680
          }
1681
          if($.isArray(returnedValue)) {
1682
            returnedValue.push(response);
1683
          }
1684
          else if(returnedValue !== undefined) {
1685
            returnedValue = [returnedValue, response];
1686
          }
1687
          else if(response !== undefined) {
1688
            returnedValue = response;
1689
          }
1690
          return found;
1691
        }
1692
      };
1693
      module.initialize();
1694
    })
1695
  ;
1696
1697
  return (returnedValue !== undefined)
1698
    ? returnedValue
1699
    : this
1700
  ;
1701
};
1702
1703
$.fn.form.settings = {
1704
1705
  name              : 'Form',
1706
  namespace         : 'form',
1707
1708
  debug             : false,
1709
  verbose           : false,
1710
  performance       : true,
1711
1712
  fields            : false,
1713
1714
  keyboardShortcuts : true,
1715
  on                : 'submit',
1716
  inline            : false,
1717
1718
  delay             : 200,
1719
  revalidate        : true,
1720
1721
  transition        : 'scale',
1722
  duration          : 200,
1723
1724
  onValid           : function() {},
1725
  onInvalid         : function() {},
1726
  onSuccess         : function() { return true; },
1727
  onFailure         : function() { return false; },
1728
1729
  metadata : {
1730
    defaultValue : 'default',
1731
    validate     : 'validate'
1732
  },
1733
1734
  regExp: {
1735
    htmlID  : /^[a-zA-Z][\w:.-]*$/g,
1736
    bracket : /\[(.*)\]/i,
1737
    decimal : /^\d+\.?\d*$/,
1738
    email   : /^[a-z0-9!#$%&'*+\/=?^_`{|}~.-]+@[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/i,
1739
    escape  : /[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g,
1740
    flags   : /^\/(.*)\/(.*)?/,
1741
    integer : /^\-?\d+$/,
1742
    number  : /^\-?\d*(\.\d+)?$/,
1743
    url     : /(https?:\/\/(?:www\.|(?!www))[^\s\.]+\.[^\s]{2,}|www\.[^\s]+\.[^\s]{2,})/i
1744
  },
1745
1746
  text: {
1747
    unspecifiedRule  : 'Please enter a valid value',
1748
    unspecifiedField : 'This field'
1749
  },
1750
1751
  prompt: {
1752
    empty                : '{name} must have a value',
1753
    checked              : '{name} must be checked',
1754
    email                : '{name} must be a valid e-mail',
1755
    url                  : '{name} must be a valid url',
1756
    regExp               : '{name} is not formatted correctly',
1757
    integer              : '{name} must be an integer',
1758
    decimal              : '{name} must be a decimal number',
1759
    number               : '{name} must be set to a number',
1760
    is                   : '{name} must be "{ruleValue}"',
1761
    isExactly            : '{name} must be exactly "{ruleValue}"',
1762
    not                  : '{name} cannot be set to "{ruleValue}"',
1763
    notExactly           : '{name} cannot be set to exactly "{ruleValue}"',
1764
    contain              : '{name} cannot contain "{ruleValue}"',
1765
    containExactly       : '{name} cannot contain exactly "{ruleValue}"',
1766
    doesntContain        : '{name} must contain  "{ruleValue}"',
1767
    doesntContainExactly : '{name} must contain exactly "{ruleValue}"',
1768
    minLength            : '{name} must be at least {ruleValue} characters',
1769
    length               : '{name} must be at least {ruleValue} characters',
1770
    exactLength          : '{name} must be exactly {ruleValue} characters',
1771
    maxLength            : '{name} cannot be longer than {ruleValue} characters',
1772
    match                : '{name} must match {ruleValue} field',
1773
    different            : '{name} must have a different value than {ruleValue} field',
1774
    creditCard           : '{name} must be a valid credit card number',
1775
    minCount             : '{name} must have at least {ruleValue} choices',
1776
    exactCount           : '{name} must have exactly {ruleValue} choices',
1777
    maxCount             : '{name} must have {ruleValue} or less choices'
1778
  },
1779
1780
  selector : {
1781
    checkbox   : 'input[type="checkbox"], input[type="radio"]',
1782
    clear      : '.clear',
1783
    field      : 'input, textarea, select',
1784
    group      : '.field',
1785
    input      : 'input',
1786
    message    : '.error.message',
1787
    prompt     : '.prompt.label',
1788
    radio      : 'input[type="radio"]',
1789
    reset      : '.reset:not([type="reset"])',
1790
    submit     : '.submit:not([type="submit"])',
1791
    uiCheckbox : '.ui.checkbox',
1792
    uiDropdown : '.ui.dropdown'
1793
  },
1794
1795
  className : {
1796
    error   : 'error',
1797
    label   : 'ui prompt label',
1798
    pressed : 'down',
1799
    success : 'success'
1800
  },
1801
1802
  error: {
1803
    identifier : 'You must specify a string identifier for each field',
1804
    method     : 'The method you called is not defined.',
1805
    noRule     : 'There is no rule matching the one you specified',
1806
    oldSyntax  : 'Starting in 2.0 forms now only take a single settings object. Validation settings converted to new syntax automatically.'
1807
  },
1808
1809
  templates: {
1810
1811
    // template that produces error message
1812
    error: function(errors) {
1813
      var
1814
        html = '<ul class="list">'
1815
      ;
1816
      $.each(errors, function(index, value) {
1817
        html += '<li>' + value + '</li>';
1818
      });
1819
      html += '</ul>';
1820
      return $(html);
1821
    },
1822
1823
    // template that produces label
1824
    prompt: function(errors) {
1825
      return $('<div/>')
1826
        .addClass('ui basic red pointing prompt label')
1827
        .html(errors[0])
1828
      ;
1829
    }
1830
  },
1831
1832
  rules: {
1833
1834
    // is not empty or blank string
1835
    empty: function(value) {
1836
      return !(value === undefined || '' === value || $.isArray(value) && value.length === 0);
1837
    },
1838
1839
    // checkbox checked
1840
    checked: function() {
1841
      return ($(this).filter(':checked').length > 0);
1842
    },
1843
1844
    // is most likely an email
1845
    email: function(value){
1846
      return $.fn.form.settings.regExp.email.test(value);
1847
    },
1848
1849
    // value is most likely url
1850
    url: function(value) {
1851
      return $.fn.form.settings.regExp.url.test(value);
1852
    },
1853
1854
    // matches specified regExp
1855
    regExp: function(value, regExp) {
1856
      if(regExp instanceof RegExp) {
1857
        return value.match(regExp);
1858
      }
1859
      var
1860
        regExpParts = regExp.match($.fn.form.settings.regExp.flags),
1861
        flags
1862
      ;
1863
      // regular expression specified as /baz/gi (flags)
1864
      if(regExpParts) {
1865
        regExp = (regExpParts.length >= 2)
1866
          ? regExpParts[1]
1867
          : regExp
1868
        ;
1869
        flags = (regExpParts.length >= 3)
1870
          ? regExpParts[2]
1871
          : ''
1872
        ;
1873
      }
1874
      return value.match( new RegExp(regExp, flags) );
1875
    },
1876
1877
    // is valid integer or matches range
1878
    integer: function(value, range) {
1879
      var
1880
        intRegExp = $.fn.form.settings.regExp.integer,
1881
        min,
1882
        max,
1883
        parts
1884
      ;
1885
      if( !range || ['', '..'].indexOf(range) !== -1) {
1886
        // do nothing
1887
      }
1888
      else if(range.indexOf('..') == -1) {
1889
        if(intRegExp.test(range)) {
1890
          min = max = range - 0;
1891
        }
1892
      }
1893
      else {
1894
        parts = range.split('..', 2);
1895
        if(intRegExp.test(parts[0])) {
1896
          min = parts[0] - 0;
1897
        }
1898
        if(intRegExp.test(parts[1])) {
1899
          max = parts[1] - 0;
1900
        }
1901
      }
1902
      return (
1903
        intRegExp.test(value) &&
1904
        (min === undefined || value >= min) &&
1905
        (max === undefined || value <= max)
1906
      );
1907
    },
1908
1909
    // is valid number (with decimal)
1910
    decimal: function(value) {
1911
      return $.fn.form.settings.regExp.decimal.test(value);
1912
    },
1913
1914
    // is valid number
1915
    number: function(value) {
1916
      return $.fn.form.settings.regExp.number.test(value);
1917
    },
1918
1919
    // is value (case insensitive)
1920
    is: function(value, text) {
1921
      text = (typeof text == 'string')
1922
        ? text.toLowerCase()
1923
        : text
1924
      ;
1925
      value = (typeof value == 'string')
1926
        ? value.toLowerCase()
1927
        : value
1928
      ;
1929
      return (value == text);
1930
    },
1931
1932
    // is value
1933
    isExactly: function(value, text) {
1934
      return (value == text);
1935
    },
1936
1937
    // value is not another value (case insensitive)
1938
    not: function(value, notValue) {
1939
      value = (typeof value == 'string')
1940
        ? value.toLowerCase()
1941
        : value
1942
      ;
1943
      notValue = (typeof notValue == 'string')
1944
        ? notValue.toLowerCase()
1945
        : notValue
1946
      ;
1947
      return (value != notValue);
1948
    },
1949
1950
    // value is not another value (case sensitive)
1951
    notExactly: function(value, notValue) {
1952
      return (value != notValue);
1953
    },
1954
1955
    // value contains text (insensitive)
1956
    contains: function(value, text) {
1957
      // escape regex characters
1958
      text = text.replace($.fn.form.settings.regExp.escape, "\\$&");
1959
      return (value.search( new RegExp(text, 'i') ) !== -1);
1960
    },
1961
1962
    // value contains text (case sensitive)
1963
    containsExactly: function(value, text) {
1964
      // escape regex characters
1965
      text = text.replace($.fn.form.settings.regExp.escape, "\\$&");
1966
      return (value.search( new RegExp(text) ) !== -1);
1967
    },
1968
1969
    // value contains text (insensitive)
1970
    doesntContain: function(value, text) {
1971
      // escape regex characters
1972
      text = text.replace($.fn.form.settings.regExp.escape, "\\$&");
1973
      return (value.search( new RegExp(text, 'i') ) === -1);
1974
    },
1975
1976
    // value contains text (case sensitive)
1977
    doesntContainExactly: function(value, text) {
1978
      // escape regex characters
1979
      text = text.replace($.fn.form.settings.regExp.escape, "\\$&");
1980
      return (value.search( new RegExp(text) ) === -1);
1981
    },
1982
1983
    // is at least string length
1984
    minLength: function(value, requiredLength) {
1985
      return (value !== undefined)
1986
        ? (value.length >= requiredLength)
1987
        : false
1988
      ;
1989
    },
1990
1991
    // see rls notes for 2.0.6 (this is a duplicate of minLength)
1992
    length: function(value, requiredLength) {
1993
      return (value !== undefined)
1994
        ? (value.length >= requiredLength)
1995
        : false
1996
      ;
1997
    },
1998
1999
    // is exactly length
2000
    exactLength: function(value, requiredLength) {
2001
      return (value !== undefined)
2002
        ? (value.length == requiredLength)
2003
        : false
2004
      ;
2005
    },
2006
2007
    // is less than length
2008
    maxLength: function(value, maxLength) {
2009
      return (value !== undefined)
2010
        ? (value.length <= maxLength)
2011
        : false
2012
      ;
2013
    },
2014
2015
    // matches another field
2016
    match: function(value, identifier) {
2017
      var
2018
        $form = $(this),
2019
        matchingValue
2020
      ;
2021
      if( $('[data-validate="'+ identifier +'"]').length > 0 ) {
2022
        matchingValue = $('[data-validate="'+ identifier +'"]').val();
2023
      }
2024
      else if($('#' + identifier).length > 0) {
2025
        matchingValue = $('#' + identifier).val();
2026
      }
2027
      else if($('[name="' + identifier +'"]').length > 0) {
2028
        matchingValue = $('[name="' + identifier + '"]').val();
2029
      }
2030
      else if( $('[name="' + identifier +'[]"]').length > 0 ) {
2031
        matchingValue = $('[name="' + identifier +'[]"]');
2032
      }
2033
      return (matchingValue !== undefined)
2034
        ? ( value.toString() == matchingValue.toString() )
2035
        : false
2036
      ;
2037
    },
2038
2039
    // different than another field
2040
    different: function(value, identifier) {
2041
      // use either id or name of field
2042
      var
2043
        $form = $(this),
2044
        matchingValue
2045
      ;
2046
      if( $('[data-validate="'+ identifier +'"]').length > 0 ) {
2047
        matchingValue = $('[data-validate="'+ identifier +'"]').val();
2048
      }
2049
      else if($('#' + identifier).length > 0) {
2050
        matchingValue = $('#' + identifier).val();
2051
      }
2052
      else if($('[name="' + identifier +'"]').length > 0) {
2053
        matchingValue = $('[name="' + identifier + '"]').val();
2054
      }
2055
      else if( $('[name="' + identifier +'[]"]').length > 0 ) {
2056
        matchingValue = $('[name="' + identifier +'[]"]');
2057
      }
2058
      return (matchingValue !== undefined)
2059
        ? ( value.toString() !== matchingValue.toString() )
2060
        : false
2061
      ;
2062
    },
2063
2064
    creditCard: function(cardNumber, cardTypes) {
2065
      var
2066
        cards = {
2067
          visa: {
2068
            pattern : /^4/,
2069
            length  : [16]
2070
          },
2071
          amex: {
2072
            pattern : /^3[47]/,
2073
            length  : [15]
2074
          },
2075
          mastercard: {
2076
            pattern : /^5[1-5]/,
2077
            length  : [16]
2078
          },
2079
          discover: {
2080
            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)/,
2081
            length  : [16]
2082
          },
2083
          unionPay: {
2084
            pattern : /^(62|88)/,
2085
            length  : [16, 17, 18, 19]
2086
          },
2087
          jcb: {
2088
            pattern : /^35(2[89]|[3-8][0-9])/,
2089
            length  : [16]
2090
          },
2091
          maestro: {
2092
            pattern : /^(5018|5020|5038|6304|6759|676[1-3])/,
2093
            length  : [12, 13, 14, 15, 16, 17, 18, 19]
2094
          },
2095
          dinersClub: {
2096
            pattern : /^(30[0-5]|^36)/,
2097
            length  : [14]
2098
          },
2099
          laser: {
2100
            pattern : /^(6304|670[69]|6771)/,
2101
            length  : [16, 17, 18, 19]
2102
          },
2103
          visaElectron: {
2104
            pattern : /^(4026|417500|4508|4844|491(3|7))/,
2105
            length  : [16]
2106
          }
2107
        },
2108
        valid         = {},
2109
        validCard     = false,
2110
        requiredTypes = (typeof cardTypes == 'string')
2111
          ? cardTypes.split(',')
2112
          : false,
2113
        unionPay,
2114
        validation
2115
      ;
2116
2117
      if(typeof cardNumber !== 'string' || cardNumber.length === 0) {
2118
        return;
2119
      }
2120
2121
      // allow dashes in card
2122
      cardNumber = cardNumber.replace(/[\-]/g, '');
2123
2124
      // verify card types
2125
      if(requiredTypes) {
2126
        $.each(requiredTypes, function(index, type){
2127
          // verify each card type
2128
          validation = cards[type];
2129
          if(validation) {
2130
            valid = {
2131
              length  : ($.inArray(cardNumber.length, validation.length) !== -1),
2132
              pattern : (cardNumber.search(validation.pattern) !== -1)
2133
            };
2134
            if(valid.length && valid.pattern) {
2135
              validCard = true;
2136
            }
2137
          }
2138
        });
2139
2140
        if(!validCard) {
2141
          return false;
2142
        }
2143
      }
2144
2145
      // skip luhn for UnionPay
2146
      unionPay = {
2147
        number  : ($.inArray(cardNumber.length, cards.unionPay.length) !== -1),
2148
        pattern : (cardNumber.search(cards.unionPay.pattern) !== -1)
2149
      };
2150
      if(unionPay.number && unionPay.pattern) {
2151
        return true;
2152
      }
2153
2154
      // verify luhn, adapted from  <https://gist.github.com/2134376>
2155
      var
2156
        length        = cardNumber.length,
2157
        multiple      = 0,
2158
        producedValue = [
2159
          [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
2160
          [0, 2, 4, 6, 8, 1, 3, 5, 7, 9]
2161
        ],
2162
        sum           = 0
2163
      ;
2164
      while (length--) {
2165
        sum += producedValue[multiple][parseInt(cardNumber.charAt(length), 10)];
2166
        multiple ^= 1;
2167
      }
2168
      return (sum % 10 === 0 && sum > 0);
2169
    },
2170
2171
    minCount: function(value, minCount) {
2172
      if(minCount == 0) {
2173
        return true;
2174
      }
2175
      if(minCount == 1) {
2176
        return (value !== '');
2177
      }
2178
      return (value.split(',').length >= minCount);
2179
    },
2180
2181
    exactCount: function(value, exactCount) {
2182
      if(exactCount == 0) {
2183
        return (value === '');
2184
      }
2185
      if(exactCount == 1) {
2186
        return (value !== '' && value.search(',') === -1);
2187
      }
2188
      return (value.split(',').length == exactCount);
2189
    },
2190
2191
    maxCount: function(value, maxCount) {
2192
      if(maxCount == 0) {
2193
        return false;
2194
      }
2195
      if(maxCount == 1) {
2196
        return (value.search(',') === -1);
2197
      }
2198
      return (value.split(',').length <= maxCount);
2199
    }
2200
  }
2201
2202
};
2203
2204
})( jQuery, window, document );
2205
2206
/*!
2207
 * # Semantic UI 2.2.11 - Accordion

public/lib/semantic/components/form.js 1 location

@@ 11-1706 (lines=1696) @@
8
 *
9
 */
10
11
;(function ($, window, document, undefined) {
12
13
"use strict";
14
15
window = (typeof window != 'undefined' && window.Math == Math)
16
  ? window
17
  : (typeof self != 'undefined' && self.Math == Math)
18
    ? self
19
    : Function('return this')()
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);
116
117
          $submit     = $module.find(selector.submit);
118
          $clear      = $module.find(selector.clear);
119
          $reset      = $module.find(selector.reset);
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 {
289
              module.verbose('Checking if form is valid');
290
              $.each(validation, function(fieldName, field) {
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) {
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) {
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 {
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());
450
            }
451
            if(requiresName) {
452
              $label = $field.closest(selector.group).find('label').eq(0);
453
              name = ($label.length == 1)
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
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'),
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) {
758
              module.debug('Removed all rules');
759
              validation[field].rules = [];
760
              return;
761
            }
762
            if(validation[field] == undefined || !$.isArray(validation[field].rules)) {
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) {
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
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) {
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) {
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,
1018
              value        = $field.val(),
1019
              isValid      = true,
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;
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);
1040
          }
1041
          else if(value !== undefined) {
1042
            settings[name] = value;
1043
          }
1044
          else {
1045
            return settings[name];
1046
          }
1047
        },
1048
        internal: function(name, value) {
1049
          if( $.isPlainObject(name) ) {
1050
            $.extend(true, module, name);
1051
          }
1052
          else if(value !== undefined) {
1053
            module[name] = value;
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');
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];
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];
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);
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) );
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) {
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) &&
1407
        (max === undefined || value <= max)
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),
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)
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),
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)
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         = {},
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;
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) {
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) {
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) {
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