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

Привет форум!

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

  • Валидация: Тут пока всё понятно, нам по хорошему нужно валидировать номер телефона.
  • Форматирование: А с этим пунктом веселее, т.к. пользователи могут писать номер телефона в разных форматах: 8 *** *** ** **, +7 ***-***-**-** +7 (***) *******, 8 (***) *** **-** и еще 100500 вариантов.
  • Разные страны: А теперь всё серьезнее, дело в том, что разные страны это разное форматирование и соответственно разная валидация.
Вы скажете — это очень просто, человечество бороздит космос, электрифицировало города и сёла, придумало интернет и в частности Viber. И вы будете правы, хотя, если бы все люди были похожи на меня, то мы остались бы в первобытном обществе с палками и камнями… но с Viber-ом?..


Решение


— Устанавливаем FetchIt. Напоминаю, вчера я выпустил полноценную замену AjaxForm под названием FetchIt. Он также бесплатен и доступен на маркетплейсах modx.com и modstore.pro.

— Вызываем сниппет FetchIt в том месте вёрстки где находится наша форма.

[[!FetchIt?
    &form=`bootstrap.form.example`
    &snippet=`FormIt`
    &hooks=`email,FormItSaveForm`
    &formName=`Заявка на обратный звонок`
    &validate=`name:required,phone:required`
    &fieldNames=`name==Имя,phone==Контактный телефон,pageId==ID страницы`
    &emailSubject=`Заявка на обратный звонок`
    ... остальные параметры FormIt
]]

— Для простоты примера, представим, что у нас вёрстка на Bootstrap и чанк bootstrap.form.example будет выглядеть так:

<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. Сначала стили.

<!-- 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 модуля:

<script type="module">
    import intlTelInput from 'https://cdn.jsdelivr.net/npm/intl-tel-input@17/+esm'
</script>

Далее нам нужно инициализировать библиотеку для каждого поля ввода телефона, указав некоторые свойства. Со всеми её возможностями вы можете ознакомиться в репозитории библиотеки.

<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, где и будет производиться валидация.

<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 и естественно для продакшена нужно подключать библиотеку другими способами.

Всем желаю мира и спасибо за внимание.

Документация
Репозиторий на GitHub
☕ Угостить чашкой кофе
Баха Волков
03 марта 2023, 13:10
modx.pro
7
4 046
+18

Комментарии: 13

