Conflict between jQuery Validate and Masked Input

2019-01-17 03:37发布

I've got a form that's using both jQuery Validate and Masked Input for phone number and US zip code fields.

For example, for a US zip code, Masked Input allows only numbers to be entered, and forces either the format "99999" or "99999-9999" (where 9 can be any number). The Validate rule requires the same. But in some cases, Validate marks a field as invalid when it should actually be valid.

Code Specifics

The regex that jQuery Validate is using on the zip code field is ^\d{5}$|^\d{5}\-\d{4}$.

The mask I'm applying with Masked Input is .mask('99999?-9999')

Steps to reproduce

A conflict between them is produced when I do the following:

  • Fill out an invalid zip code (say, three digits)
  • Tab away from the field. Masked Input erases the input (expected behavior) and jQuery Validate marks it as invalid because it's blank (also expected).
  • Go back to the zip field and fill in a valid 5-digit zip.
  • Tab away from the field. It is still marked as invalid. This is unexpected.

This problem does not happen if I fill out a 9-digit zip.

Hypothesis

I think this error is because in the case of the 5-digit zip, Masked Input has temporarily inserted "-____" to show the user that they can optionally enter a dash and four more digits. This is removed on blur, but before it's removed, the field is validated and fails, since underscores aren't permitted.

This hypothesis is supported by the fact that, if the form is subsequently re-validated, the zip field will pass. I have done this two ways:

  • By submitting the form; all fields are re-validated when the submit button is clicked, the zip field passes, and the form submits.
  • By setting up a blur event that re-validates that specific field. For example:

    $("#zipcode").blur(function(){ $(this).closest('form').validate().element($(this)); });

This can serve as a hacky solution, but isn't very satisfactory, because 1) the default settings already re-validate on blur, so this is repetitive, and 2) it requires additional code besides the normal Validate rules.

Has anybody else run into this issue? Do you have a more elegant solution than setting up an additional blur event listener?

Update: applying the hacky solution

Even applying the hacky solution above doesn't work as nicely as I'd like. For instance, this doesn't work:

$appDiv.delegate('input,select,textarea','blur',function(){
  $(this).closest('form').validate().element($(this));
});

...nor does this:

$('input,select,textarea').live('blur',function(){
  $(this).closest('form').validate().element($(this));
});

...but this does:

$('input,select,textarea').each(function(){
  $(this).blur(function(){
    $(this).closest('form').validate().element($(this));
  });
});

Since these elements are loaded by AJAX, the .each version has to be run each time a form section is loaded.

6条回答
男人必须洒脱
2楼-- · 2019-01-17 04:02

I was actually looking for an answer to this exact question. Ended up figuring out a more reliable workaround.

Since I am defining my own validator method for the zipcode I modified it so that it would remove the hyphen if the length of the zipcode was 6 after I removed the placeholder from the value.

It looked like this:

$.validator.addMethod("zipcode", function(postalcode, element) {
            //removes placeholder from string
            postalcode = postalcode.split("_").join("");

            //Checks the length of the zipcode now that placeholder characters are removed.
            if (postalcode.length === 6) {
                //Removes hyphen
                postalcode = postalcode.replace("-", "");
            }
            //validates postalcode.
            return this.optional(element) || postalcode.match(/^\d{5}$|^\d{5}\-\d{4}$/);
        }, "Please specify a valid zip code");

So the postalcode I am validating will have the placeholders added by the input plugin and the hyphen removed if the zipcode that is entered only has 5 numerical digits (6 including the hyphen). So it will validate it properly.

查看更多
Explosion°爆炸
3楼-- · 2019-01-17 04:03

Does it matter the order that you do the binding? For example, is there a difference between

$(function() {
  var $form = $("#myForm"), $zip = $("#zip");
  $zip.mask("99999?-9999");
  $form.validate();
});

and

$(function() {
  var $form = $("#myForm"), $zip = $("#zip");
  $form.validate();
  $zip.mask("99999?-9999");
});

I think that the bindings should fire in order.

查看更多
我欲成王,谁敢阻挡
4楼-- · 2019-01-17 04:16

I tried following solution, and it works like a charm.

$("[data-input-type=phone]", "body")
  .mask("(999) 999 99 99")
  .bind("blur", function () {
    // force revalidate on blur.

    var frm = $(this).parents("form");
    // if form has a validator
    if ($.data( frm[0], 'validator' )) {
      var validator = $(this).parents("form").validate();
      validator.settings.onfocusout.apply(validator, [this]);
    }
  });

What triggers the problem is event ordering. When an element is masked, it is validated by maskedinput plugin on blur, but same element is validated by validator plugin on focusout event (which is a wrapper for blur on non-ie browsers) which is called before maskedinput's blur.

In this situation input element has the value "(___) ___ __ __" when validator checks for the value. When code reaches maskedinput's blur event, plugin tests and clears the value since it is not a valid input.

