Skip to content

Allow validators to only warn the user #504

@michaelzangl

Description

@michaelzangl

I have several validation rules that should only check if something is likely and warn the user if they fail. The user should still be able to save with those errors.

I solved this by adding a validator that only generates warnings.

$.formUtils.addValidator({
    name: 'warn_custom',
    validatorFunction: function (val, $el) {
        var regexp = new RegExp($el.valAttr('warn-regexp'));
        return regexp.test(val);
    },
    errorMessage: '',
    errorMessageKey: 'warnBadCustomVal',
    warnOnly : true,
});

I then added the ability to store the warnings to the form. It works fine although the code is somewhat hacked.

--- a/public/js/form-validator/jquery.form-validator.js
+++ b/public/js/form-validator/jquery.form-validator.js
@@ -295,16 +295,33 @@
     applyInputErrorStyling: function ($input, conf) {
       $input
         .addClass(conf.errorElementClass)
+        .removeClass(conf.warningElementClass)
         .removeClass('valid');

       this.getParentContainer($input)
         .addClass(conf.inputParentClassOnError)
+        .removeClass(conf.inputParentClassOnWarning)
         .removeClass(conf.inputParentClassOnSuccess);

       if (conf.borderColorOnError !== '') {
         $input.css('border-color', conf.borderColorOnError);
       }
     },
+    applyInputWarningStyling: function ($input, conf) {
+        $input
+          .removeClass(conf.errorElementClass)
+          .addClass(conf.warningElementClass)
+          .removeClass('valid');
+
+        this.getParentContainer($input)
+          .removeClass(conf.inputParentClassOnError)
+          .addClass(conf.inputParentClassOnWarning)
+          .removeClass(conf.inputParentClassOnSuccess);
+
+        if (conf.borderColorOnError !== '') {
+          $input.css('border-color', conf.borderColorOnError);
+        }
+      },
     applyInputSuccessStyling: function($input, conf) {
       $input.addClass('valid');
       this.getParentContainer($input)
@@ -316,6 +333,7 @@
       $input
         .removeClass('valid')
         .removeClass(conf.errorElementClass)
+        .removeClass(conf.warningElementClass)
         .css('border-color', '');

       var $parentContainer = dialogs.getParentContainer($input);
@@ -323,6 +341,7 @@
       // Reset parent css
       $parentContainer
         .removeClass(conf.inputParentClassOnError)
+        .removeClass(conf.inputParentClassOnWarning)
         .removeClass(conf.inputParentClassOnSuccess);

       // Remove possible error message
@@ -333,7 +352,7 @@
         }
       } else {
         $parentContainer
-          .find('.' + conf.errorMessageClass)
+          .find('.' + conf.errorMessageClass + ', .' + conf.warningMessageClass)
           .remove();
       }

@@ -347,7 +366,7 @@
           $errorMessagesInTopOfForm.html('');
         }
       } else {
-        $form.find('.' + conf.errorMessageClass + '.alert').remove();
+        $form.find('.' + conf.errorMessageClass + '.alert, .' + conf.warningMessageClass + '.alert').remove();
       }

       // Remove input css/messages
@@ -355,19 +374,24 @@
         dialogs.removeInputStylingAndMessage($(this), conf);
       });
     },
