Решили раскрыть такой кейс, как форма с полем ввода номера телефона, ведь с такой задачей разработчик сталкивается частенько. Она далеко непростая и имеет подводные камни. Так давайте же нырнём и первое, что сделаем - это разобьём её на подзадачи.
- Валидация: Тут пока всё понятно, нам по хорошему нужно валидировать номер телефона.
- Форматирование: А с этим пунктом веселее, т.к. пользователи могут писать номер телефона в разных форматах:
8 *** *** ** **
,+7 ***-***-**-**
+7 (***) *******
,8 (***) *** **-**
и еще 100500 вариантов. - Разные страны: А теперь всё серьезнее, дело в том, что разные страны это разное форматирование и соответственно разная валидация.
Вы скажете - это очень просто, человечество бороздит космос, электрифицировало города и сёла, придумало интернет и в частности Viber. И вы будете правы, хотя, если бы все люди были похожи на меня, то мы остались бы в первобытном обществе с палками и камнями... но с Viber-ом 😃
Решение
Устанавливаем FetchIt. Напоминаем, недавно мы выпустили полноценную замену AjaxForm под названием FetchIt. Он также бесплатен и доступен на маркетплейсах modx.com и modstore.pro.
Вызываем сниппет FetchIt в том месте вёрстки где находится наша форма.
modx[[!FetchIt? &form=`bootstrap.form.example` &snippet=`FormIt` &hooks=`email,FormItSaveForm` &formName=`Заявка на обратный звонок` &validate=`name:required,phone:required` &fieldNames=`name==Имя,phone==Контактный телефон,pageId==ID страницы` &emailSubject=`Заявка на обратный звонок` ]]
Для простоты примера, представим, что у нас вёрстка на Bootstrap и чанк
bootstrap.form.example
будет выглядеть так:modx<form> <div class="mb-3"> <input type="text" name="name" class="form-control rounded-3" placeholder="Ваше имя" value="[[+fi.name]]"> <div data-error="name" class="invalid-feedback">[[+fi.error.name]]</div> </div> <div class="mb-3"> <input type="tel" name="phone" class="form-control rounded-3" placeholder="Телефон" value="[[+fi.phone]]"> <div data-error="phone" class="invalid-feedback">[[+fi.error.phone]]</div> </div> <button class="btn btn-primary rounded-3 w-100" type="submit">Отправить</button> </form>
Уже на данном этапе у нас форма будет работать, отправлять письма, записывать формы в БД и валидировать поля, но т.к. у нас задача сложнее, то продолжим.
Подключим библиотеку intl-tel-input, именно она нам поможет реализовать выбор страны, валидацию и форматирование. Для простоты примера сделаем это через CDN. Сначала стили.
html<!-- CSS --> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/intl-tel-input@17/build/css/intlTelInput.min.css"> <!-- Если у вас Bootstrap, то нужно добавить еще и парочку стилей --> <style> .iti { display: block; } // Для того, чтобы поле было на всю доступную ширину form[data-fetchit] [data-error="phone"] { display: block; } // Библиотека нарушает нужную для Bootstrap разметку, поэтому такой хак </style>
А скрипт в виде ES модуля:
html<script type="module"> import intlTelInput from 'https://cdn.jsdelivr.net/npm/intl-tel-input@17/+esm' </script>
Далее нам нужно инициализировать библиотеку для каждого поля ввода телефона, указав некоторые свойства. Со всеми её возможностями вы можете ознакомиться в репозитории библиотеки.
html<script type="module"> import intlTelInput from 'https://cdn.jsdelivr.net/npm/intl-tel-input@17/+esm' document.querySelectorAll('form[data-fetchit] [name="phone"]').forEach((input) => { intlTelInput(input, { autoPlaceholder: 'aggressive', // Изменять плейсхолдер на формат выбранной страны initialCountry: 'ru', // Код страны при инициализации preferredCountries: ['ru', 'kz', 'by', 'ge', 'us'], // Список кодов стран, которые будут в начале списка utilsScript: 'https://cdn.jsdelivr.net/npm/intl-tel-input@17/build/js/utils.js', // Подключение вспомогательного скрипта, он нужен для валидации и форматирования }); }); </script>
Подсказка
Данный пример будет работать даже в том случае, если у вас несколько форм с вводом телефона.
Теперь наша форма выглядит таким образом:
Осталось немного. А точнее подружить intl-tel-input и FetchIt. Для этого нам нужно добавить обработчик на событие
fetchit:before
, где и будет производиться валидация.html<script type="module"> import intlTelInput from 'https://cdn.jsdelivr.net/npm/intl-tel-input@17/+esm' document.querySelectorAll('form[data-fetchit] [name="phone"]').forEach((input) => { intlTelInput(input, { autoPlaceholder: 'aggressive', initialCountry: 'ru', preferredCountries: ['ru', 'kz', 'by', 'ge', 'us'], utilsScript: 'https://cdn.jsdelivr.net/npm/intl-tel-input@17/build/js/utils.js', }); }); document.addEventListener('fetchit:before', (e) => { const { form, formData, fetchit } = e.detail; // Получим ссылку на форму, экземпляры FormData и FetchIt const phoneInput = form.querySelector('[name="phone"]'); // Поищем в форме поле с телефоном if (!phoneInput) return; // Если не нашли, то прерываем работу обработчика const iti = window.intlTelInputGlobals.getInstance(phoneInput); // Получаем экземпляр поля intlTelInput if (iti.isValidNumber()) { // Проверяем поле на валидность и если валидно... formData.set('phone', iti.getNumber()); // Приводим введенное пользователем значение в нормальный формат return; // И прерываем работу обработчика } // Иначе fetchit.setError('phone', 'Введите пожалуйста корректный номер телефона'); // Выводим сообщение об ошибке e.preventDefault(); // Прерываем отправку формы }); </script>
Готово! Теперь при попытке ввести невалидный номер телефона, пользователь увидит сообщение об ошибке.
А в случае ввода валидного номера в любом формате, вы получите единый.
Информация
Все данные и совпадения случайные.
В заключении надо проговорить пару вещей:
- Любая валидация на стороне клиента небезопасна и нужна только для удобства пользователя. Вы можете также улучшить её на стороне сервера с помощью FormIt.
- Для простоты примера здесь используется CDN и естественно для продакшена нужно подключать библиотеку другими способами.
Всем спасибо за внимание.