[AjaxFormitLogin] AjaxForm на стрероидах и чистом JS

Приветствую, коллеги. Всех с наступающим! В честь этого праздника, я решил сделать сообществу подарок и собрал некоторые свои наработки в пакет. Надеюсь в новом году он кому-нибудь облегчит жизнь. Примеры вызовов можно найти в папке компонента в подпапке templates.

Отличия от AjaxForm:
1. Нет jQuery.
2. Для показа уведомлений используется библиотека IziToast.
3. Принимает параметр clearFieldsOnSuccess, тем самым позволяя управлять очисткой полей при успешной оправке формы.
4. Принимает параметр transmittedParams (значение должно быть валидным JSON), который позволяете передавать в JS кастомные параметры отдельно при успешной, отдельно при неудачной отправке.
5. Позволяет отображать процесс загрузки файлов на сервер, для этого нужно указать параметр showUploadProgress со значением 1.
6. Событие af_complete заменено на afl_complete.
document.addEventListener('afl_complete', e => {
    console.log(e.detail.response); // ответ сервера
    console.log(e.detail.form); // текущая форма
});
7. Изменен формат ответа сервера.
8. Работают редиректы. Для этого необходимо указать параметр redirectTo (абсолютная ссылка или ID ресурса) и, при необходимости изменить стандартное значение в 2с, redirectTimeout (в милисекундах) для задания задержки перед переходом на другую страницу.
9. Добавлен метод помогающий валидировать чекбоксы. Для его работы необходимо проверяемому чекбоксу добавить атрибут data-afl-required, где значением будет ключ указанный в параметре validate, а также нужно добавить скрытое поле с этим именем в форму. Самому чекбоксу имя можно не указывать.
10. Нет поддержки капчи от гугла, но встроена собственная защита от спама по методу Алексея Смирнова. Для активации нужно в вызове указать параметр spamProtection со значением 1.
11. Есть возможность регистрации, авторизации, сброса пароля и редактирования личных данных, при условии установки компонента FormIt.Подробнее о поддерживаемых параметрах можно прочитать в этой заметке
12. При обновлении данных пользователя добавлено системное событие aiOnUserUpdate, которое получает следующие данные $user — объект обновленного пользователя, $profile — его профиль, $data — переданные данные.

Благодарности:
1. @Василий Наумкин за AjaxForm, modExtra и вообще всю базу, которая легла в основу этого компонента.
2. @Баха Волков за код-ревью и подсказки по JS.
3. @Дима Сайт old см. профиль за подсказку по неймингу.
4. @Алексей Смирнов за метод защиты от спама.

Скачать пакет пока можно только с GitHub, как появится в modstore выложу ссылку сюда.
Артур Шевченко
30 декабря 2022, 20:27
modx.pro
2
1 817
+12
Поблагодарить автора Отправить деньги

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

