Статьи

Работа с телефонными номерами в JavaScript

Когда вы собираете данные от пользователей, есть две ключевые проблемы; собирать эту информацию и проверять ее. Некоторые типы информации просты — например, чей-то возраст не может быть проще собрать и проверить. Имена не так просты, как звучат, но при условии, что вы учитываете крайние случаи и международные различия — например, отчества, одноименные или даже просто люди с дефисными фамилиями — вы не можете пойти слишком далеко не так (хотя множество приложений и услуги делают!). Адреса электронной почты, хотя теоретически их очень легко проверить, имеют свои проблемы, но, тем не менее, существует множество регулярных выражений, которые не совсем верны.

И тогда есть телефонные номера. Это сложно. Действительно трудно. В этой статье я расскажу о некоторых проблемах, связанных со сбором, проверкой и отображением телефонных номеров.

Почему телефонные номера разные

Возможно, вы думаете, что, поскольку телефонные номера, как правило, имеют довольно жесткий формат, такой как этот:

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 

Исходя из этого, мы знаем, что apparoach с регулярными выражениями не так прост, как мы думали, но это только половина. Эти примеры только для номера в США. Конечно, если вы знаете, что номер, который вы собираете, будет для определенной страны, вы можете использовать регулярное выражение. В противном случае такой подход не поможет.

Давайте посмотрим на некоторые другие вопросы, касающиеся телефонных номеров, и почему они делают нашу работу еще сложнее.

Изменение номера

Все виды внешних факторов могут иметь значение для нумерации телефонов. Целые страны приходят и уходят, вводя новые префиксы стран. Новые классификации номеров вводят новые системы нумерации — премиум-тариф, местный тариф, бесплатный и т. Д. Когда у оператора заканчивается один набор номеров — как, к сожалению, премиум-тариф — он просто вводит новый префикс.

Некоторые изменения имеют огромное значение; например, в Соединенном Королевстве несколько лет назад вся региональная система нумерации претерпела радикальные изменения: практически в каждый код города добавлена ​​дополнительная цифра «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 цифр.
  • Первая часть номера телефона — это код страны.
  • Вторая часть — это национальный код пункта назначения (НДЦ).
  • Последняя часть — это номер абонента (SN)
  • NDC и SN вместе называются национальным (значимым) числом

( источник )

Вот номер из ранее, в формате E.164:

 +12024561111 

В качестве примера мы можем использовать тот же формат для британского номера в Лондоне:

 +442079250918 

Мы можем представить любой действительный номер телефона в формате E.164. Мы знаем, к какой стране это относится, и это однозначно — что делает его идеальным выбором для хранения. Он также широко используется для услуг телефонии, таких как SMS-провайдеры, как мы увидим чуть позже.

Конечно, есть подвох. Стандарт E.164 может быть хорош для хранения, но ужасен для двух вещей. Во-первых, практически никто не наберет и не зачитает их число в этом формате. Во-вторых, он безнадежен с точки зрения читабельности. Позже, хотя, когда мы посмотрим на libphonenumber , мы увидим, что существуют способы форматирования чисел для людей.

Сбор телефонных номеров

Сначала давайте посмотрим на вопрос сбора телефонных номеров.

HTML5 и «тел» ввод

HTML5 ввел новый тип ввода «тел». Однако из-за проблем, связанных с различиями в формате, он фактически не накладывает никаких ограничений на то, что пользователь может печатать, и не выполняет никакой проверки так же, как, например, элемент электронной почты. Тем не менее, есть некоторые преимущества — при использовании на мобильном сайте обычно отображается телефонная клавиатура пользователя, а не обычная раскладка клавиатуры.

Вы можете использовать один элемент для сбора числа:

 <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, и что спрайт флага доступен и правильно build/img/flags.png в CSS — вы найдете его в build/img/flags.png .

Далее создайте элемент:

 <input type="tel" id="number"> 

Наконец, инициализируйте это следующим образом:

 $("#number").intlTelInput(); 

Полный список параметров конфигурации см. В документации . Позже мы рассмотрим параметр utilsScript , но сначала нам нужно углубиться в другую полезную библиотеку.

Представляем libphonenumber

К счастью, есть решение многих проблем с проверкой и форматированием. Библиотека Google libphonenumber, изначально разработанная для операционной системы Android, предлагает всевозможные методы и утилиты для работы с телефонными номерами. Более того, он был перенесен с Java на Javascript, поэтому мы можем использовать его в веб-приложениях или приложениях Node.js.

