MODX. AjaxForm обработка форм с использованием Ajax

AjaxForm это надстройка для компонента FormIt, которая позволяет отправлять результаты форм используя ajax (можно использовать не только FormIt, но и другие скрипты). Для перехода с FormIt на AjaxForm достаточно изменить запись вызова 

[[!AjaxForm?
   &snippet=`FormIt` // указываем сниппет который должен вызывать AjaxForm
   &form=`tpl.form.feedback` // указываем шаблон формы
   ... // остальные строки вызова FormIt
]]

Этапы создания Ajax формы на сайте:

  1. Устанавливаем компоненты FormIt и AjaxForm.
  2. Создаем чанк с HTML формой и модификаторами.
  3. Размещаем вызов в соответствующем ресурсе или шаблоне.

Стандартная форма обратной связи

[[!AjaxForm?
   &snippet=`FormIt`
   &form=`tpl.form.feedback`
   &emailTpl=`mailtpl.feedback`
   &hooks=`email,spam,FormItSaveForm`
   &spamCheckIp=`true`
   &emailFrom=`info@domain.ru`
   &emailSubject=`Письмо с сайта domain.ru`
   &emailTo=`info@domain.ru`
   &validate=`name:required,email:email:required,message:required`
   &validationErrorMessage=`Не заполненны все необходимые поля`
   &successMessage=`Сообщение успешно отправлено`
]]

// содержимое чанка tpl.form.feedback
<form action="" method="post">
     <label for="name">Имя*</label>
     <input id="name" name="name" value="[[!+fi.name]]">

     <label for="email">Email*</label>
     <input id="email" name="email" value="[[!+fi.email]]">

     <label for="message">Сообщение*</label>
     <textarea id="message" name="message" value="[[!+fi.message]]"></textarea>
                      
     <input class="btn gradient-border" type="submit" value="Отправить сообщение">
                     
     [[!+fi.validation_error_message:!empty=`
     <div class="alert">
       <p>Пожалуйста, исправьте следующие ошибки:</p>
           <ul>
               [[!+fi.error.name:!empty=`<li><a href="notes/web/back-end/modx/formit/#name">Поле «Имя» не заполнено</a></li>`]]
               [[!+fi.error.email:!empty=`<li><a href="notes/web/back-end/modx/formit/#email">Поле «Email» не заполнено</a></li>`]]
           </ul>
     </div>`]]
</form>

// чанк mailtpl.feedback
<p>Имя: [[+name]]</p>
<p>Email: [[+email]]</p>
<p>Телефон: [[+phone]]</p>
<p>Сообщение: [[+message]]</p>

В AjaxForm есть дефолтный вид всплывающих окон об успешной отправке и ошибках, чтобы его изменить, надо указать собственные файлы в параметрах frontend_js и frontend_css.

Пример файла без jGrowl, со статичным сообщением во всплывающем окне с id massage

