Ещё парочка способов подружить ajax и FormLister в Evo 1.4.x / 3.x
Суть: делаем отправку формы, не перезагружая вообще ничего. Этот способ максимально заботливый в плане UI.
Даже файлы, которые юзер полчаса раскидывал по инпутам, останутся на месте в случае ошибки в валидации (самое бесячее, согласитесь?)
Принцип: FormLister работает в режиме api, возвращая только результаты валидации. Делает это он в формате json. Мы принимаем, разбираем ответ, ищем поля с ошибками, помечаем их. Даём юзеру ещё шанс.
В случае корректной обработки формы, FormLister добавляет в ответ чанк, сообщающий об успехе. Достаём его, вставляем на место формы. Done.
Я постарался засунуть в форму побольше инпутов — для образца и дальнейшего копипаста. Используются Bootstrap 5 и jquery, но это не принципиально. Сгодится любой способ отправить форму и получить json.
Демо на 1.4: тут
Принципиально обязательно в этом шаблоне только воткнуть jquery и файлик ajax.js. Остальное — чисто визуальщина, чтобы вы скопировали, вставили и потестили.
(из чанка award_form)
Обратите внимание на айди обёртки формы (award_form_wrapper) и айди формы (award_form). Эти переменные вы можете изменять, как вам угодно. При этом не забудьте также поменять их включения в файлах ajax.js и ajax.php ниже.
А вот дата-атрибуты типа data-field-wrapper=«name» нуждаются в вашем внимании. В значение data-field-wrapper вы должны подставить имя того поля, которое у вас будет участвовать в валидации. Именно по этому атрибуту js отработает в итоге и подсветит ошибки. При этом мультиполя с именами типа files[] ставьте просто как files, без скобок.
Никаких вызовов FormLister в самом шаблоне не происходит. А происходит только обработка формы, сборка данных и отправка всего великолепия на адрес ajax.php?q=айди_формы. В нашем случае это award_form.
В чанке successTplFormlisterJson хранится ваше «Спасибо за письмо, наши менеджеры уже...» а в reportTplFormlisterJson файл самого письма. Там просто ваши поля в формате [+name.value+] и т.д.
0. установить его из Extras
1. зайти в core/custom/composer.json
2. Найти там секцию require. Сунуть туда «pathologic/modxapi»: "*"
3. открыть консоль в папке core, и выполнить команду composer update
Создаём файл этого шаблона /views/clear_ajaxform.blade.php
Абсолютно та же самая форма.
Добавляем там вот такое
И вверху
Первая строка, скорее всего, у вас уже есть. А во второй мы подключаем свой контроллер для аякса.
Создаём контроллер AjaxController либо же используем свой, но тогда правьте роуты под свои нужды. Тут полная свобода воли, и вполне можно было обойтись без case в контроллере, просто сразу вызвав нужный метод вместо index.
Путь
И пишем, в принципе, всё то же, что и в Evo 1.4, но с поправками на развиваться-стайл.
Как вы можете заметить, есть парочка отличий.
Оба файла лежат в папке /views/parts/
Оформляйте их уже на свой вкус.
P.S.:
Если вы модифицируете скрипт и будете, скажем, создавать ресурсы, а не отправлять почту, в вашем AjaxController нужно будет заюзать соответствующий контроллер. Типа
use Pathologic\EvolutionCMS\MODxAPI\modResource;
А в вызове уже подставлятькотзнаетчто что-то типа
'controller' => 'Content'
'model' => 'Pathologic\EvolutionCMS\MODxAPI\modResource'
'userModel'=> 'Pathologic\EvolutionCMS\MODxAPI\modUsers'
Загляните в core/vendor/pathologic/modxapi/src
Даже файлы, которые юзер полчаса раскидывал по инпутам, останутся на месте в случае ошибки в валидации (самое бесячее, согласитесь?)
Принцип: FormLister работает в режиме api, возвращая только результаты валидации. Делает это он в формате json. Мы принимаем, разбираем ответ, ищем поля с ошибками, помечаем их. Даём юзеру ещё шанс.
В случае корректной обработки формы, FormLister добавляет в ответ чанк, сообщающий об успехе. Достаём его, вставляем на место формы. Done.
Я постарался засунуть в форму побольше инпутов — для образца и дальнейшего копипаста. Используются Bootstrap 5 и jquery, но это не принципиально. Сгодится любой способ отправить форму и получить json.
Демо на 1.4: тут
Evolution 1.4.x
— для олдфагов и меня.Шаблон:
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=[(modx_charset)]" />
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
<script src="https://code.jquery.com/jquery-2.2.3.min.js"></script>
</head>
<body>
<section class="main">
<div class="container">
<div class="row content">
<div class="col-sm-8">
<h1>[*pagetitle*]</h1>
{{award_form}}
</div>
<aside class="col-sm-4"></aside>
</div>
</div>
</section>
<script src="assets/templates/theme/js/ajax.js"></script>
</body>
</html>
Принципиально обязательно в этом шаблоне только воткнуть jquery и файлик ajax.js. Остальное — чисто визуальщина, чтобы вы скопировали, вставили и потестили.
Форма
(из чанка award_form)
<div id="award_form_wrapper">
<form method="post" id="award_form" enctype="multipart/form-data" class="needs-validation ">
<input type="hidden" name="formid" value="award_form">
<div class="form-group mb-3" data-field-wrapper="name">
<label for="name" class="form-label">Как вас зовут?</label>
<input class="form-control form-control-sm" id="name" name="name" type="text" >
<div class="invalid-feedback"></div>
</div>
<div class="form-group">
<label class="form-label" >Что хотите сообщить?</label>
</div>
<div class="form-group mb-3" data-field-wrapper="topic">
<div class="form-check form-check-inline">
<label class="form-check-label"><input class="form-check-input" type="radio" name="topic" value="Предложение">Есть предложение</label>
</div>
<div class="form-check form-check-inline">
<label class="form-check-label"><input class="form-check-input " type="radio" name="topic" value="Жалоба">Есть жалоба</label>
</div>
<div class="invalid-feedback"></div>
</div>
<div class="form-group mb-3" data-field-wrapper="comment">
<label for="comment" class="form-label">Текстом:</label>
<textarea class="form-control form-control-sm" id="comment" name="comment" ></textarea>
<div class="invalid-feedback"></div>
</div>
<div class="form-group mb-3" data-field-wrapper="files">
<label for="files" class="form-label">Фотки с места событий:</label>
<input class="form-control form-control-sm" type="file" class="form-control" id="files" name="files[]" multiple>
<div class="invalid-feedback"></div>
</div>
<div class="form-group mb-3" data-field-wrapper="department">
<label for="department" class="form-label" >Обслуживающий офис</label>
<select name="department" class="form-select form-select-sm">
<option value="1">ул. Толстых Партизан</option>
<option value="2">ул. Добрых Строителей</option>
<option value="3">Не помню, был пьян</option>
</select>
<div class="invalid-feedback"></div>
</div>
<div class="form-group " >
<label class="form-label" for="products">Какими услугами вы пользуетесь?</label>
</div>
<div class="form-group mb-3" data-field-wrapper="products">
<div class="form-check form-check-inline">
<label><input class="form-check-input" checked type="checkbox" name="products[]" value="1">Техподдержка</label>
</div>
<div class="form-check form-check-inline">
<label><input class="form-check-input" type="checkbox" name="products[]" value="2">Хостинг</label>
</div>
<div class="form-check form-check-inline">
<label><input class="form-check-input" type="checkbox" name="products[]" value="3">Разработка</label>
</div>
<div class="invalid-feedback"></div>
</div>
<div class="form-group mb-3" data-field-wrapper="agree">
<div class="form-check">
<label><input class="form-check-input" type="checkbox" name="agree" value="Да">Я согласен с правилами обработки обращений</label>
</div>
<div class="invalid-feedback"></div>
</div>
<div class="form-group field">
<input type="submit" value="Отправить" class="btn btn-primary">
</div>
</form>
</div>
Обратите внимание на айди обёртки формы (award_form_wrapper) и айди формы (award_form). Эти переменные вы можете изменять, как вам угодно. При этом не забудьте также поменять их включения в файлах ajax.js и ajax.php ниже.
А вот дата-атрибуты типа data-field-wrapper=«name» нуждаются в вашем внимании. В значение data-field-wrapper вы должны подставить имя того поля, которое у вас будет участвовать в валидации. Именно по этому атрибуту js отработает в итоге и подсветит ошибки. При этом мультиполя с именами типа files[] ставьте просто как files, без скобок.
Скрипт
assets/templates/theme/js/ajax.js
var ajax = {
sendForm: function(form_wrapper_id,form_id,query_key){
//про аргументы дальше
$.ajax({
type: 'post',
url: '/ajax.php?q=' + query_key,
data: new FormData($(form_id)[0]),
cache: false,
dataType: "json",
contentType: false,
processData: false,
success: function(data) {
$(form_id).find('[data-field-wrapper]').removeClass('has-error');
$(form_id).find('[data-field-wrapper]').find('.invalid-feedback').html('');
if (data.status == false) {
$.each(data.errors, function(index, item) {
var error_text = '';
var validate_errors_types = Object.keys(item);
for (var key in validate_errors_types) {
var error_text = item[validate_errors_types[key]];
}
var field_container = $(form_id).find('[data-field-wrapper="' + index + '"]');
field_container.find('.invalid-feedback').addClass('d-block');
field_container.addClass('has-error');
field_container.find('.invalid-feedback').html(error_text);
});
} else if (data.status == true) {
$(form_id).remove();
$(form_wrapper_id).html(data.output);
} else {
//console.log('С формой ваще беда');
}
},
beforeSend: function(){
//console.log('Запрос начат');
$(form_id).find('input,button,select,textarea').prop("disabled",true);
},
complete: function(data){
//console.log('Запрос закончен');
$(form_id).find('input,button,select,textarea').prop("disabled",false);
},
error: function(xhr, ajaxOptions, thrownError){
//console.log('Запрос с ошибкой');
console.log(xhr);
}
});
}
};
// Вызов функции.
// Передаём id формы, id слоя-обёртки, значение q из запроса
// Здесь ajax.php?q=award_form значит передаём award_form
$(document).ready(function(){
$(document).on('submit', '#award_form',function(e){
ajax.sendForm(
'#award_form_wrapper',
'#award_form',
'award_form',
);
e.preventDefault();
});
});
Никаких вызовов FormLister в самом шаблоне не происходит. А происходит только обработка формы, сборка данных и отправка всего великолепия на адрес ajax.php?q=айди_формы. В нашем случае это award_form.
ajax.php
А тут уже и магия внутри
<?php
define('MODX_API_MODE', true);
include_once("index.php");
$modx->db->connect();
if (empty($modx->config)) {
$modx->getSettings();
}
switch ($_REQUEST['q']) {
case 'award_form': // тот самый award_form из функции js выше
$result = $modx->runSnippet('FormLister', array(
'formid' => 'award_form', // Айди формы
'api' => 2,
'rules' => [
"name" => [
"required" => "Введите имя"
],
"comment" => [
"required" => "Текст не может быть пустым",
"minLength" => [
"params" => 10,
"message" => "Не менее 10 символов"
]
],
"products" => [
"required" => "Нужно выбрать 2 услуги",
"minCount" => [
"params" => 2,
"message" => "Минимум 2 услуги"
]
],
"department" => [
"required" => "Выберите офис"
],
"topic" => [
"required" => "Выберите тему"
],
"agree" => [
"required" => "Вы не можете отправить обращение, если не согласны с правилами"
]
],
'fileRules' => [
"files" => [
"required" => "Приложите от 2 до 5 фото",
"maxSize" => [
"params" => 2048,
"message" => "Фото не более 2 Мб"
],
"allowed" => [
"params" => [["jpg", "jpeg", "png"]],
"message" => "Только фото"
],
"maxCount" => [
"params" => 5,
"message" => "Не больше 5 фото"
],
"minCount" => [
"params" => 2,
"message" => "Не меньше 2 фото"
]
]
],
'attachments' => 'files',
'formControls' => 'agree,topic,department,products',
'to' => 'your@email.to',
'subject' => 'Заявка с сайта',
'successTpl' => 'successTplFormlisterJson',
'reportTpl' => 'reportTplFormlisterJson'
));
echo $modx->parseDocumentSource($result);
break;
}
В чанке successTplFormlisterJson хранится ваше «Спасибо за письмо, наши менеджеры уже...» а в reportTplFormlisterJson файл самого письма. Там просто ваши поля в формате [+name.value+] и т.д.
Evolution 3.x
— для развиваться ©Преамбула:
Если у вас не поставлен FormLister в Evolution 3.x., нужно:0. установить его из Extras
1. зайти в core/custom/composer.json
2. Найти там секцию require. Сунуть туда «pathologic/modxapi»: "*"
3. открыть консоль в папке core, и выполнить команду composer update
Шаблон
Создаём в админке шаблон с псевдонимом clear_ajaxformСоздаём файл этого шаблона /views/clear_ajaxform.blade.php
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=[(modx_charset)]" />
<title>{{ $documentObject['pagetitle']}}</title>
<base href="{{ $modx->getConfig('site_url') }}">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous">
<script src="https://code.jquery.com/jquery-2.2.3.min.js"></script>
</head>
<body>
<section class="main">
<div class="container">
<div class="row content">
<div class="col-sm-8">
<h1>{{ $documentObject['pagetitle']}}</h1>
@include('parts.form')
</div>
<aside class="col-sm-4">
<p>Всякое с аяксами</p>
</aside>
</div>
</div>
</section>
<script src="theme/js/ajax.js"></script>
</body>
</html>
Файл формы
Создаём файл /views/parts/form.blade.php
<div id="award_form_wrapper">
<form method="post" id="award_form" enctype="multipart/form-data" class="needs-validation ">
<input type="hidden" name="formid" value="award_form">
<div class="form-group mb-3" data-field-wrapper="name">
<label for="name" class="form-label">Как вас зовут?</label>
<input class="form-control form-control-sm" id="name" name="name" type="text" >
<div class="invalid-feedback"></div>
</div>
<div class="form-group">
<label class="form-label" >Что хотите сообщить?</label>
</div>
<div class="form-group mb-3" data-field-wrapper="topic">
<div class="form-check form-check-inline">
<label class="form-check-label"><input class="form-check-input" type="radio" name="topic" value="Предложение">Есть предложение</label>
</div>
<div class="form-check form-check-inline">
<label class="form-check-label"><input class="form-check-input " type="radio" name="topic" value="Жалоба">Есть жалоба</label>
</div>
<div class="invalid-feedback"></div>
</div>
<div class="form-group mb-3" data-field-wrapper="comment">
<label for="comment" class="form-label">Текстом:</label>
<textarea class="form-control form-control-sm" id="comment" name="comment" ></textarea>
<div class="invalid-feedback"></div>
</div>
<div class="form-group mb-3" data-field-wrapper="files">
<label for="files" class="form-label">Фотки с места событий:</label>
<input class="form-control form-control-sm" type="file" class="form-control" id="files" name="files[]" multiple>
<div class="invalid-feedback"></div>
</div>
<div class="form-group mb-3" data-field-wrapper="department">
<label for="department" class="form-label" >Обслуживающий офис</label>
<select name="department" class="form-select form-select-sm">
<option value="1">ул. Толстых Партизан</option>
<option value="2">ул. Добрых Строителей</option>
<option value="3">Не помню, был пьян</option>
</select>
<div class="invalid-feedback"></div>
</div>
<div class="form-group " >
<label class="form-label" for="products">Какими услугами вы пользуетесь?</label>
</div>
<div class="form-group mb-3" data-field-wrapper="products">
<div class="form-check form-check-inline">
<label><input class="form-check-input" checked type="checkbox" name="products[]" value="1">Техподдержка</label>
</div>
<div class="form-check form-check-inline">
<label><input class="form-check-input" type="checkbox" name="products[]" value="2">Хостинг</label>
</div>
<div class="form-check form-check-inline">
<label><input class="form-check-input" type="checkbox" name="products[]" value="3">Разработка</label>
</div>
<div class="invalid-feedback"></div>
</div>
<div class="form-group mb-3" data-field-wrapper="agree">
<div class="form-check">
<label><input class="form-check-input" type="checkbox" name="agree" value="Да">Я согласен с правилами обработки обращений</label>
</div>
<div class="invalid-feedback"></div>
</div>
<div class="form-group field">
<input type="submit" value="Отправить" class="btn btn-primary">
</div>
</form>
</div>
Абсолютно та же самая форма.
ajax.js
Такой же, как и для 1.4. Разве что я размещаю это в папке /theme/js/ajax.jsRoute
Лезем в роуты core/custom/routes.phpДобавляем там вот такое
Route::post('/ajax/{action}', [AjaxController::class, 'index']);
И вверху
use Illuminate\Support\Facades\Route;
use EvolutionCMS\Main\Controllers\AjaxController;
Первая строка, скорее всего, у вас уже есть. А во второй мы подключаем свой контроллер для аякса.
Создаём контроллер AjaxController либо же используем свой, но тогда правьте роуты под свои нужды. Тут полная свобода воли, и вполне можно было обойтись без case в контроллере, просто сразу вызвав нужный метод вместо index.
Путь
/core/custom/packages/main/src/Controllers/AjaxController.php
И пишем, в принципе, всё то же, что и в Evo 1.4, но с поправками на развиваться-стайл.
<?php
namespace EvolutionCMS\Main\Controllers;
class AjaxController extends BaseController
{
public function index($action){
switch ($action) {
case 'award_form':
$result = $this->evo->runSnippet('FormLister', array(
'formid' => 'award_form',
'api' => 2,
'rules' => [
"name" => [
"required" => "Введите имя"
],
"comment" => [
"required" => "Текст не может быть пустым",
"minLength" => [
"params" => 10,
"message" => "Не менее 10 символов"
]
],
"products" => [
"required" => "Нужно выбрать 2 услуги",
"minCount" => [
"params" => 2,
"message" => "Минимум 2 услуги"
]
],
"department" => [
"required" => "Выберите офис"
],
"topic" => [
"required" => "Выберите тему"
],
"agree" => [
"required" => "Вы не можете отправить обращение, если не согласны с правилами"
]
],
'fileRules' => [
"files" => [
"required" => "Приложите от 2 до 5 фото",
"maxSize" => [
"params" => 2048,
"message" => "Фото не более 2 Мб"
],
"allowed" => [
"params" => [ ["jpg","jpeg","png"] ],
"message" => "Только фото"
],
"maxCount" => [
"params" => 5,
"message" => "Не больше 5 фото"
],
"minCount" => [
"params" => 2,
"message" => "Не меньше 2 фото"
]
]
],
'attachments' => 'files',
'formControls' => 'agree,topic,department,products',
'to' => 'email@email.to',
'reportTpl' => '@B_FILE: parts/form_report',
'subject' => 'Заявка с сайта',
'successTpl' => '@B_FILE: parts/form_thanks',
));
return $result;
break;
default:
break;
}
}
}
Как вы можете заметить, есть парочка отличий.
@B_FILE: parts/form_report - файл письма
@B_FILE: parts/form_thanks - файл "спасиба".
Оба файла лежат в папке /views/parts/
Оформляйте их уже на свой вкус.
P.S.:
Если вы модифицируете скрипт и будете, скажем, создавать ресурсы, а не отправлять почту, в вашем AjaxController нужно будет заюзать соответствующий контроллер. Типа
use Pathologic\EvolutionCMS\MODxAPI\modResource;
А в вызове уже подставлять
'controller' => 'Content'
'model' => 'Pathologic\EvolutionCMS\MODxAPI\modResource'
'userModel'=> 'Pathologic\EvolutionCMS\MODxAPI\modUsers'
Загляните в core/vendor/pathologic/modxapi/src
1 комментарий
решил написать тут так как проблема связанно как раз с отправкой формы аяксом.
У меня есть старый проект на 1.4.x где форма работает через плагин и все работает нормально. Новый проект решил постаивть на Evolution 3.x и использовать старых подход через плагин evoAjax сделал по аналогии, но не использовал jQuery.ajax() а использовал fetch().
И никак не могу заставить заработать.
Ошибка в консоли:
JS:
Шаблон формы:
evoAjax плагин (событие на OnPageNotFound)
Подскажите в чем может быть проблема? Такое ощущение что плагин вообще не срабатывет.