-    setInlineMessage: function ($input, errorMsg, conf) {
-
-      this.applyInputErrorStyling($input, conf);
+    setInlineMessage: function ($input, errorMsg, conf, warnOnly) {
+       if (warnOnly) {
+           this.applyInputWarningStyling($input, conf);
+       } else {
+           this.applyInputErrorStyling($input, conf);
+       }

       var custom = document.getElementById($input.attr('name') + '_err_msg'),
         $messageContainer = false,
         setErrorMessage = function ($elem) {
           $.formUtils.$win.trigger('validationErrorDisplay', [$input, $elem]);
+          $message.toggleClass(conf.errorMessageClass, !warnOnly);
+          $message.toggleClass(conf.warningMessageClass, warnOnly);
           $elem.html(errorMsg);
         },
         addErrorToMessageContainer = function() {
           var $found = false;
-          $messageContainer.find('.' + conf.errorMessageClass).each(function () {
+          $messageContainer.find('.' + conf.errorMessageClass + ', .' + conf.warningMessageClass).each(function () {
             if (this.inputReferer === $input[0]) {
               $found = $(this);
               return false;
@@ -380,7 +404,7 @@
               setErrorMessage($found);
             }
           } else if(errorMsg !== '') {
-            $message = $('<div class="' + conf.errorMessageClass + ' alert"></div>');
+            $message = $('<div class="' +  + ' alert"></div>');
             setErrorMessage($message);
             $message[0].inputReferer = $input[0];
             $messageContainer.prepend($message);
@@ -402,7 +426,7 @@
         addErrorToMessageContainer();
       } else {
         var $parent = this.getParentContainer($input);
-        $message = $parent.find('.' + conf.errorMessageClass + '.help-block');
+        $message = $parent.find('.' + conf.errorMessageClass + '.help-block, .' + conf.warningMessageClass + '.help-block');
         if ($message.length === 0) {
           $message = $('<span></span>').addClass('help-block').addClass(conf.errorMessageClass);
           $message.appendTo($parent);
@@ -681,10 +705,12 @@
     }

     if (result.shouldChangeDisplay) {
-      if (result.isValid) {
-        $.formUtils.dialogs.applyInputSuccessStyling($elem, conf);
+      if (!result.isValid) {
+          $.formUtils.dialogs.setInlineMessage($elem, result.errorMsg, conf, false);
+      } else if (!result.hasNoWarning) {
+          $.formUtils.dialogs.setInlineMessage($elem, result.warningMsg, conf, true);
       } else {
-        $.formUtils.dialogs.setInlineMessage($elem, result.errorMsg, conf);
+          $.formUtils.dialogs.applyInputSuccessStyling($elem, conf);
       }
     }

@@ -879,7 +905,7 @@
           $.formUtils.dialogs.setMessageInTopOfForm($form, errorMessages, conf, language);
         } else {
           $.each(errorInputs, function (i, $input) {
-            $.formUtils.dialogs.setInlineMessage($input, $input.attr('current-error'), conf);
+            $.formUtils.dialogs.setInlineMessage($input, $input.attr('current-error'), conf, false);
           });
         }
         if (conf.scrollToTopOnError) {
@@ -1269,6 +1295,7 @@
         errorElementClass: 'error', // Class that will be put on elements which value is invalid
         borderColorOnError: '#b94a48', // Border color of elements which value is invalid, empty string to not change border color
         errorMessageClass: 'form-error', // class name of div containing error messages when validation fails
+        warningMessageClass: 'form-warning', // class name of div containing error messages when validation fails
         validationRuleAttribute: 'data-validation', // name of the attribute holding the validation rules
         validationErrorMsgAttribute: 'data-validation-error-msg', // define custom err msg inline with element
         errorMessagePosition: 'inline', // Can be either "top" or "inline"
@@ -1277,11 +1304,17 @@
           messages: '<strong>{errorTitle}</strong><ul>{fields}</ul>',
           field: '<li>{msg}</li>'
         },
+        warningMessageTemplate: {
+            container: '<div class="{warningMessageClass} alert alert-warning">{messages}</div>',
+            messages: '<strong>{errorTitle}</strong><ul>{fields}</ul>',
+            field: '<li>{msg}</li>'
+          },
         scrollToTopOnError: true,
         dateFormat: 'yyyy-mm-dd',
         addValidClassOnAll: false, // whether or not to apply class="valid" even if the input wasn't validated
         decimalSeparator: '.',
         inputParentClassOnError: 'has-error', // twitter-bootstrap default class name
+        inputParentClassOnWarning: 'has-warning', // twitter-bootstrap default class name
         inputParentClassOnSuccess: 'has-success', // twitter-bootstrap default class name
         validateHiddenInputs: false, // whether or not hidden inputs should be validated
         inlineErrorMessageCallback: false,
@@ -1399,8 +1432,10 @@
           skipBecauseItsEmpty = !value && inputIsOptional,
           validationRules = $elem.attr(conf.validationRuleAttribute),
           isValid = true,
+          hasNoWarning = true,
           errorMsg = '',
-          result = {isValid: true, shouldChangeDisplay:true, errorMsg:''};
+          warningMsg = '',
+          result = {isValid: true, hasNoWarning: true, shouldChangeDisplay:true, errorMsg:'', warningMsg:''};

       // For input type="number", browsers attempt to parse the entered value into a number.
       // If the input is not numeric, browsers handle the situation differently:
@@ -1439,17 +1474,31 @@
             $elem = $form.find('[name="' + $elem.attr('name') + '"]:eq(0)');
           }

+          var valid = false;
           if (eventContext !== 'keyup' || validator.validateOnKeyUp) {
             // A validator can prevent itself from getting triggered on keyup
-            isValid = validator.validatorFunction(value, $elem, conf, language, $form, eventContext);
+            valid = validator.validatorFunction(value, $elem, conf, language, $form, eventContext);
+            if (validator.warnOnly) {
+               hasNoWarning = hasNoWarning && valid;
+            } else {
+               isValid = isValid && valid;
+            }
           }

-          if (!isValid) {
+          if (!valid) {
             if (conf.validateOnBlur) {
               $elem.validateOnKeyUp(language, conf);
             }
-            errorMsg = $.formUtils.dialogs.resolveErrorMessage($elem, validator, rule, conf, language);
-            return false; // break iteration
+            var msg = $.formUtils.dialogs.resolveErrorMessage($elem, validator, rule, conf, language);
+            
+            if (validator.warnOnly) {
+               if (!warningMsg) {
+                   warningMsg = msg;
+               }
+            } else {
+               errorMsg = msg;
+                return false; // break iteration
+            }
           }

         } else {
@@ -1473,7 +1522,12 @@
         // the input at this time. Most probably some async stuff need to gets finished
         // first and then the validator will re-trigger the validation.
         result.shouldChangeDisplay = false;
-      } else {
+      } else if (hasNoWarning === false) {
+          $elem.trigger('validation', true);
+          result.warningMsg = warningMsg;
+          result.hasNoWarning = false;
+          result.shouldChangeDisplay = true;
+        } else {
         $elem.trigger('validation', true);
         result.shouldChangeDisplay = true;
       }

It would be great if a feature like this could be included in the next release

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions