[FetchIt] - Дополнительная валидация с помощью библиотеки yup

В данной заметке я расскажу как подружить компонент FetchIt с популярной библиотекой yup и реализовать дополнительную валидацию на стороне клиента.
Допустим, что нам необходимо обработать несложную форму с двумя полями, имя и возраст. И логика будет заключаться в том, если пользователь указывает свой возраст и он оказывается ниже 18-ти то мы не дадим отправить форму и покажем сообщение.




Подключение библиотеки


У нас есть возможность импортировать библиотеку из CDN. Но помните, в продакшене такой способ лучше не использовать.

<script type="module">
  import * as yup from 'https://cdn.jsdelivr.net/npm/yup@1/+esm';
</script>

Добавление обработчика


1. Добавим обработчик на событие fetchit:before.

<script type="module">
  import * as yup from 'https://cdn.jsdelivr.net/npm/yup@1/+esm';
 
  document.addEventListener('fetchit:before', (e) => {
 
  });
</script>

2. В данном событии мы можем получить доступ к данным формы и экземпляру класса FetchIt и именно это нам нужно сделать.

<script type="module">
  import * as yup from 'https://cdn.jsdelivr.net/npm/yup@1/+esm';
 
  document.addEventListener('fetchit:before', (e) => {
    const { formData, fetchit } = e.detail; 
  });
</script>

3. Теперь нам необходимо преобразовать formData в обычный объект.

<script type="module">
  import * as yup from 'https://cdn.jsdelivr.net/npm/yup@1/+esm';
 
  document.addEventListener('fetchit:before', (e) => {
    const { formData, fetchit } = e.detail;
    const fields = Object.fromEntries(formData.entries()); 
  });
</script>

4. Затем, напишем схему по которой будет валидироваться наша форма и сразу же укажем сообщения об ошибках.

<script type="module">
  import * as yup from 'https://cdn.jsdelivr.net/npm/yup@1/+esm';
 
  document.addEventListener('fetchit:before', (e) => {
    const { formData, fetchit } = e.detail;
    const fields = Object.fromEntries(formData.entries());
 
    const formSchema = yup.object({
      name: yup
        .string()
        .required('Введите своё имя'),
      age: yup
        .number()
        .required('Введите свой возраст')
        .min(18, 'Вам должно быть 18 лет')
        .integer()
        .typeError('Поле должно быть числом'),
    });
  });
</script>

Как вы видите, мы указали, что наша схема должна быть объектом с ключами:

  • name — значение которого должно быть строкой и оно обязательное.
  • age — значение которого должно быть числом, оно обязательное, должно быть больше 18-ти и целым.
Библиотека yup предоставляет несколько способов локализации текстов ошибок. Для примера я выбрал самый простой.

Узнать о всех возможностях можно в документации.

5. Вызовем метод валидации validateSync() нашей схемы в блоке try..catch для того чтобы мы могли отлавливать неудачные попытки.

<script type="module">
  import * as yup from 'https://cdn.jsdelivr.net/npm/yup@1/+esm';
 
  document.addEventListener('fetchit:before', (e) => {
    const { formData, fetchit } = e.detail;
    const fields = Object.fromEntries(formData.entries());
 
    const formSchema = yup.object({
      name: yup
        .string()
        .required('Введите своё имя'),
      age: yup
        .number()
        .required('Введите свой возраст')
        .min(18, 'Вам должно быть 18 лет')
        .integer()
        .typeError('Поле должно быть числом'),
    });
 
    try { 
      formSchema.validateSync(fields, { abortEarly: false }); 
    } catch (err) { 
       
    } 
  });
</script>

6. Затем обратим внимание на блок catch, т.к. там мы и будем отлавливать все ошибки валидации и с помощью метода setError() экземпляра класса FetchIt устанавливать невалидное состояние полям. Также вызовем метод preventDefault() события для того, чтобы прервать отправку формы.

<script type="module">
  import * as yup from 'https://cdn.jsdelivr.net/npm/yup@1/+esm';
 
  document.addEventListener('fetchit:before', (e) => {
    const { formData, fetchit } = e.detail;
    const fields = Object.fromEntries(formData.entries());
 
    const formSchema = yup.object({
      name: yup
        .string()
        .required('Введите своё имя'),
      age: yup
        .number()
        .required('Введите свой возраст')
        .min(18, 'Вам должно быть 18 лет')
        .integer()
        .typeError('Поле должно быть числом'),
    });
 
    try {
      formSchema.validateSync(fields, { abortEarly: false });
    } catch (err) {
      e.preventDefault(); 
 
      for (const { path, message } of err.inner) { 
        fetchit.setError(path, message); 
      } 
    }
  });
</script>

7. Также по желанию вы можете показать всплывающее сообщение об ошибке и делается это очень просто.

<script type="module">
  import * as yup from 'https://cdn.jsdelivr.net/npm/yup@1/+esm';
 
  document.addEventListener('fetchit:before', (e) => {
    const { formData, fetchit } = e.detail;
    const fields = Object.fromEntries(formData.entries());
 
    const formSchema = yup.object({
      name: yup
        .string()
        .required('Введите своё имя'),
      age: yup
        .number()
        .required('Введите свой возраст')
        .min(18, 'Вам должно быть 18 лет')
        .integer()
        .typeError('Поле должно быть числом'),
    });
 
    try {
      formSchema.validateSync(fields, { abortEarly: false });
    } catch (err) {
      e.preventDefault();
 
      for (const { path, message } of err.inner) {
        fetchit.setError(path, message);
      }
 
      FetchIt.Message.error('Исправьте ошибки в форме'); // Показать всплывающее сообщение
    }
  });
</script>

Вот и всё! После этих шагов мы получим валидацию с использованием библиотеки yup, но помните, на стороне клиента она небезопасна. И поэтому при вызове сниппета нужно воспользоваться средствами валидации FormIt или если вы используете собственный сниппет, то производить её в нём.

Пример вызова сниппета с валидацией FormIt:

[[!FetchIt?
  &form=`form.tpl`
  &hooks=`email,FormItSaveForm`
  &validate=`name:required,age:required:isNumber:minValue=^18^`
 
  // Необязательные параметры
  &snippet=`FormIt`
  &formName=`Название формы`
  &emailSubject=`Тема письма`
]]

Вы сможете сейчас скачать бесплатно компонент с modstore.pro и modx.com с последними фиксами.

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

Документация
Репозиторий на GitHub
☕ Угостить чашкой кофе
Баха Волков
21 марта 2023, 19:00
modx.pro
1
2 206
+11

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

Баха Волков
21 марта 2023, 19:30
+1
Кстати, тут меня в комментариях @Лёша спросил про возможность показа сообщения в самой форме и я показал как это можно реализовать, но дело в том, что такая возможность была в альфа версии компонента, но я потом убрал такой функционал из коробки. Если проявите интерес, то я могу вернуть его.
    Константин Ильин
    22 марта 2023, 12:52
    0
    Подскажите пожалуйста, если форма подгружается после инициализации FetchIt, например модалку(с вызовом сниппета FetchIt) подгружаю по надобности, как на форму в модалке «инициализировать» FetchIt?
      Баха Волков
      22 марта 2023, 20:12
      +1
      Если коротко, то так:

      const fetchitInstance = new FetchIt(form: HTMLFormElement, config: {
        action: string,
        assetsUrl: string,
        actionUrl: string,
        inputInvalidClass: string,
        customInvalidClass: string,
        clearFieldsOnSuccess: boolean,
        pageId: number,
      });
      
      // или
      
      FetchIt.create(config: {
        action: string,
        assetsUrl: string,
        actionUrl: string,
        inputInvalidClass: string,
        customInvalidClass: string,
        clearFieldsOnSuccess: boolean,
        pageId: number,
      });

      Но проблема в том, что конфиг генерируется во время рендера страницы. Так уж задумано. Я подумаю как можно облегчить такие задачи в будущем
        Константин Ильин
        23 марта 2023, 08:02
        0
        Спасибо, «кастомно» получилось

        if (FetchIt === undefined){
            D.querySelector('head').append('<script src="/assets/components/fetchit/js/fetchit.js" defer=""></script>');
        }
        if(!empty(FetchIt)){
            let a = D.querySelector(".modalTmp").querySelector('form').getAttribute('data-fetchit')
            FetchIt.create({
                action: a,
                assetsUrl: "/assets/components/fetchit/",
                actionUrl: "/assets/components/fetchit/action.php",
                inputInvalidClass: "is-invalid",
                customInvalidClass: "",
                clearFieldsOnSuccess: true,
                pageId: hashp,
            });
        }
          Баха Волков
          23 марта 2023, 11:18
          +1
          Пожалуйста

          1. Помни, это может поменяться, обращай внимания на обновления FetchIt
          2. Пиши так:

          let a = D.querySelector('.modalTmp form').getAttribute('data-fetchit')
          // или
          let a = D.querySelector('.modalTmp form').dataset.fetchit
            Константин Ильин
            23 марта 2023, 15:30
            +1
            буду следить (особенно жду эту фичу с дозагрузкой), понравился компонент, и сам начал пользоваться fetch в скриптах

            Js ванилу не так давно начал, поэтому такие огрехи встречаются :)
            let a = D.querySelector('.modalTmp form').getAttribute('data-fetchit')
            Александр Мельник
            23 марта 2023, 18:22
            0
            а что за объект D к которому вы обращаетесь?
            Это что какой-то поддерживаемый синоним объекта document?
              Баха Волков
              23 марта 2023, 19:33
              0
              Обыкновенная переменная со ссылкой на объект document:

              var D = document;
              let D = document;
              const D = document;

              Не знаю зачем ему она, видимо для того чтобы писать короче
      deleted
      27 марта 2023, 04:58
      0
      А рекапча поддерживается?
      Сам пользуюсь сниппетом, который аяксом обновляет чанк (на самом деле нет, это кастомный блок для zoomx, но так понятнее). И формы можно отправлять через formit, и фильтры делать. Не планируете расширять функционал, чтоб можно было чанки загружать? Название как раз к этому располагает) Мне самому не нужно, но думаю многим пригодится, пару раз видел тут, какие люди костыли городят для такого
        Баха Волков
        27 марта 2023, 09:08
        0
        А рекапча поддерживается?
        На данный момент можно использовать компонент reCaptchaV3, он отработает также как и с AjaxForm.

        Не планируете расширять функционал, чтоб можно было чанки загружать?
        Пока в планах такого нет, но всё может изменится
        elec3c
        16 апреля 2023, 23:09
        0
        @Баха Волков А с MODX 3 пока не работает?
        Авторизуйтесь или зарегистрируйтесь, чтобы оставлять комментарии.
        15