从用户收集数据时,存在两个主要挑战; 收集并验证信息。 某些类型的信息很简单-例如,某人的年龄实际上并不太容易收集和验证。 名称并不像听起来那样简单,但可以满足特殊情况和国际变化的需要(例如,赞助人,专名或什至只是带有姓氏的人),您不会做错太多(尽管有很多应用服务呢!)。 电子邮件地址虽然在理论上很容易验证,但也面临着挑战-然而,仍然有很多正则表达式不太正确。
然后是电话号码。 这些很难。 真的很难。 在本文中,我将讨论有关收集,验证和显示电话号码的一些挑战。
为什么电话号码不同
也许您在想,由于电话号码通常遵循严格的格式,例如:
202-456-1111
…构造一个简单的正则表达式来验证它们应该很简单。 实际上,这是一个:
^(\([0-9]{3}\)|[0-9]{3}-)[0-9]{3}-[0-9]{4}$
好吧,就在那儿停下来。 对于初学者来说,这只是上面数字的一些变体,所有这些都完全有效:
202 456 1111
(202) 456 1111
2024561111
1-202-456-1111
1-202-456-1111 x1234
1-202-456-1111 ext1234
1 (202) 456-1111
1.202.456.1111
1/202/456/1111
12024561111
+1 202 456 1111
因此,基于此,我们知道正则表达式方法并不像我们最初想到的那么简单-但这只是其中的一半。 这些示例仅适用于美国的电话号码。 当然,如果您知道要收集的号码将用于特定国家/地区,则可以使用正则表达式。 否则,这种方法将无法实现。
让我们看一下有关电话号码的其他一些问题,以及它们为什么使我们的工作更加困难。
号码变更
各种外部因素都可能对电话号码产生影响。 整个国家来来往往,引入了新的国家前缀。 新的数字分类引入了新的编号系统-溢价率,本地费率,免费电话等。 当运营商用完一组数字(例如,可悲的是溢价)时,它们只是引入了一个新的前缀。
一些变化具有巨大的影响。 例如,几年前在英国,整个区域编号系统发生了翻天覆地的变化,几乎每个区号都插入了额外的“ 1”。 即使到那时,首都的系统也有所不同。 在全国改变标识以反映变化的大概十年。
然后,当然,移动技术有了前所未有的巨大增长。 所需的电话号码不再主要限于家庭数目,而是许多次。 可用数字池的持续紧张只会增加进一步变化的可能性。
国际拨号代码
捕获号码的国际拨号代码通常很重要。 在某些情况下,上下文可能意味着它们不是必需的。 例如,如果您在单个国家/地区开展业务,而电话号码被捕获以供操作员使用,则可能不需要它们。 但是对于任何远程自动化的工具(例如发送SMS消息)或要对其进行有效验证,则需要捕获国家/地区前缀。
国家图书馆包含大量的地理信息,其中包括国际拨号代码。 这是该库的countries.json
的摘录:
{
"name": {
"common": "Austria",
"official": "Republic of Austria",
// ... //
},
// ... //
"callingCode": ["43"],
// ... //
},
如您所见,这表明奥地利使用国际拨号代码43。
那么我们将如何使用这些信息? 好吧,利用Lodash(或Underscore)的魔力,我们可以通过几种方式查询与拨号代码相关的信息。
例如,要确定给定的拨号代码是否有效:
var _ = require('lodash')
, data = require('world-countries')
module.exports = {
/**
* Determines whether a given international dialing code is valid
*
* @param string code
* @return bool
*/
isValid : function(code) {
var codes = _.flatten(_.pluck(data, 'callingCode'));
return _.contains(codes, code);
}
// ...
}
当然,有更有效的方法可以执行此操作,因此此示例和以下示例不一定针对生产进行优化。
我们可以查找使用特定拨号代码的国家/地区:
/**
* Gets a list of countries with the specified dialing code
*
* @param string code
* @return array An array of two-character country codes
*/
getCountries : function(code) {
var countryEntries = _.filter(data, function(country){
return (_.contains(country.callingCode, code));
})
return _.pluck(countryEntries, 'cca2');
}
最后,我们可以获得给定国家/地区的拨号代码:
/**
* Gets the dialing codes for a given country
*
* @param string country The two-character country code
* @return array An array of strings representing the dialing codes
*/
getCodes : function(country) {
// Get the country entry
var countryData = _.find(data, function(entry) {
return (entry.cca2 == country);
});
// Return the code(s)
return countryData.callingCode;
}
您将在本文随附的存储库中找到这些功能以及单元测试,它们打包在一起作为模块。
但是,即使是国际拨号代码也没有您想像的那么简单。 格式可以有所不同– 1,43,962 1868均为有效代码。 不一定是一对一的映射。 例如,44不仅用于英国,而且用于马恩岛,根西岛和泽西岛。
号码也必须根据您拨号的位置进行更改。 在国外,要拨打英国号码,您需要删除前导零,并在拨号代码44之前添加前缀:
020 7925 0918
…成为…
+44 20 7925 0918
您也可以将“ +”替换为双零:
0044 20 7925 0918
要复杂的事情,甚至进一步,一些数字取决于你从拨号哪个国家从国家之外调用时会有所不同。 例如,在美国,数字还必须带有美国出口代码011
前缀,因此上面的示例变为:
011 44 20 7925 0918
值得庆幸的是,我们可以使用一种格式来解决这些变化。
E.164
幸运的是,对于开发人员来说,世界上任何地方都有一个明确的国际公认的电话号码标准,称为E.164。 格式细分如下:
- 一个电话号码最多可以包含15位数字
- 电话号码的第一部分是国家/地区代码
- 第二部分是国家目的地代码(NDC)
- 最后一部分是订户号(SN)
- NDC和SN统称为国家(重要)号码
( 来源 )
这是E.164格式的较早编号:
+12024561111
例如,我们可以对伦敦的英国号码使用相同的格式:
+442079250918
我们可以使用E.164格式表示任何有效的电话号码。 我们知道它指的是哪个国家,而且它并不陌生-使其成为理想的存储选择。 它也常用于基于电话的服务,例如SMS提供商,我们将在稍后介绍。
当然有收获。 E.164标准可能非常适合存储,但是对于两件事却很糟糕。 首先,几乎没有人会以这种格式输入或读出他们的电话号码。 其次,就可读性而言,它毫无希望。 不过,稍后,当我们查看libphonenumber
,我们会看到有一些格式化人类的方法。
收集电话号码
不过,首先让我们看一下收集电话号码的问题。
HTML5和“电话”输入
HTML5引入了一种新的“电话”输入类型。 但是,由于围绕格式变化的问题,它实际上并未对用户可以输入的内容施加任何限制,也没有以与email元素相同的方式执行任何验证。 尽管如此,还是有一些优势–在移动站点上使用时,通常会显示用户的电话键盘,而不是传统的键盘布局。
您可以使用单个元素来收集数字:
<input type="tel" name="number">
另外,您可以将数字分解为单独的元素:
<!-- area code and number -->
<input type="tel" name="number">
<!-- country code, area code and number -->
<input type="tel" name="country" size="4"> <input type="tel" name="area" size="6"> <input type="tel" name="number" size="8">
<!-- US-style -->
(<input type="tel" size="3">) <input type="tel" size="3"> - <input type="tel" size="4">
浏览器支持相当不错(例如Chrome 6 +,Firefox 4 +,Safari 5 +,IE 10+),但是即使在较旧的浏览器中,它也只会退回到纯文本字段。
如果我们确定一个正则表达式就足够了(并记住存在问题),那么我们可以使用pattern
属性添加一些验证:
<input type="tel" name="number" pattern="^(?:\(\d{3}\)|\d{3})[- ]?\d{3}[- ]?\d{4}$">
屏蔽输入
屏蔽输入是一种常见的技术,用于限制用户输入或提供有关预期格式的提示。 但是,再次重申,除非您确信数字将始终针对特定国家/地区,否则很难迎合国际差异。 但是,通过做出假设来烦扰用户是一回事–要求非美国用户提供州和邮政编码。 使表格完全不可用是另一回事,例如,强迫人们以某个国家的格式提供数字。
但是,如果您知道某些数字将在特定范围内,它们将是有效的。 这是美国电话号码的掩码输入示例。
更好的方法
以出色的jQuery插件的形式,有一种更好,更灵活的方式来收集电话号码。 如下图所示。
您也可以在此处进行现场演示。
用法很简单-确保已包含jQuery,库和CSS文件,并且标志sprite可用并且可以从CSS中正确引用-您可以在build/img/flags.png
找到它。
接下来,创建一个元素:
<input type="tel" id="number">
最后,将其初始化如下:
$("#number").intlTelInput();
有关配置选项的完整列表, 请参阅文档 。 稍后,我们将研究utilsScript
选项,但首先,我们需要深入研究另一个有用的库。
引入libphonenumber
幸运的是,有许多解决方案可以解决我们的验证和格式化问题。 Google的libphonenumber库最初是为Android操作系统开发的,它提供了用于处理电话号码的各种方法和实用程序。 更好的是,它已从Java移植到Javascript,因此我们可以在Web或Node.js应用程序中使用它。
安装
您可以从项目主页上(如您期望的那样)从Google Code下载该库。
您也可以通过npm获取它。 这是项目页面 ,可以从命令行安装:
npm install google-libphonenumber
您也可以使用Bower安装它:
bower install libphonenumber
如果您正在考虑在前端项目中使用它,请注意-即使经过压缩和压缩,它的价格也超过200Kb。
解析数字
为了演示该库的关键功能,我假设您正在编写一个Node.js应用程序。 您可以在存储库中找到一些示例代码,作为对本文的补充 。
首先,导入phoneUtil
:
var phoneUtil = require('google-libphonenumber').phoneUtil;
现在,您可以使用其parse()
方法来解释电话号码:
var tel = phoneUtil.parse('+12024561111');
我们可以做很多事情。 首先让我们从库中导入一些常量。 将您的require
声明更改为以下内容:
var phoneUtil = require('google-libphonenumber').phoneUtil
, PNF = require('google-libphonenumber').PhoneNumberFormat
, PNT = require('google-libphonenumber').PhoneNumberType;
现在,我们可以执行以下操作:
var tel = phoneUtil.parse('+12024561111');
console.log(phoneUtil.format(tel, PNF.INTERNATIONAL));
console.log(phoneUtil.format(tel, PNF.NATIONAL));
console.log(phoneUtil.format(tel, PNF.E164));
输出如下:
+1 202-456-1111
(202) 456-1111
+12024561111
现在尝试在不使用国际拨号代码的情况下解析号码:
var tel = phoneUtil.parse('2024561111');
这将引发以下异常:
Error: Invalid country calling code
这是因为,如果没有明确告知电话号码所代表的国家/地区,就无法解释。 parse()
方法采用可选的第二个参数,它是ISO 3166-1 alpha-2(即两个字符)国家/地区代码。
如果您再次尝试该行,但是这次传递“ US”作为第二个参数,则会发现结果与以前一样:
var tel = phoneUtil.parse('2024561111', 'US');
您还可以尝试各种格式; 所有这些也将起作用:
var tel = phoneUtil.parse('202-456-1111', 'US');
var tel = phoneUtil.parse('(202) 456 1111', 'US');
解释英国号码:
var tel = phoneUtil.parse('(0) 20 7925 0918', 'GB');
console.log(phoneUtil.format(tel, PNF.INTERNATIONAL));
console.log(phoneUtil.format(tel, PNF.NATIONAL));
console.log(phoneUtil.format(tel, PNF.E164));
这将输出以下内容:
+44 20 7925 0918
020 7925 0918
+442079250918
解析数字后,就可以对其进行验证-正如我们将在下一部分中看到的那样。
验证号码
验证遵循类似的模式; 同样,还有第二个可选参数,但是如果未明确说明国家/地区,则需要使用该参数。
以下是一些有效数字的示例,其中国家/地区代码或者作为第二个参数提供,或者包含在第一个参数中:
console.log(phoneUtil.isValidNumber(phoneUtil.parse('+12024561111')));
// => outputs true
console.log(phoneUtil.isValidNumber(phoneUtil.parse('202-456-1111', 'US')));
// => outputs true
console.log(phoneUtil.isValidNumber(phoneUtil.parse('(0) 20 7925 0918', 'GB')));
// => outputs true
如果您不提供国家代码,或者没有提供国家代码,则将得到与以前相同的错误:
console.log(phoneUtil.isValidNumber(phoneUtil.parse('(0) 20 7925 0918')));
// => throws exception "Error: Invalid country calling code"
console.log(phoneUtil.isValidNumber(phoneUtil.parse('2024561111')));
// => throws exception "Error: Invalid country calling code"
以下是验证失败并返回false
一些示例:
console.log(phoneUtil.isValidNumber(phoneUtil.parse('573 1234 1234', 'US')));
// => outputs false
console.log(phoneUtil.isValidNumber(phoneUtil.parse('555-555-5555', 'US')));
// => outputs false (this is often used as a placeholder, but it's not a valid number)
console.log(phoneUtil.isValidNumber(phoneUtil.parse('295-123-1234', 'US')));
// => outputs false (there is no 295 area code in the US)
但是请注意,因为无效数字可能会引发异常:
console.log(phoneUtil.isValidNumber(phoneUtil.parse('NOT-A-NUMBER', 'US')));
// => throws exception "Error: The string supplied did not seem to be a phone number"
确定数字的类型
有时,了解电话号码的类型很有用。 例如,您可能希望确保获得一个手机号码-也许您打算发送SMS消息(例如,实施两因素身份验证)或尝试淘汰特优费率号码。
库的getNumberType()
函数就是这样做的。 让我们来看看。
该函数将已解析的电话号码作为其参数:
var tel = phoneUtil.parse('(0) 20 7925 0918', 'GB');
var type = phoneUtil.getNumberType(tel)
返回值是在PhoneNumberType子模块中定义的常量–您会记得我们require
将此输入为PNF。
例如,让我们查询所讨论的数字是移动电话还是固定电话:
if (type === PNT.MOBILE) {
console.log("It's a mobile number");
} else if (type === PNT.FIXED_LINE) {
console.log("It's a fixed line");
}
似乎是该主题的主题,自然有一个问题。 有时,甚至libphonenumber库也无法确定。 例如,美国号码很难区分。 因此常量PNT.FIXED_LINE_OR_MOBILE
。
我们只需要更改示例代码即可反映这种不确定性:
if (type === PNT.MOBILE) {
console.log("It's a mobile number");
} else if (type === PNT.FIXED_LINE) {
console.log("It's a fixed line");
} else if (type === PNT.FIXED_LINE_OR_MOBILE) {
console.log("Your guess is as good as mine");
}
也有许多其他可能性。 这是当前的完整列表:
-
PNT.FIXED_LINE
-
PNT.MOBILE
-
PNT.FIXED_LINE_OR_MOBILE
-
PNT.TOLL_FREE
-
PNT.PREMIUM_RATE
-
PNT.SHARED_COST
-
PNT.VOIP
-
PNT.PERSONAL_NUMBER
-
PNT.PAGER
-
PNT.UAN
-
PNT.UNKNOWN
如您所见, PNT.UNKNOWN
反映了一个事实,即我们不一定可以肯定地收集任何信息。 因此,总而言之,尽管此功能可以用作快速的初始检查,但我们不能依靠它。
号码在服务中吗?
有很多电话号码可以验证,但尚未使用。 它们可能已断开连接,尚未分配,或者SIM卡已掉落在厕所中。
如果您需要确保一个数字不仅有效而且有效,那么您可以使用许多选项。
一种方法是要求用户确认其号码,与您可能要求用户确认其电子邮件地址的方式几乎相同。 您可以使用Twilio之类的服务来发送SMS ,甚至可以拨打电话 。
这是一个非常简单的代码片段,用于使用Twilio通过SMS生成和发送确认代码:
// You'll need to install the Twilio library with "npm install twilio"
var client = require('twilio')('YOUR-SID', 'YOUR-AUTH-TOKEN');
// Generate a random four-digit code
var code = Math.floor(Math.random()*8999+1000);
// Send the SMS
client.sendMessage({
to: phoneUtil.format(tel, PNF.E164), // using libphonenumber to convert to E.164
from: 'YOUR-NUMBER',
body: 'Your confirmation code is ' + code
}, function(err, respons) {
// ...do something
});
然后,要求用户在您的Web应用程序中将代码输入表单中进行验证是一件很简单的事情,或者您甚至可以允许人们通过回复消息来验证其号码。
还有(收费)服务,将实时检查您是否正在使用某个号码,例如Byteplant的服务 。
其他事宜
法律
与任何个人信息一样,也要注意很多法律问题。 例如,在英国, 电话优先服务(TPS)是电话号码的国家注册簿,该电话簿已由不希望接收市场营销通讯的人明确注册。 有一些付费服务提供API来根据此寄存器检查数字,例如this 。
可用性注意事项
以一种形式请求多达三个不同的电话号码是很常见的。 例如白天,晚上和移动。
还值得记住的是,在互联网上询问电话号码可能很麻烦。 如果有人尽管您已将其设置为必填字段而仍不愿意提供该信息,则他们可能会执行以下两项操作之一:
- 尝试“欺骗”验证。 根据方法的不同,他们可能会键入“ ex directory”之类的内容,或输入无效的数字-例如仅包含数字的数字。
- 走开。
结合jQuery插件和libphonenumber
您可能还记得,jQuery插件有一个名为utilsScript
的相当隐秘的名称选项。
此选项使我们可以利用libphonenumber
的验证和格式化功能。 选择一个国家(使用下拉列表或输入拨号代码)后,它将把文本字段转换为可反映该国家编号格式的带掩码输入。
该插件包含libphonenumber的打包版本。 将此文件的路径传递给构造函数,如下所示:
$("#number").intlTelInput(
{
utilsScript : '/bower_components/intl-tel-input/lib/libphonenumber/build/utils.js'
}
);
如前所述,请记住,由于libphonenumber
库的文件大小,应谨慎使用此方法。 但是,在构造函数中在此处引用它确实意味着可以按需加载它。
显示电话号码
我们已经研究了如何使用PNF.INTERNATIONAL
和PNF.NATIONAL
格式显示数字时使其更“友好”。
我们还可以使用tel
和callto
协议在电话号码上添加超链接,这在移动网站上特别有用-允许用户直接从网页拨打号码。
为此,我们需要链接本身采用E.164格式-例如:
<a href="tel:+12024561111">+1 202-456-1111</a>
当然,您可以使用libphonenumber
库的format()
方法来呈现E.164版本( PNF.E164
)和更加用户友好的显示版本。
微数据
我们还可以使用微数据在语义上标记电话号码。 这是一个例子。 请注意使用itemprop="telephone"
标记链接:
<div itemscope itemtype="http://schema.org/LocalBusiness">
<h1 itemprop="name">Acme Corp, Inc.</h1>
Phone: <span itemprop="telephone"><a href="tel:+12024561111">202-456-1111</a></span>
</div>
摘要
在本文中,我们打开了黄蜂巢,即电话号码。 到现在为止,应该很明显地知道,如果需要收集,验证和显示它们,则需要了解各种复杂性,微妙性和陷阱。
我们已经研究了几种收集数字的方法-“ tel”输入类型,带掩码的输入,最后是intl-tel-input
jQuery插件。
然后,我们研究了围绕验证的一些问题,以及为什么常规方法(例如正则表达式)常常不足,尤其是在您走出国门时。
我们看了一下Google的libphonenumber
库; 使用它来解析,验证,显示和确定电话号码的类型。
我们将intl-tel-input
插件与libphonenumber
,以提供更好的用户体验,尽管这是以性能为代价的。
最后,我们研究了如何在HTML中标记电话号码。
对于处理电话号码,我将提出一些建议:
- 除非您仅在一个国家/地区开展业务,否则请注意国际差异。
- 请谨慎使用带掩盖的输入。
- 基于正则表达式的验证要非常小心。
- 尽可能使用E.164进行存储。
- 使用Google的libphonenumber库。
- 显示数字时,请尽可能设置其格式,使用tel:或callto:链接类型,然后使用微数据。
From: https://www.sitepoint.com/working-phone-numbers-javascript/