Validation result may be different for each validation case. For instance required rules will pass with success since element has a value. non-required number fields will fail even if we leave the input empty since a mask like "999" may be tested as 12_

the code above tests if form of masked input has validation attached to it, and recalls focusout event handler. Since our handler is attached as the latest, hopefully it will be called at last.

A warning, code simply copies behavior of validation plugin. It will probably work for a decade but may fail if validation plugin decides to do things different than now.

sincerely

查看更多
不美不萌又怎样
5楼-- · 2019-01-17 04:17

I had a similar problem and managed to solve it changing the default placeholder character to an empty string.

I was using a .mask('9?99'); and was validating with a simple 'number' rule, and was having the same kind of conflict.

I changed the mask declaration to .mask('9?99',{placeholder:''}); and... no more conflict. :)

There's a possible problem in your case, because of the - in the zip code, which is always inserted in the form by the mask plugin. I think you can change the regexp to ^\d{5}\-$|^\d{5}\-\d{4}$ (matching the dash in both cases) and it should validate then.

Anyway, you posted a while ago and probably don't need this anymore but perhaps it'll help someone else. :)

查看更多
手持菜刀,她持情操
6楼-- · 2019-01-17 04:21

Simply hook in to the mask() function and append some additional logic to fire your own blur logic after the default mask's blur logic:

//Store the original mask function
var origMaskFn = $.fn.mask;

$.fn.mask = function (mask, settings) {

    //Call the original function applying the default settings
    origMaskFn.apply(this, [mask, settings]);

    //Manually validate our element on blur to prevent unobtrusive messages
    //from showing before the mask is properly scrubbed
    $(this).bind('blur', function () {
        var $form = $(this).parents('form');
        if (!$form.exists())
            return;

        var validator = $form.validate().element($(this));
    });
}

Also, you may notice the 'exists()' function. This is something I use a lot with my jQuery selectors:

//Function to detect if a jQuery elements exists.  i.e.:
//      var $element = $(selector);
//      if($element.exists()) do something...

$.fn.exists = function () {
    return this.length !== 0;
}
查看更多
时光不老,我们不散
7楼-- · 2019-01-17 04:23

I tried some of the solutions but none worked for me, i'm using 'bootstrapValidator' (http://bootstrapvalidator.com/) and 'jquery.maskedinput' (http://digitalbush.com/projects/masked-input-plugin/) so if someone still have this problem, my solution was:

My input was marked like this:

<input type="text" class="form-control" id="telefone" placeholder="(__) ____-_____" name="telefone" required>

And the full script like this:

$(document).ready(function() {
   var telefone = $('#telefone'), //THAT'S MY INPUT WITH MASK
       form = telefone.parents("form"), //THAT'S MY FORM WITH THE VALIDATE
       alteredField = false; //THAT'S A FLAG TO SSE IF THE INPUT WAS ALTERED

            // APPLY MY MASK
            telefone.mask('(99) 9999-9999?9');

            // FOCUS ON ANY INPUT
            form.find('input[type=text]').focus(function(){
                // REMOVE THE VALIDATION STATUS
                $(form).data('bootstrapValidator').updateStatus($(this).attr('name'), 'NOT_VALIDATED');
                // ENABLE THE SUBMIT BUTTON
                $(form).data('bootstrapValidator').disableSubmitButtons(false);
            });

            // FOCUS ON THE MASKED INPUT
            telefone.focus(function(){
                // DISABLE THE VALIDATE
                $(form).data('bootstrapValidator').enableFieldValidators('telefone', false);
                // ENABLE THE SUBMIT BUTTON
                $(form).data('bootstrapValidator').disableSubmitButtons(false);
            }).blur(function(){ // BLUR ON THE MASKED INPUT
                // GET THE INPUT VALUE
                var value = telefone.val();
                // ENABLE THE VALIDATE 
                $(form).data('bootstrapValidator').enableFieldValidators('telefone', true);
                // CHECK IF THE VALUE IS EMPTY
                if(value != ""){
                    // CHANGE THE STATUS OF THE INPUT TO VALID 
                    $(form).data('bootstrapValidator').updateStatus('telefone', 'VALID')
                    // ACTIVE THE FLAG
                    alteredField = true;
                }else if (alteredField) { // IF THE INPUT WAS ALTERED BEFORE AND DON'T HAVE VALUE
                    // CHANGE THE STATUS OF THE INPUT TO INVALID
                    $(form).data('bootstrapValidator').updateStatus('telefone', 'INVALID')
                };
            });

        });

This code modifies the standard behavior of the 'bootstrapValidator', but was the only way that I gotta to solve the bug.

I don't know about performance issues, so please modify and improve the code ^^ Hope that this help ^_^

查看更多
登录 后发表回答