Validating international phone numbers
← ejemplos
FormValidation provides the built-in phone validator to validate the phone number in various countries. Behind the scenes, it uses Javascript regular expressions to check if a given number matches the pattern of phone numbers in particular country. Therefore, despite the fact that it doesn't cover all possible formats of phone number in the world, you can use the regexp validator to test a phone number in your country.
This example helps you validate an international phone number by using a different approach. We will use the intl-tel-input plugin for that purpose.
intl-tel-input is a popular jQuery plugin for entering and validating international telephone numbers. Below is some of its advanced features:
- Provides a very friendly user interface to enter a phone number. All countries are shown as a drop list with the flags and suggestion phone number
- Provides up-to-date patterns of phone numbers in the world. The data are taken from Google libphonenumber library so they are completely stable
- Has a few of APIs to validate and integrate with other tools
Using the intl-tel-input plugin
It's quite easy to use the intl-tel-input plugin for an input field:
- Download its latest version
- Include its CSS, Javascript files
<!-- The paths might be changed to suit with your folder structure -->
<link rel="stylesheet" href="/vendor/intl-tel-input/build/css/intlTelInput.css" />
<script src="/vendor/intl-tel-input/build/js/intlTelInput.min.js"></script>
- Attach the plugin to field
$(document).ready(function() {
$('#contactForm')
.find('[name="phoneNumber"]')
.intlTelInput({
utilsScript: '/vendor/intl-tel-input/lib/libphonenumber/build/utils.js',
autoPlaceholder: true,
preferredCountries: ['fr', 'us', 'gb']
});
});
In the next sections, you will see how to integrate the intl-tel-input plugin with FormValidation.
Using the callback validator
We can make the intl-tel-input plugin work with FormValidation easily by combining its isValidNumber() method and the callback validator:
$('#contactForm').formValidation({
...
fields: {
phoneNumber: {
validators: {
callback: {
message: 'The phone number is not valid',
callback: function(value, validator, $field) {
return value === '' || $field.intlTelInput('isValidNumber');
}
}
}
}
}
});
There is an important note that the input must be revalidated when the user choose
another country from the drop list. Unfortunately, intl-tel-input doesn't provide an
event or callback that is executed after choosing a country. But it can be done by using
a simple click
event handler on the countries list element which has
.country-list
class:
$('#contactForm')
.formValidation(...)
// Revalidate the number when changing the country
.on('click', '.country-list', function() {
$('#contactForm').formValidation('revalidateField', 'phoneNumber');
});
The field is revalidated by the revalidateField() method.
You can try this approach yourself in the live form below:
<link rel="stylesheet" href="/vendor/intl-tel-input/build/css/intlTelInput.css" />
<form id="contactForm" class="form-horizontal">
<div class="form-group">
<label class="col-xs-3 control-label">Phone number</label>
<div class="col-xs-5">
<input type="tel" class="form-control" name="phoneNumber" />
</div>
</div>
</form>
<script src="/vendor/intl-tel-input/build/js/intlTelInput.min.js"></script>
<script>
$(document).ready(function() {
$('#contactForm')
.find('[name="phoneNumber"]')
.intlTelInput({
utilsScript: '/vendor/intl-tel-input/lib/libphonenumber/build/utils.js',
autoPlaceholder: true,
preferredCountries: ['fr', 'us', 'gb']
});
$('#contactForm')
.formValidation({
framework: 'bootstrap',
icon: {
valid: 'glyphicon glyphicon-ok',
invalid: 'glyphicon glyphicon-remove',
validating: 'glyphicon glyphicon-refresh'
},
fields: {
phoneNumber: {
validators: {
callback: {
message: 'The phone number is not valid',
callback: function(value, validator, $field) {
return value === '' || $field.intlTelInput('isValidNumber');
}
}
}
}
}
})
// Revalidate the number when changing the country
.on('click', '.country-list', function() {
$('#contactForm').formValidation('revalidateField', 'phoneNumber');
});
});
</script>
Developing custom validator
If you often use intl-tel-input plugin in multiple forms, it's the right time to write a custom validator and reuse it whenever you want.
It's easy to develop new validator, assume that it's named as
intPhoneNumber
:
$(document).ready(function() {
// Define new validator
FormValidation.Validator.intPhoneNumber = {
init: function(validator, $field, options) {
// Attach the intlTelInput on field
$field.intlTelInput({
utilsScript: '/vendor/intl-tel-input/lib/libphonenumber/build/utils.js',
autoPlaceholder: true,
preferredCountries: 'fr,us,gb',
});
// Revalidate the field when changing the country
var $form = validator.getForm(),
fieldName = $field.attr('data-fv-field');
$form.on('click.country.intphonenumber', '.country-list', function() {
$form.formValidation('revalidateField', fieldName);
});
},
destroy: function(validator, $field, options) {
$field.intlTelInput('destroy');
// Turn off the event
validator.getForm().off('click.country.intphonenumber');
},
validate: function(validator, $field, options) {
return $field.val() === '' || $field.intlTelInput('isValidNumber');
}
};
});
The following table recaps the purpose of init
, destroy
and
validate
methods used above:
Method | Functionality |
---|---|
init |
This method does anything you need to prepare the validation. It's called once right after attaching the validator to field |
destroy |
This method is called when you destroy the FormValidation instance by using the destroy() method |
validate |
This is the validation method. It returns true or
false that indicates the field is valid or invalid |
These methods take three parameters:
validator
is the FormValidation instance. So you can call public methods on it$field
is the field elementoptions
is an object containing the validator options
Applying the new validator to field is easy:
$('#contactForm').formValidation({
...
fields: {
phoneNumber: {
validators: {
intPhoneNumber: {
message: 'The phone number is not valid'
}
}
}
}
});
There's still a small thing we should improve. As seen in the code above, there're some hard coded options when attaching intl-tel-input to field:
FormValidation.Validator.intPhoneNumber = {
init: function(validator, $field, options) {
// The utilsScript, autoPlaceholder and preferredCountries options are fixed
$field.intlTelInput({
utilsScript: '/vendor/intl-tel-input/lib/libphonenumber/build/utils.js',
autoPlaceholder: true,
preferredCountries: 'fr,us,gb',
});
...
}
...
};
You don't need to worry about this. It's possible to define and pass options to the
options
parameter as following:
FormValidation.Validator.intPhoneNumber = {
html5Attributes: {
message: 'message',
autoplaceholder: 'autoPlaceholder',
preferredcountries: 'preferredCountries',
utilsscript: 'utilsScript'
},
init: function(validator, $field, options) {
// Determine the preferred countries
var autoPlaceholder = options.autoPlaceholder === true || options.autoPlaceholder === 'true',
preferredCountries = options.preferredCountries || 'us';
if ('string' === typeof preferredCountries) {
preferredCountries = preferredCountries.split(',');
}
// Attach the intlTelInput on field
$field.intlTelInput({
utilsScript: options.utilsScript || '',
autoPlaceholder: autoPlaceholder,
preferredCountries: preferredCountries
});
...
}
...
};
The html5Attributes
property defines the HTML attributes which can be mapped
with the validator options. These attributes can be used in the declarative mode, for example:
<input name="phoneNumber"
data-fv-intphonenumber="true"
data-fv-intphonenumber-autoplaceholder="true"
data-fv-intphonenumber-preferredcountries="fr,us,gb"
data-fv-intphonenumber-utilsscript="/vendor/intl-tel-input/lib/libphonenumber/build/utils.js"
data-fv-intphonenumber-message="The phone number is not valid" />
The snippet code below is a programmatic usage of passing the options:
$('#contactForm').formValidation({
...
fields: {
phoneNumber: {
validators: {
intPhoneNumber: {
utilsScript: '/vendor/intl-tel-input/lib/libphonenumber/build/utils.js',
autoPlaceholder: true,
preferredCountries: 'fr,us,gb',
message: 'The phone number is not valid'
}
}
}
}
});
You can take a look at the code tab to see how all of these things work together:
<link rel="stylesheet" href="/vendor/intl-tel-input/build/css/intlTelInput.css" />
<form id="contactForm" class="form-horizontal">
<div class="form-group">
<label class="col-xs-3 control-label">Phone number</label>
<div class="col-xs-5">
<input type="tel" class="form-control" name="phoneNumber" />
</div>
</div>
</form>
<script src="/vendor/intl-tel-input/build/js/intlTelInput.min.js"></script>
<script>
$(document).ready(function() {
// Define new validator
FormValidation.Validator.intPhoneNumber = {
html5Attributes: {
message: 'message',
autoplaceholder: 'autoPlaceholder',
preferredcountries: 'preferredCountries',
utilsscript: 'utilsScript'
},
init: function(validator, $field, options) {
// Determine the preferred countries
var autoPlaceholder = options.autoPlaceholder === true || options.autoPlaceholder === 'true',
preferredCountries = options.preferredCountries || 'us';
if ('string' === typeof preferredCountries) {
preferredCountries = preferredCountries.split(',');
}
// Attach the intlTelInput on field
$field.intlTelInput({
utilsScript: options.utilsScript || '',
autoPlaceholder: autoPlaceholder,
preferredCountries: preferredCountries
});
// Revalidate the field when changing the country
var $form = validator.getForm(),
fieldName = $field.attr('data-fv-field');
$form.on('click.country.intphonenumber', '.country-list', function() {
$form.formValidation('revalidateField', fieldName);
});
},
destroy: function(validator, $field, options) {
$field.intlTelInput('destroy');
validator.getForm().off('click.country.intphonenumber');
},
validate: function(validator, $field, options) {
return $field.val() === '' || $field.intlTelInput('isValidNumber');
}
};
$('#contactForm').formValidation({
framework: 'bootstrap',
icon: {
valid: 'glyphicon glyphicon-ok',
invalid: 'glyphicon glyphicon-remove',
validating: 'glyphicon glyphicon-refresh'
},
fields: {
phoneNumber: {
validators: {
intPhoneNumber: {
utilsScript: '/vendor/intl-tel-input/lib/libphonenumber/build/utils.js',
autoPlaceholder: true,
preferredCountries: 'fr,us,gb',
message: 'The phone number is not valid'
}
}
}
}
});
});
</script>
Bonus: Showing useful validation message
What if you want to provide more descriptive validation message. For example, how to inform the user that the number is too short, too long or even not a number.
We can archive this requirement by adding a dynamic message supported by the callback validator.
The message is determined based on the validation error that is retrieved by the
getValidationError()
method:
$('#contactForm').formValidation({
...
fields: {
phoneNumber: {
validators: {
callback: {
callback: function(value, validator, $field) {
var isValid = value === '' || $field.intlTelInput('isValidNumber'),
err = $field.intlTelInput('getValidationError'),
message = null;
switch (err) {
case intlTelInputUtils.validationError.INVALID_COUNTRY_CODE:
message = 'The country code is not valid';
break;
case intlTelInputUtils.validationError.TOO_SHORT:
message = 'The phone number is too short';
break;
case intlTelInputUtils.validationError.TOO_LONG:
message = 'The phone number is too long';
break;
case intlTelInputUtils.validationError.NOT_A_NUMBER:
message = 'The value is not a number';
break;
default:
message = 'The phone number is not valid';
break;
}
return {
valid: isValid,
message: message
};
}
}
}
}
}
});
Here is the full working demonstration:
<link rel="stylesheet" href="/vendor/intl-tel-input/build/css/intlTelInput.css" />
<form id="contactForm" class="form-horizontal">
<div class="form-group">
<label class="col-xs-3 control-label">Phone number</label>
<div class="col-xs-5">
<input type="tel" class="form-control" name="phoneNumber" />
</div>
</div>
</form>
<script src="/vendor/intl-tel-input/build/js/intlTelInput.min.js"></script>
<script>
$(document).ready(function() {
$('#contactForm')
.find('[name="phoneNumber"]')
.intlTelInput({
utilsScript: '/vendor/intl-tel-input/lib/libphonenumber/build/utils.js',
autoPlaceholder: true,
preferredCountries: ['fr', 'us', 'gb']
});
$('#contactForm')
.formValidation({
framework: 'bootstrap',
icon: {
valid: 'glyphicon glyphicon-ok',
invalid: 'glyphicon glyphicon-remove',
validating: 'glyphicon glyphicon-refresh'
},
fields: {
phoneNumber: {
validators: {
callback: {
callback: function(value, validator, $field) {
var isValid = value === '' || $field.intlTelInput('isValidNumber'),
err = $field.intlTelInput('getValidationError'),
message = null;
switch (err) {
case intlTelInputUtils.validationError.INVALID_COUNTRY_CODE:
message = 'The country code is not valid';
break;
case intlTelInputUtils.validationError.TOO_SHORT:
message = 'The phone number is too short';
break;
case intlTelInputUtils.validationError.TOO_LONG:
message = 'The phone number is too long';
break;
case intlTelInputUtils.validationError.NOT_A_NUMBER:
message = 'The value is not a number';
break;
default:
message = 'The phone number is not valid';
break;
}
return {
valid: isValid,
message: message
};
}
}
}
}
}
})
// Revalidate the number when changing the country
.on('click', '.country-list', function() {
$('#contactForm').formValidation('revalidateField', 'phoneNumber');
});
});
</script>
Bonus: Asking number to match given type
The patterns provided by Google libphonenumber covers most of possible types of a phone number such as mobile, fixed line, free phone lines, voice over IP, etc.
The type can be retrieved by the getNumberType()
method. By adding the type
to the callback return value, and reuse it later, we can treat a phone number as invalid
one if it doesn't match our type.
The following code illustrates how to accept the mobile phone numbers only:
$('#contactForm')
.formValidation({
...
fields: {
phoneNumber: {
validators: {
callback: {
message: 'The phone number is not valid',
callback: function(value, validator, $field) {
return {
valid: value === '' || $field.intlTelInput('isValidNumber'),
type: $field.intlTelInput('getNumberType')
};
}
}
}
}
}
})
.on('success.validator.fv', function(e, data) {
if (data.field === 'phoneNumber' && data.validator === 'callback' && data.element.val() !== '') {
if (data.result.type !== intlTelInputUtils.numberType.MOBILE) {
data.fv
// Mark the field as invalid
.updateStatus('phoneNumber', 'INVALID', 'callback')
// Update the message
.updateMessage('phoneNumber', 'callback', 'We accept the mobile numbers only');
} else {
// Reset the message
data.fv.updateMessage('phoneNumber', 'callback', 'The phone number is not valid');
}
}
});
In short, it handles the success.validator.fv
event which is triggered when the field passes a validator. Then depend on the
type
returned by the validation result, it can use the updateStatus() method to mark field as invalid one.
The updateMessage() method is also used to set or reset the validation message.
<link rel="stylesheet" href="/vendor/intl-tel-input/build/css/intlTelInput.css" />
<form id="contactForm" class="form-horizontal">
<div class="form-group">
<label class="col-xs-3 control-label">Phone number</label>
<div class="col-xs-5">
<input type="tel" class="form-control" name="phoneNumber" />
</div>
</div>
</form>
<script src="/vendor/intl-tel-input/build/js/intlTelInput.min.js"></script>
<script>
$(document).ready(function() {
$('#contactForm')
.find('[name="phoneNumber"]')
.intlTelInput({
utilsScript: '/vendor/intl-tel-input/lib/libphonenumber/build/utils.js',
autoPlaceholder: true,
preferredCountries: ['fr', 'us', 'gb']
});
$('#contactForm')
.formValidation({
framework: 'bootstrap',
icon: {
valid: 'glyphicon glyphicon-ok',
invalid: 'glyphicon glyphicon-remove',
validating: 'glyphicon glyphicon-refresh'
},
fields: {
phoneNumber: {
validators: {
callback: {
message: 'The phone number is not valid',
callback: function(value, validator, $field) {
return {
valid: value === '' || $field.intlTelInput('isValidNumber'),
type: $field.intlTelInput('getNumberType')
};
}
}
}
}
}
})
.on('success.validator.fv', function(e, data) {
if (data.field === 'phoneNumber' && data.validator === 'callback' && data.element.val() !== '') {
// You can see type of phone number by printing out data.result.type
// console.log(data.result.type);
if (data.result.type !== intlTelInputUtils.numberType.MOBILE) {
data.fv
// Mark the field as invalid
.updateStatus('phoneNumber', 'INVALID', 'callback')
// Update the message
.updateMessage('phoneNumber', 'callback', 'We accept the mobile numbers only');
} else {
// Reset the message
data.fv.updateMessage('phoneNumber', 'callback', 'The phone number is not valid');
}
}
})
// Revalidate the number when changing the country
.on('click', '.country-list', function() {
$('#contactForm').formValidation('revalidateField', 'phoneNumber');
});
});
</script>