Сергей Шлоков
31 декабря 2022, 08:05
0
AjaxForm на стреройдах
Наверно, имелось ввиду «на стероидах»?
R2m0x94 (Vasily)
31 декабря 2022, 15:27
0
Запиши видео в upd поста или полный обзор, хоть посмотреть как работает. Похвастайся :)
    Константин Ильин
    07 января 2023, 14:40
    0
    Артур подскажи пожалуйста, для этого вызова(из примеров взял) получается надо написать плагин который будет сохранять или хук?
    Просто при таком вызове, редактирую fullname сохранить нажал, «Данные сохранены», но они не записались
    {'!AjaxFormitLogin' | snippet : [
            'form' =>  'updateProfileForm',
            'snippet' => 'FormIt',
            'hooks' => 'AjaxIdentification',
            'method' => 'update',
            'successMessage' => 'Данные сохранены.',
            'clearFieldsOnSuccess' => 0,
    
            'validate' => 'email:required:email',
            'validationErrorMessage' => 'Исправьте, пожалуйста, ошибки!',
            'email.vTextRequired' => 'Укажите email.'
            ]}
      Артур Шевченко
      09 января 2023, 11:40
      0
      Дополнительно ничего писать не нужно, всё есть в комплекте. Я проверил на чистом сайте, у меня всё из коробки завелось без проблем. Посмотрите может есть какие-то ошибки? Попробуйте установить версию 1.0.2.(доступна по ссылке в конце поста)
        Константин Ильин
        09 января 2023, 13:02
        0
        Хм, походу я криворучко или праздники сказываются, даже авторизация не работает.
        Модхост сейчас поднял
        s31388.h10.modhost.pro/

        Простой пользователь
        test@test.ru
        123123123

        админский
        s31388.h10.modhost.pro/manager/
        s31388
        AzbNTYpnYF11
          Артур Шевченко
          09 января 2023, 13:18
          +1
          Ну так-то, конечно не работает))) Ты указал, что имя пользователя надо брать из поля email, а имя пользователя у тебя не равно email.
            Константин Ильин
            09 января 2023, 13:32
            0
            а, да) Я просто копирнул из примеров) Вызывал сомнение этот параметр, но не трогал)
            Сейчас все норм и сохраняется, а сохраняется наверно потому что, я до этого под админом открывал сайт, т.е. не был авторизирован в контексте)
            Спасибо Артур! буду дальше тестировать :)
      Сергей Карпович
      03 февраля 2023, 17:34
      0
      Подскажите как правильно прописать проверку заполненности множественного поля checkbox?
      Вот мое поле из формы:
      <div class="pole pole-checkbox">
                  <div class="pole-label">Тип помещения <span class="error_floor">[[+fi.error.room]]</span></div>
                  <input type="hidden" name="room[]" value="" data-afl-required="room"/>
                  <div class="pole-checkbox-item">
                      <input id="room_1" type="radio" name="room[]" value="Квартира" [[!+fi.room:FormItIsChecked=`Квартира`]] >
                      <label for="room_1">Квартира</label>
                  </div>
                  <div class="pole-checkbox-item">
                      <input id="room_2" type="radio" name="room[]" value="Частный дом" [[!+fi.room:FormItIsChecked=`Частный дом`]]>
                      <label for="room_2">Частный дом</label>
                  </div>
                  <div class="pole-checkbox-item">
                      <input id="room_3" type="radio" name="room[]" value="Гостиница" [[!+fi.room:FormItIsChecked=`Гостиница`]]>
                      <label for="room_3">Гостиница</label>
                  </div>
                  <div class="pole-checkbox-item">
                      <input id="room_4" type="radio" name="room[]" value="Коммерческое помещение" [[!+fi.room:FormItIsChecked=`Коммерческое помещение`]]>
                      <label for="room_4">Коммерческое помещение</label>
                  </div>
              </div>
      в стиппете указываю валидатор:
      'validate' => 'room:minValue=^1^',
      'room.vTextMinValue' => 'Выберите значение.'
      На выходе валидация не работает, при отправке формы ошибки в консоли
        Сергей Карпович
        03 февраля 2023, 17:47
        0
        если использую просто room:required — то все равно ошибка в консоли вылетает, как только валидатор по этому полю убираю — форма работатет
          Артур Шевченко
          03 февраля 2023, 17:53
          0
          Предположу, что разметка неверная. Не вижу контрольного поля и атрибута data-afl-required
            Сергей Карпович
            03 февраля 2023, 17:55
            0
            присутствует:
            <input type="hidden" name="room[]" value="" data-afl-required="room"/>
              Артур Шевченко
              03 февраля 2023, 18:14
              0
              Понятно. Смысл в том, что пустое поле типа checkbox не передается на сервер, поэтому нужно создать другое поле типа hidden и проверять именно его, а чтобы значение в поле типа hidden менялось нужно в атрибуте data-afl-required указать имя проверяемого поля. Т.е. должно быть так
              <div class="pole pole-checkbox">
                          <div class="pole-label">Тип помещения <span class="error_floor">[[+fi.error.room]]</span></div>
                          <input type="hidden" name="room-control" value=""/>
                          <div class="pole-checkbox-item">
                              <input id="room_1" type="radio" name="room[]" value="Квартира" data-afl-required="oom-control" [[!+fi.room:FormItIsChecked=`Квартира`]] >
                              <label for="room_1">Квартира</label>
                          </div>
                          <div class="pole-checkbox-item">
                              <input id="room_2" type="radio" name="room[]" value="Частный дом" data-afl-required="oom-control" [[!+fi.room:FormItIsChecked=`Частный дом`]]>
                              <label for="room_2">Частный дом</label>
                          </div>
                          <div class="pole-checkbox-item">
                              <input id="room_3" type="radio" name="room[]" value="Гостиница" data-afl-required="oom-control" [[!+fi.room:FormItIsChecked=`Гостиница`]]>
                              <label for="room_3">Гостиница</label>
                          </div>
                          <div class="pole-checkbox-item">
                              <input id="room_4" type="radio" name="room[]" value="Коммерческое помещение" data-afl-required="oom-control" [[!+fi.room:FormItIsChecked=`Коммерческое помещение`]]>
                              <label for="room_4">Коммерческое помещение</label>
                          </div>
                      </div>
              А в вызове
              'validate' => 'room-control:minValue=^1^',
              'room-control.vTextMinValue' => 'Выберите значение.'
      Maks
      04 февраля 2023, 10:49
      0
      Подскажите как закрыть окно после отправки формы?
      Для минишоп можно как то сделать что бы сообщения подтягивались с вашего дополнения?
        Артур Шевченко
        04 февраля 2023, 12:31
        0
        Компонент генерирует JS событие afl-complete. Повесьте на него обработчик, в котором вызовите метод закрытия окна.
        Для минишоп можно как то сделать что бы сообщения подтягивались с вашего дополнения?
        Не совсем понятно, что имеется в виду, но предположим, что речь об уведомлениях. Если вы используете новый комплект JS скриптов, то там точно такие же уведомления. Если используете стандартный, то нужно переопределить объект Message.
        import AflIziToast from './../components/ajaxformitlogin/js/modules/aflizitoast.class.js';
        
        document.addEventListener('DOMContentLoaded', (e) => {
            if (typeof miniShop2 !== 'undefined') {
                miniShop2.Message = new AflIziToast({
                    "jsPath": "assets/components/ajaxformitlogin/js/lib/izitoast/iziToast.min.js",
                    "cssPath": "assets/components/ajaxformitlogin/css/lib/izitoast/iziToast.min.css",
                    "handlerClassName": "iziToast",
                    "handlerOptions": {
                        "timeout": 2000,
                        "position": "topCenter"
                    }
                });
            }
        });
        Если не будет работать используйте задержку через setTimeout, поскольку при импорте скрипт должен быть type=«module», а скрипты этого типа всегда грузятся асинхронно.
          Maks
          04 февраля 2023, 14:42
          0
          А куда это прописать что бы переопределить?
            Артур Шевченко
            04 февраля 2023, 15:58
            0
            Если скрипты ваши подключаются без type=«module», то в отдельный файл.
              Maks
              14 февраля 2023, 10:56
              0
              К сожалению не работает. Встроенные сообщения выводятся.
                Артур Шевченко
                14 февраля 2023, 10:59
                0
                Значит что-то не так. Смотрите ошибки в консоли, если их нет, логируйте процесс самостоятельно.
                  Maks
                  14 февраля 2023, 18:12
                  0
                  Спасибо.
        Сергей Карпович
        09 марта 2023, 12:14
        0
        Если для поля установлено несколько валидаторов, например:
        name.vTextRequired и name.vTextMinLength
        То при незаполненном поле — показывается текст ошибки и с первого валидатора со второго валидатора, хотя до него еще не дошли. Это можно поправить?
        Те. если поле не заполнено — показываем name.vTextRequired, если заполнено мало, то name.vTextMinLength
          Артур Шевченко
          09 марта 2023, 12:28
          0
          Немного не по адресу вопрос, за валидацию отвечает FormIt.
            Сергей Карпович
            09 марта 2023, 13:15
            0
            Интересно то, что если я исполню сниппет AjaxForm то такой проблемы не возникает
              Артур Шевченко
              09 марта 2023, 18:51
              0
              Скиньте вызов, я посмотрю. А вообще, AjaxForm точно так же работает, показывает все ошибки сразу.
                Сергей Карпович
                09 марта 2023, 20:54
                0
                отбой, не в то поле смотрел. от сниппета не зависит
          Сергей Карпович
          12 марта 2023, 14:54
          0
          Продолжаю разбираться с компонентом, делаю регистрацию.
          Пользователь создается, на почту приходит письмо с ссылкой для активации (ссылка ведет на главную с ключом).
          Перехожу по ней, просто открывается главная.
          Проверяю юзера в админке — он все также не активирован. Не пойму как настроить активацию
          Сниппет aflActivateUser, куда его ставить, в чем его функция?
            Артур Шевченко
            12 марта 2023, 15:44
            0
            Функция сниппета aflActivateUser в том, чтобы активировать аккаунт пользователя. Ставить его надо на ту страницу, на которую ведёт ссылка активации.
              Сергей Карпович
              12 марта 2023, 16:17
              0
              Ага, не сразу нашел этот параметр.
              Теперь ссылка открывает страницу активации, но при переходе на страницу по ссылке — страница как то рушится, половина страницы не рендерится, а в коде куча такого: «ignore_e1c06d85ae7b8b032bef47e42e4c08f9»

              Сама страница стандартная, на ней только:
              {set $user  = '!aflActivateUser' | snippet:[]}
              {if $user}
                  <h2>Уважаемый, {$user.username}, Ваш аккаунт успешно активирован!</h2>
              {/if}
              Из-за чего это может быть, а пользователь не активируется при этом
                Артур Шевченко
                12 марта 2023, 16:24
                0
                Не знаю. В таких случаях смотрят логи и потом думают, как это исправить.
                  Сергей Карпович
                  12 марта 2023, 16:25
                  0
                  В том то и дело, что там пусто (в логах MODX).
                  в консоли браузера, появляется ошибка:
                  Uncaught (in promise) Error: A listener indicated an asynchronous response by returning true, but the message channel closed before a response was received
                    Артур Шевченко
                    12 марта 2023, 16:31
                    0
                    Ну кроме логов Modx, есть ещё логи сервера.
                      Сергей Карпович
                      13 марта 2023, 12:07
                      0
                      При стандартных параметрах (только адрес отправителя меняю и ID страницы подтверждения)
                      При отправке формы регистрации:
                      все поля принимают disabled
                      magazin.ru.xsph.ru/assets/components/ajaxformitlogin/action.php 500 (Internal Server Error)

                      В логах сервера:
                      [u][13/Mar/2023:12:05:38 +0300] 0.214 0.250 500 141.8.197.42 magazin.ru POST /assets/components/ajaxformitlogin/action.php HTTP/1.0 "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36" "http://magazin.ru.xsph.ru/lk/registracziya" 282 141.8.195.33 a0454867
                        Артур Шевченко
                        13 марта 2023, 14:12
                        0
                        Это не тот лог, там ошибка должна быть.
                          Сергей Карпович
                          13 марта 2023, 14:23
                          0
                          после 30-й попытке, я все таки поймал ошибку, указывающую на обязательное поле, которого не было в форме.
                          я его добавил, письмо для подтверждения улетело. Перехожу по ссылке, а на странице подтверждения следующий прикол:
                          пол страницы отрезано, все начина с {set $user = '!aflActivateUser' | snippet:[]}
                          В консоли ошибка:
                          /lk/confirmation?lu=YWRtOTNydXNAeWFuZGV4LnJ1 500 (Internal Server Error)

                          Если этот сниппет убрать, страница загружается нормально.

                          Вобще вкурить не могу, что я делаю не правильно…
                            Артур Шевченко
                            13 марта 2023, 22:30
                            0
                            В логе ошибок сервера должна быть информация о том, где и какая ошибка.
                              Сергей Карпович
                              13 марта 2023, 22:34
                              0
                              Там то что по ошибке 500 только то, что я выше писал.
                              Вобщем решил эксперименты отложить до лучших времен, когда потребность будет, пока вернул Login в работу. С ним проблем не возникает.
                              Артур Шевченко
                              13 марта 2023, 22:36
                              0
                              Дайте доступы в админку. сюда t.me/ShevArtV мне самому интересно в чём там проблема
                              Сергей Карпович
                              13 марта 2023, 22:49
                              0
                              Ок, завтра. А то все спят
                              Артур Шевченко
                              14 марта 2023, 14:40
                              0
                              Выкатил обновление с исправлением вашей проблемы.
          Ivan
          20 мая 2023, 19:12
          0
          Есть какая-нибудь информация по срокам появления поддержки modx3? Спасибо
            Артур Шевченко
            20 мая 2023, 19:42
            0
            Надеюсь скоро. Я задумал масштабное обновление, думаю под это дело сделаю и адаптацию. Ориентировочно 1-2 месяца
            Сергей Карпович
            27 июля 2023, 15:49
            0
            Ребята, подскажите, как правильно настроить скрипт, что бы он срабатывал только при отправке определенной формы, например у которай есть data атрибу post (к примеру)

            Сейчас у меня такой скрипт, он срабатывает на все формы:
            document.addEventListener('afl_complete', e => {
                if (e.detail.response.success) {
                    тут что должно происзодить
                }
            });
            я хочу на формы регистрации и.т.п. сделать один уведомления, а на заявки другие
              Сергей Карпович
              27 июля 2023, 16:46
              0
              Не знаю правильно я сделал или нет, но работает такой способ:
              в transmittedParams в success добавил параметр post
              ниже его вызвал 'post' => 'true',

              И в самом скрипте смотрим был этот параметр вызван или нет
              document.addEventListener('afl_complete', e => {
                  if (e.detail.response.data.post) {
                      тут что должно произойти
                  }
              });
                Артур Шевченко
                27 июля 2023, 20:47
                0
                В целом, можно и так, но проще добавить id самой форме и проверять его, ведь есть e.detail.form, который содержит DOMElement формы.
                  Сергей Карпович
                  27 июля 2023, 21:01
                  0
                  Если почтовых форм несколько на странице, то придется проверять несколько ID.
                    Артур Шевченко
                    27 июля 2023, 23:03
                    0
                    Если несколько, можно добавить общий класс и проверять его.
                      Сергей Карпович
                      28 июля 2023, 08:35
                      0
                      Можешь пример подсказать?
                        Артур Шевченко
                        28 июля 2023, 10:14
                        1
                        0
                        document.addEventListener('afl_complete', e => {
                            if (e.detail.form.classList.contains('some-class-name')) {
                                тут что должно произойти
                            }
                        });
                        Ну и в чанке формы
                        <form class="some-class-name">
                        ...
                        </form>
            Сергей Карпович
            05 декабря 2023, 13:49
            0
            Почему может не отрабатывать параметр spamProtection?
            В вызове он включен, но приходит спам, если посмотреть форму через FormIT, то там поле aflsecret пустое, как понимаю, форма не должна отправляться если оно пустое?
            Сергей Карпович
            08 января 2024, 10:40
            0
            Подскажите, почему можете не работать валидация select?
            форма:
            <form action="{$_modx->resource.id | url}" method="post" enctype="multipart/form-data">
                <input type="hidden" name="page" value="{$_modx->resource.pagetitle | htmlent}">
                <div class="form">
                    <div class="pole pole-icon">
                        <input type="tel" name="phone" placeholder="Ваш телефон" autocomplete="off">
                        <span class="error-info error_phone"></span>
                    </div>
                    <div class="pole pole-icon">
                        <input type="text" name="name" placeholder="Как Вас зовут?" autocomplete="off">
                        <span class="error-info error_name"></span>
                    </div>
                    <div class="pole">
                        <select name="otdel">
                            <option disabled selected value="">Выбор отдела*</option>
                            <option value="Сервис"[[!+fi.otdel:FormItIsSelected=`Сервис`]]>Сервис</option>
                            <option value="Запчасти"[[!+fi.otdel:FormItIsSelected=`Запчасти`]]>Запчасти</option>
                            <option value="Автомобили"[[!+fi.otdel:FormItIsSelected=`Автомобили`]]>Автомобили</option>
                        </select>
                       <span class="error-info error_otdel"></span>
                    </div>
                    <div class="pole pole-btn">
                        <button type="submit" class="btn bg"><span>Свяжитесь со мной</span></button>
                    </div>
                </div>
                
                <div class="police">
                «я ознакомлен(-а) и принимаю условия публичной оферты и даю согласие на обработку персональных данных на условиях»
                </div>
            </form>
            вызов:
            {'!AjaxFormitLogin' | snippet : [
                        'form' =>  'tpl_form_popup',
                        'emailTpl' => 'tpl_email',
                        'snippet' => 'FormIt',
                        'hooks' => 'FormItSaveForm,email',
                        'emailTo' => $_modx->config.email_address,
                        'emailFrom' => $_modx->config.emailsender,
                        'formName' => 'Заявка на консультацию с сайта ' ~ $_modx->config.site_name,
                        'emailSubject' => 'Заявка на консультацию с сайта ' ~ $_modx->config.site_name,
                        'successMessage' => '',
                        'clearFieldsOnSuccess' => 1,
                        'transmittedParams' => ["success" => 'ym_goal', "error" => 'aliases'],
                        'aliases' => 'phone==Телефон,name==Имя',
                        'showUploadProgress' => 1,
                        'spamProtection' => 1,
                        'ym_goal' => 'FORM_GOAL',
                        'validate' => 'otdel:required,name:required:minLength=^5^,phone:required:minLength=^18^',
                        'validationErrorMessage' => 'Исправьте, пожалуйста, ошибки!',
                        'otdel.vTextRequired' => 'Выберите отдел',
                        'name.vTextRequired' => ' ',
                        'name.vTextMinLength' => 'Слишком короткое ФИО.',
                        'phone.vTextRequired' => ' ',
                        'phone.vTextMinLength' => 'Слишком короткий телефон.',
                        'secret.vTextContains' => 'Кажется Вы робот. Если это не так, обновите страницу.',
                    ]}
              Сергей Карпович
              08 января 2024, 11:20
              0
              Временно решил проблему убрав disabled у первого option
              <option disabled selected value="">Выбор отдела*</option>
                Артур Шевченко
                08 января 2024, 16:12
                0
                Из-за атрибута disabled и не работала, ведь значение с этим атрибутом не может быть выбрано, значит всегда выбрано другое значение, значит оно всегда есть, значит валидатор всегда возвращает true.
                  Сергей Карпович
                  08 января 2024, 16:23
                  0
                  Ну с AjaxFormit как раз с disabled работало, это позволяло в select показать подсказку, которую нельзя выбрать.

                  Поэтому тут бился в стену какое то время =)
                    Артур Шевченко
                    08 января 2024, 16:32
                    0
                    Валидацией занимается FormIt, а мой компонент просто обёртка, такая же как AjaxForm, поэтому и там бы это не работало.
            Авторизуйтесь или зарегистрируйтесь, чтобы оставлять комментарии.
            61