Частенько спрашивают, как подружить modx и ajax, как сделать форму на FormLister и как объединить это всё вместе. Я понимаю, что у гуру разработки, в изобилии присутствующих на этом сайте, должно нехило подгорать от подобных опусов, но это лично их дело. Масса народу не знает действительно простейших вещей, почему бы не дать им это в максимально разжёванном виде?
Теперь буду кидать ссылку на эту статью, да и сам копипастить с неё при случае кусочки кода.

Задача

  • Сделать форму «Перезвоните мне» во всплывающем окне.
  • Не использовать самописных сниппетов MODx
  • Форма должны быть быстрой, без всяких «отправить запрос на страницу и на js вырезать ответ».

Суть метода

Мы делаем плагин, в котором обрабатываем событие «Страница не найдена» (OnPageNotFound). В плагине вызываем через апи Modx сниппет FormLister, проверяем введённые данные, отсылаем письмо и возвращаем пользователю ответ.
Всплывающую форму будем делать через библиотеку fancyBox.

Подготовка

Скачайте fancyBox и подключите к сайту. Для этого нужны всего 2 файла из дистрибутива, это jquery.fancybox.min.js и jquery.fancybox.min.css.
Далее в тегах head пропишите пути к этим файлам. Например:
<link charset="utf-8" href="assets/templates/theme/vendors/fancybox/jquery.fancybox.min.css" rel="stylesheet" type="text/css" />
<script src="assets/templates/theme/vendors/fancybox/jquery.fancybox.min.js" charset="utf-8"></script>

Разумеется, также вам потребуется jQuery. Если он не подключен, то выше всех этих скриптов вызовите и его. Допустим, так:
<script src="//code.jquery.com/jquery-3.2.1.min.js"></script>

Теперь необходимо установить сам FormLister. Если вы не поставили его вместе с сайтом, то установить можно автоматически, в админке «Модули» — «Extras».

Вызов FormLister.

В нужном месте страницы разместите примерно следующее:
<div class="callback-form" id="callback-form">
<div id="frmwrapper">
[!FormLister?
&formid=`form_callme`
&rules=`{"name":{"required":"Введите имя"},"phone":{"required":"Введите номер телефона","phone":"Введите номер правильно"}}`
&formTpl=`form_callme_tpl`
&messagesOuterTpl=`form_callme_outer`
&errorTpl=`form_callme_error_tpl`
&errorClass=` error`
&requiredClass=` error`
&to=`ВАША ПОЧТА`
&subject=`Запрос обратного звонка с сайта`
&reportTpl=`form_callme_report`
&successTpl=`form_callme_success`
!]
</div></div>

Сразу обращу внимание на то, что вместо чанков с шаблонами вы вполне можете вставить их содержимое сразу в сниппет, добавив в самом начале слово @CODE.
Например
&successTpl=`@CODE: <p>Спасибо за обращение</p>`

В нашем случае лучше всего воспользоваться чанками, так как код этих шаблонов нам будет нужен в дальнейшем, и незачем дублировать одну и ту же информацию в двух местах.

Содержимое чанков.

В чанке form_callme_tpl
<form  method="post" action="[~[*id*]~]#form_callme">
<input type="hidden" name="formid" value="form_callme">
<div class="row  [+name.errorClass+] [+name.requiredClass+]">
<label>Ваше имя</label>
<input type="text" class="" placeholder="Как вас зовут?" name="name" value="[+name.value+]">
<span class="error">[+name.error+]</span>
</div>
<div class="row  [+phone.errorClass+] [+phone.requiredClass+]">
<label>Ваш телефон</label>
<input type="text" class=" [+phone.errorClass+] [+phone.requiredClass+]" placeholder="Ваш номер телефона" name="phone" value="[+phone.value+]">
<span class="error">[+phone.error+]</span>
</div>
<button type="submit" class="submit">Заказать звонок</button>
[+form.messages+]
</form>

В чанки form_callme_outer и form_callme_error_tpl я вставил [+messages+] и [+message+] соответственно, т.к. мне не требовалось какое-то сильно навороченное сообщение об ошибках.
В form_callme_success сообщение об удачной отправке
<div class="alert-success">
<h3>Спасибо!</h3>
<p>Наш менеджер свяжется с вами в ближайшее время.</p>
</div>