var AjaxForm = {

    initialize: function (afConfig) {
        if (!jQuery().ajaxForm) {
            document.write('<script src="' + afConfig['assetsUrl'] + 'js/lib/jquery.form.min.js"><\/script>');
        }

        $(document).off('submit', afConfig['formSelector']).on('submit', afConfig['formSelector'], function (e) {
            $(this).ajaxSubmit({
                dataType: 'json',
                data: {pageId: afConfig['pageId']},
                url: afConfig['actionUrl'],
                beforeSerialize: function (form) {
                    form.find(':submit').each(function () {
                        if (!form.find('input[type="hidden"][name="' + $(this).attr('name') + '"]').length) {
                            $(form).append(
                                $('<input type="hidden">').attr({
                                    name: $(this).attr('name'),
                                    value: $(this).attr('value')
                                })
                            );
                        }
                    })
                },
                beforeSubmit: function (fields, form) {
                    //noinspection JSUnresolvedVariable
                    if (typeof(afValidated) != 'undefined' && afValidated == false) {
                        return false;
                    }
                    form.find('.error').html('');
                    form.find('.error').removeClass('error');
                    form.find('input,textarea,select,button').attr('disabled', true);
                    return true;
                },
                success: function (response, status, xhr, form) {
                    form.find('input,textarea,select,button').attr('disabled', false);
                    response.form = form;
                    $(document).trigger('af_complete', response);
                    if (!response.success) {
                        if (response.data) {
                            var key, value, focused;
                            for (key in response.data) {
                                if (response.data.hasOwnProperty(key)) {
                                    if (!focused) {
                                        form.find('[name="' + key + '"]').focus();
                                        focused = true;
                                    }
                                    value = response.data[key];
                                    form.find('.error_' + key).html(value).addClass('error');
                                    form.find('[name="' + key + '"]').addClass('error');
                                }
                            }
                        }
                    }
                    else {
                        form.find('.error').removeClass('error');
                        form[0].reset();
                        //noinspection JSUnresolvedVariable
                        if (typeof(grecaptcha) != 'undefined') {
                            //noinspection JSUnresolvedVariable
                            grecaptcha.reset();
                        }
                        $('.overlay').fadeIn('fast', function () {
                            $('#message').animate({
                                'top': '50%'
                            }, 500);
                        });
                    }
                }
            });
            e.preventDefault();
            return false;
        });

        $(document).on('keypress change', '.error', function () {
            var key = $(this).attr('name');
            $(this).removeClass('error');
            $('.error_' + key).html('').removeClass('error');
        });

        $(document).on('reset', afConfig['formSelector'], function () {
            $(this).find('.error').html('');
            AjaxForm.Message.close();
        });
    }

};

Пример файла с редиректом после успешной отправки

var AjaxForm = {

    initialize: function (afConfig) {
        if (!jQuery().ajaxForm) {
            document.write('<script src="' + afConfig['assetsUrl'] + 'js/lib/jquery.form.min.js"><\/script>');
        }
        if (!jQuery().jGrowl) {
            document.write('<script src="' + afConfig['assetsUrl'] + 'js/lib/jquery.jgrowl.min.js"><\/script>');
        }

        $(document).ready(function () {
            $.jGrowl.defaults.closerTemplate = '<div>[ ' + afConfig['closeMessage'] + ' ]</div>';
        });

        $(document).off('submit', afConfig['formSelector']).on('submit', afConfig['formSelector'], function (e) {
            $(this).ajaxSubmit({
                dataType: 'json',
                data: {pageId: afConfig['pageId']},
                url: afConfig['actionUrl'],
                beforeSerialize: function (form) {
                    form.find(':submit').each(function () {
                        if (!form.find('input[type="hidden"][name="' + $(this).attr('name') + '"]').length) {
                            $(form).append(
                                $('<input type="hidden">').attr({
                                    name: $(this).attr('name'),
                                    value: $(this).attr('value')
                                })
                            );
                        }
                    })
                },
                beforeSubmit: function (fields, form) {
                    //noinspection JSUnresolvedVariable
                    if (typeof(afValidated) != 'undefined' && afValidated == false) {
                        return false;
                    }
                    form.find('.error').html('');
                    form.find('.error').removeClass('error');
                    form.find('input,textarea,select,button').attr('disabled', true);
                    return true;
                },
                success: function (response, status, xhr, form) {
                    form.find('input,textarea,select,button').attr('disabled', false);
                    response.form = form;
                    $(document).trigger('af_complete', response);
                    if (!response.success) {
                        AjaxForm.Message.error(response.message);
                        if (response.data) {
                            var key, value, focused;
                            for (key in response.data) {
                                if (response.data.hasOwnProperty(key)) {
                                    if (!focused) {
                                        form.find('[name="' + key + '"]').focus();
                                        focused = true;
                                    }
                                    value = response.data[key];
                                    form.find('.error_' + key).html(value).addClass('error');
                                    form.find('[name="' + key + '"]').addClass('error');
                                }
                            }
                        }
                    }
                    else {
                        AjaxForm.Message.success(response.message);
                        form.find('.error').removeClass('error');
                        form[0].reset();
                        //noinspection JSUnresolvedVariable
                        if (typeof(grecaptcha) != 'undefined') {
                            //noinspection JSUnresolvedVariable
                            grecaptcha.reset();
                        }
						//редиректим на страницу 
						document.location.href = "http://адрес/для/редиректа"
                    }
                }
            });
            e.preventDefault();
            return false;
        });

        $(document).on('keypress change', '.error', function () {
            var key = $(this).attr('name');
            $(this).removeClass('error');
            $('.error_' + key).html('').removeClass('error');
        });

        $(document).on('reset', afConfig['formSelector'], function () {
            $(this).find('.error').html('');
            AjaxForm.Message.close();
        });

    }

};


