Пишем простым языком о сложных технических процессах

FetchIt - Форма с валидацией номера телефона и выбором страны

Решили раскрыть такой кейс, как форма с полем ввода номера телефона, ведь с такой задачей разработчик сталкивается частенько. Она далеко непростая и имеет подводные камни. Так давайте же нырнём и первое, что сделаем - это разобьём её на подзадачи.

  • Валидация: Тут пока всё понятно, нам по хорошему нужно валидировать номер телефона.
  • Форматирование: А с этим пунктом веселее, т.к. пользователи могут писать номер телефона в разных форматах: 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

Информация

Все данные и совпадения случайные.

В заключении надо проговорить пару вещей:

  • Любая валидация на стороне клиента небезопасна и нужна только для удобства пользователя. Вы можете также улучшить её на стороне сервера с помощью FormIt.
  • Для простоты примера здесь используется CDN и естественно для продакшена нужно подключать библиотеку другими способами.

Всем спасибо за внимание.