Установка

Вы можете скачать библиотеку с домашней страницы проекта на — как и следовало ожидать — Google Code.

Вы также можете получить его через npm. Вот страница проекта , и установить из командной строки:

 npm install google-libphonenumber 

Вы также можете установить его с помощью Bower:

 bower install libphonenumber 

Если вы думаете об использовании его в проекте переднего плана, будьте осторожны — даже если он минимизирован и сжат, он занимает более 200 КБ.

Разбор чисел

Чтобы продемонстрировать ключевые функции библиотеки, я предполагаю, что вы пишете приложение Node.js. Вы можете найти пример кода в репозитории, который дополняет эту статью .

Сначала импортируем phoneUtil :

 var phoneUtil = require('google-libphonenumber').phoneUtil; 

Теперь вы можете использовать его метод parse() для интерпретации телефонного номера:

 var tel = phoneUtil.parse('+12024561111'); 

Есть несколько вещей, которые мы можем сделать с этим. Давайте сначала импортируем некоторые константы из библиотеки. Измените вашу декларацию о требованиях следующим:

 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 или даже позвонить .

Вот очень простой фрагмент кода для генерации и отправки кода подтверждения по SMS с использованием Twilio:

 // 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 }); 

Затем это тривиальное упражнение, чтобы попросить пользователей ввести код в форму в вашем веб-приложении, чтобы проверить его, или вы даже можете позволить людям подтвердить свой номер, ответив на сообщение.

Существуют также (платные) услуги, которые проверяют, работает ли номер для вас в режиме реального времени, например, этот от Byteplant .

Другие вопросы

Как и в случае с любой личной информацией, существует множество юридических вопросов, о которых следует помнить. Например, в Великобритании Служба телефонных предпочтений (TPS) — это национальный реестр телефонных номеров, которые были явно зарегистрированы людьми, не желающими получать маркетинговые сообщения. Есть платные сервисы, которые предлагают API для проверки номера по этому регистру, например, этот .

Вопросы юзабилити

Очень часто запрашивается до трех разных телефонных номеров в одной форме; например, дневное, вечернее и мобильное.

Также стоит помнить, что запрос телефонных номеров через Интернет может показаться довольно навязчивым. Если кто-то не желает предоставлять эту информацию, несмотря на то, что вы сделали это поле обязательным, он, вероятно, сделает одно из двух:

  • Попытка «обмануть» валидацию. В зависимости от подхода они могут напечатать что-то вроде «ex directory» или ввести недопустимый номер — например, тот, который содержит только цифры.
  • Уходи.

Объединение плагина jQuery с libphonenumber

Возможно, вы помните, что плагин jQuery имеет довольно загадочно названную опцию, называемую utilsScript .

Эта опция позволяет нам использовать возможности проверки и форматирования libphonenumber . Выбрав страну — используя раскрывающийся список или введя телефонный код — она ​​преобразует текстовое поле в замаскированный ввод, который отражает формат нумерации этой страны.

Плагин содержит упакованную версию libphonenumber; передать путь к этому файлу в конструктор следующим образом:

 $("#number").intlTelInput( { utilsScript : '/bower_components/intl-tel-input/lib/libphonenumber/build/utils.js' } ); 

Как я уже упоминал ранее, имейте в виду, что этот подход следует использовать с осторожностью из-за размера libphonenumber библиотеки libphonenumber . Однако ссылка на него здесь в конструкторе означает, что он может быть загружен по требованию.

Отображение телефонных номеров

Мы рассмотрели, как мы можем форматировать числа при их отображении, чтобы сделать их более «дружественными», используя такие форматы, как PNF.INTERNATIONAL и PNF.NATIONAL .

Мы также можем использовать протоколы tel и callto для добавления гиперссылок на телефонные номера, которые особенно полезны на мобильных сайтах — позволяя пользователям набирать номер непосредственно с веб-страницы.

Для этого нам понадобится формат E.164 для самой ссылки — например:

 <a href="tel:+12024561111">+1 202-456-1111</a> 

Конечно, вы можете использовать libphonenumber format() библиотеки libphonenumber для визуализации как версии E.164 ( PNF.E164 ), так и более удобной для пользователя версии отображения.

Microdata

Мы также можем использовать микроданные для семантической разметки телефонных номеров. Вот пример; обратите внимание на использование 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: и используйте микроданные.