//noinspection JSUnusedGlobalSymbols
AjaxForm.Message = {
    success: function (message, sticky) {
        if (message) {
            if (!sticky) {
                sticky = false;
            }
            $.jGrowl(message, {theme: 'af-message-success', sticky: sticky});
        }
    },
    error: function (message, sticky) {
        if (message) {
            if (!sticky) {
                sticky = false;
            }
            $.jGrowl(message, {theme: 'af-message-error', sticky: sticky});
        }
    },
    info: function (message, sticky) {
        if (message) {
            if (!sticky) {
                sticky = false;
            }
            $.jGrowl(message, {theme: 'af-message-info', sticky: sticky});
        }
    },
    close: function () {
        $.jGrowl('close');
    },
};

Настройка целей для счетчиков

Отслеживать успешную отправку формы, без изменения кода компонента можно добавив перехватчик в сообщение об успешной отправке:

&successMessage=`Ваше письмо успешно отправлено <script>yaCounterXXXXXXXX.reachGoal('form');</script>`

Документация сниппета AjaxForm

AjaxForm ​

AjaxForm

Внимание

13 июня 2023 года команда MODX RSC прекратила поддержку AjaxForm. Он будет продолжать быть доступным на маркетплейсах modstore.pro и extras.modx.com, но мы рекомендуем использовать вместо него компонент FetchIt.

Сниппет для отправки любых форм через ajax. По умолчанию рассчитан на работу с FormIt, но можно использовать и собственный сниппет.

  • Регистрирует нужные скрипты на фронтенде: jQuery.Form и jQuery.jGrowl.
  • Сохраняет в сессию $scriptProperties при вызове сниппета.
  • Выводит указанную форму, прописывая класс ajax_form и скрытый input для получения $scriptProperties.
  • Вешает обработчик на форму, чтобы она отправлялась через ajax.
  • При отправке запускает указанный сниппет для обработки и возвращает ответ от него.
  • Выводит сообщение об успехе, или ошибки, если есть.

Параметры сниппета

Имя По умолчанию Плейсхолдеры
&form tpl.AjaxForm.example Образец чанка с формой, которую нужно обработать.
&snippet FormIt Сниппет для обработки формы.
&frontend_css [[+assetsUrl]]css/default.css Стили оформления формы и полей с ошибками
&frontend_js [[+assetsUrl]]js/default.js Javascript для отправки формы через ajax
&actionUrl [[+assetsUrl]]action.php Адрес коннектора, на который отправляется форма

Всё, что вы указываете AjaxForm, будет передано в вызываемый сниппет.

Обработка своим сниппетом

Вы можете использовать собственный сниппет, вместо FormIt, который будет делать что угодно (хоть создавать страницы на сайте). Единственное требование - он обязательно должен возвращать JSON массив с ключами:

  • status — 1 или 0, то есть успех или ошибка.
  • message — сообщение о работе сниппета, выводится если status = 0.
  • data — массив для полей с ошибками, в котором ключами является имя поля, а значением — сообщение об ошибке.

Для удобства работы в параметры сниппета передаётся переменная $AjaxForm с классом компонента, чтобы вы могли вызывать из него методы error и success при выдаче ответа.

