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.
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')
A conflict between them is produced when I do the following:
This problem does not happen if I fill out a 9-digit zip.
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?
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.
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