brioni
05 марта 2023, 17:29
+2
Спасибо, что поделился ?
    Сергей
    20 марта 2023, 16:19
    +1
    Добрый день!
    Решил протестировать данный компонент, но столкнулся со следующими проблемами.
    1. Пробовал разные версии jquery но постоянно одна и та-же ошибка при отправки Скрин
    2. Дополнительно подключили Notyf и на имени всплывающее уведомление работает, а на телефоне, если подключить все скрипты не работает. (видимо надо править код валидатора в этом примере)
    3. В данном решении конечно не хватает маски для телефона.

    На сколько я понимаю для данного компонента не нужен jquery, но все же с ним у него конфликт.
    В вообще было бы здорово, если бы компонент прятал саму форму из html так-как роботы зачастую уже надоели и все защиты обходят. (Наверное это больше вопрос к fiormit)

    Сама отправка формы, работает.

    Еще раз спасибо за ваш труд!
      Баха Волков
      20 марта 2023, 21:27
      0
      День добрый!

      1. У компонента нет и не может быть никаких конфликтов с jquery. Даже по stacktrace в вашем скрине видно, что ошибка не связана с fetchit. В вашем конкретном случае надо разбираться, уверен, выясниться что ошибка где-то в вашем коде.

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

      fetchit.setError('phone', 'Введите пожалуйста корректный номер телефона');
      FetchIt.Message.error('Введите пожалуйста корректный номер телефона');  // <-- ЭТУ СТРОКУ
      e.preventDefault();

      3. Не соглашусь, библиотека intl-tel-input как раз даёт возможность вводить номера телефонов в любом формате, а маска прямо противоречит этому. Вы можете использовать любое готовое решение или своё, а FetchIt без разницы что вы там сделаете, он просто собирает все данные с полей ввода формы и вскармливает их указанному сниппету.

      На сколько я понимаю для данного компонента не нужен jquery, но все же с ним у него конфликт.
      У FetchIt нет и не может быть конфликтов с jquery.

      В вообще было бы здорово, если бы компонент прятал саму форму из html так-как роботы зачастую уже надоели и все защиты обходят. (Наверное это больше вопрос к fiormit)
      Я подумаю как-нибудь о защите от спама, может что-то интересное и надумаю.

      Еще раз спасибо за ваш труд!
      Пожалуйста!
      Баха Волков
      20 марта 2023, 21:27
      0
      На данный момент есть баг связанный с вызовом сниппета на fenom, завтра выпущу патч с исправлением.
        Сергей
        21 марта 2023, 12:57
        0
        Добрый день!
        Проблему я все-же нашел, она связанна с дополнением AdminPanel (если смотреть сайт в инкогнито, то таких проблем нет)
        Сергей Лим
        04 мая 2023, 05:15
        0
        Не удалось загрузить и создать пакет с подписью: fetchit-
          Баха Волков
          04 мая 2023, 09:09
          0
          Официальный маркетплейс болеет, т.к. там ведутся работы. Не только FetchIt недоступен, но и некоторые другие. Используйте modstore.pro
          Константин
          19 сентября 2023, 14:13
          0
          Здравствуйте, пробую заменить AjaxForm на FetchIt, почему-то не срабатывает хук FormItSaveForm. Письма в админке не сохраняются. Вывожу так:
          [[!FetchIt?
            &snippet=`FormIt`
            &form=`contactFormTpl`
            &hooks=`spam,FormItSaveForm,email`
            &formName=`Рассчитать стоимость здания`
            &emailSubject=`Тема письма`
            &emailTo=`[[++emailsender]]`
            &emailFrom=`no-reply@rmse.ru`
            &validate=`nospam:blank,name:minLength=^2^,phone:phone:required`
            &validationErrorMessage=`В форме содержатся ошибки!`
            &successMessage=`Сообщение успешно отправлено`
            &phone.vTextRequired=`На какой номер телефона Вам перезвонить?`
            &name.vTextRequired=`Укажите Ваше имя`
            &fieldNames=`name==Имя,phone==Телефон,pageurl==Страница отправки,message==Сообщение,pageId==ID Страницы`
          		&validationErrorMessage=`В форме содержатся ошибки!`
          		&successMessage=`Сообщение успешно отправлено`
          ]]
            Баха Волков
            19 сентября 2023, 17:27
            +1
            Убедитесь, что хук spam отрабатывает правильно
              Константин
              19 сентября 2023, 20:51
              0
              Спасибо, дело было в нем! Подскажите пожалуйста варианты избавления от спама.
          Сергей
          07 ноября 2023, 18:01
          0
          Подскажите пожалуйста, почему не работает перевод в письме с латиницы, вроде как за это отвечает &fieldNames=`name==Имя,phone==Контактный телефон,pageId==ID страницы`
          Но мне по прежнему приходят письма без переводов.
          И как добавить свои данные например отправлять название страницы или url адрес.
          Можно ли как то использовать одну форму но передавать в ней разные тем? Например есть на страницы 3 услуги, что бы не платить форму, было бы удобно передавать в теме её название. Заранее спасибо!
            Баха Волков
            19 декабря 2023, 07:27
            0
            Подскажите пожалуйста, почему не работает перевод в письме с латиницы, вроде как за это отвечает &fieldNames=`name==Имя,phone==Контактный телефон,pageId==ID страницы`
            Но мне по прежнему приходят письма без переводов.
            Параметр fieldNames отвечает за вывод названий полей в админке на странице просмотра сохраненных форм, при использовании хука FormitSaveForm

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

            И как добавить свои данные например отправлять название страницы или url адрес.
            Либо используйте хуки FormIt, либо скрытые поля в форме. Первый вариант лучше.

            Можно ли как то использовать одну форму но передавать в ней разные тем? Например есть на страницы 3 услуги, что бы не платить форму, было бы удобно передавать в теме её название. Заранее спасибо!
            Можно, при вызове сниппета укажите разные значения параметра emailSubject или если у вас одна форма, то используйте связку js, скрытого поля subject и параметра emailUseFieldForSubject
            Авторизуйтесь или зарегистрируйтесь, чтобы оставлять комментарии.
            13