Простейший пример своего сниппета:

php
<?php
if (empty($_POST['name'])) {
  return $AjaxForm->error('Ошибки в форме', array(
    'name' => 'Вы не заполнили имя'
  ));
}
else {
  return $AjaxForm->success('Форма прошла проверку');
}

Вызываем так:

modx
[[!AjaxForm?
  &snippet=`MySnippet`
  &form=`tpl.AjaxForm.example`
]]

Этот сниппет ничего не делает, просто возвращает результат проверки имени.

Валидация формы

Сервер может вернуть ошибку отправки формы и массив полей, не прошедших проверку. Этим полям автоматически будет добавлен CSS класс error, который убирается при последующей отправке.

Так же вы можете запретить отправку формы, используя javascript переменную afValidated - если она объявлена и равна false, то форма не будет отправлена.

Обратите внимание, что все проверки на javascript можно обойти, так что эту переменную стоит использовать только для удобства пользователей, а не для реальной проверки данных.

modx
<script type="text/javascript">
$(document).on('submit', '.ajax_form', function() {
  // Здесь любой код для проверки формы при отправке
  // Я просто печатаю её в консоли браузера
  console.log(this);

  // Результатом работы будет выставление глобальной переменной
  afValidated = false; // Или true, если валидация пройдена
});
</script>

[[!AjaxForm]]

Событие af_complete

При получении ответа от сервера вызывается событие af_complete, которое вы можете использовать для обновления содержимого страницы или другой операции javascript.

Вам просто нужно указать функцию, в которую будет передано событие javascript и объект с ответом от сервера. Обратите внимание, что внутри этого объекта есть и отправляющая форма.

js
$(document).on('af_complete', function (event, response) {
  var form = response.form;
  // Если у формы определённый id
  if (form.attr('id') === 'my_form_3') {
    // Скрываем её!
    form.hide();
  }
  // Иначе печатаем в консоль весь ответ
  else {
    console.log(response)
  }
});

redirect на другую страницу сайта, после успешной отправки формы?

Добавляем id к форме, если его нет и затем в js файл прописать вот такие строки

js
$(document).on('af_complete', function (event, response) {
  var form = response.form;
  if (form.attr('id') === 'значение id формы') {
    window.location.href = "[[~id страницы]]"
  }
});

Можно просто обернуть в <script>выше приведенный код</script> и подключить в шаблон с формой ближе к закрытию body.

Всплывающие сообщения

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

js
AjaxForm.Message.success('Зеленый popup');
AjaxForm.Message.error('Красный popup', 1);
AjaxForm.Message.info('Черный popup');

Вторым параметром можно указать «прилипающий» popup — его нужно будет закрыть вручную, бывает полезно для показа серьёзных ошибок.

Примеры ошибок

То есть, просто вызвав сниппет на странице, вы получаете подключенный jQuery.jGrowl и можете показывать приятные всплывающие уведомления на javascript.

Примеры

Отправка email сообщения при помощи FormIt с требованием некоторых полей:

modx
[[!AjaxForm?
  &snippet=`FormIt`
  &form=`tpl.AjaxForm.example`
  &hooks=`email`
  &emailSubject=`Тестовое сообщение`
  &emailTo=`info@domain.com`
  &validate=`name:required,email:required,message:required`
  &validationErrorMessage=`В форме содержатся ошибки!`
  &successMessage=`Сообщение успешно отправлено`
]]

Отладка

При возникновении любых проблем, в первую очередь проверяйте, отправляется ли форма без AjaxForm. Помните, что AjaxForm - сниппет-обёртка, он не отправляет письма и не проводит проверку формы. Это делает ваш сниппет или FormIt.

Так же не забывайте заглядывать в консоль браузера на предмет ошибок javascript. Если сервер выдаёт ошибку 500 при отправке, проверьте параметр register_globals у вашего PHP - он должен быть отключен.