Ну и form_callme_report это шаблон письма на почту
Письмо с сайта
Имя: [+name.value+]
Телефон: [+phone.value+]

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

Делаем аякс-отправку.

Для того, чтобы содержимое формы отправлялось без перезагрузки, нам нужно немного js. Пишем похожий на этот скрипт:
$(document).on('submit','#frmwrapper form',function(ev){
var frm = $('#frmwrapper form');
$.ajax({
    type: 'post',
    url: '/form_callme',
    data: frm.serialize(),
    success: function (data) {
	 $('#frmwrapper form').remove();
        	 $('#frmwrapper').html( data );
    }
});
ev.preventDefault();
})


При загрузке страницы скрипт ищет форму, заключённую в элемент с id=frmwrapper и заменяет событие отправки своей функцией. А именно: при отправке содержимое формы сериализуется и аяксом отправляется на урл form_callme. При ответе содержимое элемента c id=frmwrapper меняется на содержимое ответа.
Обратите внимание. frmwrapper — это id элемента-обёртки для формы. Выше, в вызове FormLister мы как раз его присвоили слою.

Форма

Теперь нам нужно сделать форму всплывающей. Для начала скроем слой с формой.
.callback-form {display: none;}

Теперь зададим кнопку, которая будет при клике открывать форму.
<a class="call" data-fancybox="" data-src="#callback-form" href="#">Заказать звонок</a>

Как видите, ссылка имеет необычные атрибуты data-fancybox="" data-src="#callback-form". Это как раз вызов модальной формы, расположенной в элементе с id=callback-form. Этот id мы также присвоили чуть выше, в коде вызова FormLister. Он может быть произвольным и при этом не должен повторяться.
Проверьте, всплывает ли форма. Зачастую вылезают ошибки, отследить их появление можно в консоли разработчика. Самая часто встречающаяся — jquery подключен после fancybox. Разумеется, в таком случае вы получите что-то типа fancybox is not defined.

Получаем и обрабатываем ajax

Как вы помните, мы сделали на js отправку данных на урл form_callme. Такого адреса на нашем сайте нет, и именно так всё и задумано. Делаем плагин.
Назовите его как угодно, я назвал ajax. Перейдите на вкладку «Системные события» и отметьте чекбокс OnPageNotFound. Теперь содержимое:
if (empty($_SERVER['HTTP_X_REQUESTED_WITH']) || strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) != 'xmlhttprequest') {
    return;
}
switch($_GET['q']){
	case 'form_callme':
		 $result = $modx->runSnippet('FormLister', array(
		 'formid' => 'form_callme',
		 'rules' => '{"name":{"required":"Обязательно введите имя"},"phone":{"required":"Введите номер телефона","phone":"Введите номер правильно"}}',
		 'formTpl' => 'form_callme_tpl',
		 'messagesOuterTpl' => 'form_callme_outer',
		 'errorTpl' => 'form_callme_error_tpl',
		 'errorClass' => 'error',
		 'requiredClass' => 'error',
		 'to' =>'ВАШ ЕМАЙЛ',
		 'subject' => 'Запрос консультации с сайта',
		 'reportTpl' => 'form_callme_report',
		 'removeEmptyPlaceholders' =>'1',
		 'successTpl' => 'form_callme_success'
		));
		echo $modx->parseDocumentSource($result);
		die();
		break;
	default:
		 die();
	break;
}

Разберёмся. Скрипт сработает, когда кто-то запросил несуществующую страницу. Для начала скрипт проверяет, обратились ли к нему именно через ajax, или же просто кто-то открыл урл. Если всё хорошо, то получает данные GET-запроса и проверяет, искал ли кто-то урл form_callme. (В этом месте можно сделать ветвление для абсолютно разных запросов и выдавать наружу что угодно. Скажем, сделать 2-3 разных формы перезвона в зависимости от url и т. д. и т. п.
Потом через апи модх вызывается FormLister с аналогичными нашему первому вызову. Единственное, что добавилось, это параметр 'removeEmptyPlaceholders' =>'1' (необходимо для корректного отображения сообщений об ошибках)
Далее FormLister обрабатывает форму и выдаёт результат. Это могут быть как ошибки ввода так и успешная отправка формы. А мы получаем этот результат при помощи js-скрипта и показываем пользователю.
Проверяйте. Всё должно работать