Работа с AJAX
Albireo CMS предлагает сразу несколько готовых решений при работе с AJAX-запросами.
Что такое AJAX
С помощью AJAX можно управлять html-элементами через post-запросы к серверу без перезагрузки страниц. Наиболее распространено использование AJAX совместно с формами. Когда форма отправлена, то страница без перезагрузки отобразит результат отправки.
Структура AJAX
Сам по себе AJAX — это обычный post-запрос к серверу. Но вместо отправки через submit-формы, используется http-запрос через fetch(). Таким образом использование AJAX подразумевает, что
- есть html-форма, в которой хранятся данные,
- есть url, по которому форма отправляет данные,
- есть php-обработчик, который принимает данные и отдаёт результат.
Есть несколько разных вариантов организации AJAX в Albireo CMS. Ниже приведены примеры для каждого из них.
Простой POST («классика»)
Вначале рассмотрим обычный post-запрос, который реализуется в Albireo CMS.
Самый простой вариант, это использование двух страниц, одна из них будет содержать форму, а другая выступать в роли обработчика POST-отправки.
Первая страница содержит форму — это произвольная страница, которая содержит форму (произвольную), например:
<form method="POST">
<div class="mar10-b">Email</div>
<input class="w100" type="email" name="email" value="">
<div class="mar10-b mar20-t">Password</div>
<input class="w100" type="password" name="password" value="">
<div class="mar10-b mar20-t">Info</div>
<input class="w100" type="text" name="info" value="">
<div><button class="mar20-tb" type="submit">Submit</button></div>
</form>
У формы обычно есть атрибут action, где указывается адрес, на который будет отправлен запрос. Если action не указан, то запрос будет отправлен на эту же страницу. Например адрес страницы с формой «сайт/form», тогда на этот же адрес придёт post-запрос с данными формы.
Поэтому в Albireo CMS достаточно сделать новую страницу (например form_handler.php) с полями:
slug: form method: POST layout: empty.php
Таким образом Albireo CMS автоматически подключит эту страницу как только будет адрес «сайт/form», но для post-запроса. В данном примере уже сама страница будет обработчиком входящего запроса.
Этот механизм — фактически стандартный для html-форм. В отличие от AJAX, страница будет перезагружена.
Простой AJAX («классика»)
Для того, чтобы сделать «чистый» AJAX в Albireo CMS, можно модернизировать форму, добавив в неё скрытое поле _method:
<form method="POST" onSubmit="...">
<input type="hidden" name="_method" value="AJAX">
... дальше форма
Поле _method указывает, что вместо «post», будет применяться «ajax». Поэтому в странице-обработчике нужно указывать поля так:
slug: form method: AJAX layout: empty.php
Отправка формы происходит любым js-методом, например через fetch().
Это «классический» вариант AJAX, который может быть реализован на чистом JS.
Единая точка входа для AJAX
Очевидно, что две страницы — это хоть и простой подход, но не очень удобный. Как правило обработчик AJAX-запроса (также как и POST) пишется на чистом PHP и в ответ выдаёт либо готовый HTML/TEXT, либо JSON данные.
Второе неудобство это необходим контроль за адресами страницы-обработчика. Если нужно сделать несколько форм, то для каждой нужно делать отдельные обработчики в виде страниц.
Поэтому в Albireo CMS можно использовать единую точку входа для всех AJAX-запросов. В этом случае остается только решить где именно разместить обработчик AJAX. Он может быть в трех вариантах:
- это может быть обычный php-файл (не страница!),
- это может быть отдельная функция,
- это может быть класс и его метод.
AJAX с php-файлом (для форм)
Albireo CMS использует AlpineJS как основную библиотеку для работы с JavaScript. В комплекте системы уже есть модуль для работы с формами через AlpineJS. Он автоматически подключается для каждой страницы, поэтому вам не нужно об этом заботится.
В простом виде можно организовать отправку произвольной формы так:
<div x-data="{...albireoForm(), agreement: true}">
<p x-show="!result || hasErrors">Заполните поля и отправьте форму.</p>
<form method="post" x-show="!result || hasErrors" x-ref="form" @submit.prevent="submitForm('<?= REQUEST_AJAX ?>')">
<?= METHOD_AJAX ?>
<?= CSRF ?>
<?= formHandler(__DIR__ . '/form_handler.php') >
<input type="text" name="form[name]" placeholder="your name..." required>
<input type="email" name="form[email]" placeholder="your email...">
<textarea name="form[message]" required></textarea>
<button type="submit" :disabled="!agreement">Submit</button>
<label><input type="checkbox" x-model="agreement"> I agree</label>
</form>
<div x-ref="result"></div>
</div>
Здесь albireoForm() «магический» компонент AlpineJS, внутри него уже есть переменные result и hasErrors, а также метод submitForm(), который принимает единый адрес AJAX. Для этого служит php-константа REQUEST_AJAX.
Для корректной работы с единым AJAX-адресом, необходимо отправлять защитные токены и скрытое поле _method. Чтобы не вводить это вручную используются уже готовые php-константы METHOD_AJAX и CSRF.
Для того, чтобы система, принимая AJAX-запрос, понимала какой нужен обработчик (handler), используется функция formHandler(). В данном примере это обычный php-файл form_handler.php в том же каталоге, где и текущая страница.
Обратите внимание, что form_handler.php — это обычный php-файл, а не pages-страница. В нём размещается итоговый html-код, который будет выведен в блоке <div x-ref="result"></div>.
Код обработчика может быть произвольным, примерно такого вида:
if (isset($_POST['form'])) {
// работаем с данными
// например отладочный вывод данных
pr($_POST);
}
Для корректной работы, также необходимо указывать имя формы x-ref="form". После отправки форма скрывается, за это отвечают инструкции x-show="!result || hasErrors".
Такой вариант отлично подходит для отправки любых форм, поскольку submitForm() берёт на себя обслуживание не только отправки запроса, но и обработку ответа. Это особенно актуально при передаче html вместе с js-кодом.
Обработчик в виде функции
В некоторых случаях, может быть удобней использовать не отдельный php-файл, а отдельную php-функцию. В Albireo CMS для размещения своих автоматически подключаемых php-файлов следует использовать каталог service/functions. Например мы можем разместить в нём myform_handler.php. Вот тестовый пример:
function myform_handler()
{
// тестовый код для отладки JSON и TEXT
$type = $_POST['type'] ?? 'text';
if ($type == 'json') {
$response = [
'received_at' => date('H:i:s'),
'post_data' => $_POST,
];
header('Content-Type: application/json');
echo json_encode($response);
} elseif ($type == 'text') {
pr('date('H:i:s'), $_POST);
}
}
После этого в форме укажем функцию в форме:
...
<?= formHandler('myform_handler') ?>
...
Albireo CMS сама поймёт, что это именно php-функция, поскольку у неё нет окончания .php.
ВАЖНО! Обратите внимание, что имя функции, так же как и имя файла для AJAX-обработчика должен завершаться на _handler. Если его не будет, то система отклонит такой запрос.Функцию-обработчик не нужно завершать по exit(), поскольку Albireo CMS сделает это самостоятельно.Обработчик в виде php-класса
Для своих php-классов лучше использовать каталог service/psr4. Albireo CMS поддерживает автоматическое подключение файлов по стандарту PSR4, поэтому вам не нужно заботится о дополнительных действиях.
Например файл service/psr4/Demo/Ajax_demo.php:
namespace Demo;
class Ajax_demo
{
public function my_handler()
{
$type = $_POST['type'] ?? 'text';
if ($type === 'json') {
$response = [
'received_at' => date('H:i:s'),
'post_data' => $_POST,
];
header('Content-Type: application/json');
echo json_encode($response);
} elseif ($type === 'text') {
pr(date('H:i:s'), $_POST);
}
}
}
Соответственно, в форме пишем:
...
<?= formHandler('\Demo\Ajax_demo@my_handler') ?>
...
Здесь символ @ является разделителем класс/метод, а также служит указателем, что обработчиком будет именно класс/метод. Метод также должен заканчиваться на «_handler» из соображений безопасности.
Метод в классе должен быть публичным (public).
Отправка AJAX вне форм
Если нужно отправить данные без использования формы, то применяется немного другой подход. Поскольку AJAX требует отправки токенов, то вместо формы используется специально сформированный AJAX-адрес. Для этого нужна php-функция ajaxURL().
Для отправки произвольных данных через JS используется функция sendData(), которая также находится в «магии» albireoForm().
<div x-data="{ ...albireoForm(), result: '' }">
<button @click="result = await sendData('<?= ajaxURL('\Demo\Ajax_demo@my_handler') ?>', { foo: 'bar', 'type': 'text'}, 'text')">
Проверить text
</button>
<template x-if="result">
<div x-html="result"></div>
</template>
</div>
Функция sendData() первым аргументом принимает адрес, вторым — произвольные данные, которые будут доступны в обработчике как обычный $_POST, а третий аргумент — это тип ответа: 'text' или 'json'.
Для получения ответа в виде JSON можно использовать такой подход:
<div x-data="{ ...albireoForm(), result: '' }">
<button @click="result = await sendData('<?= ajaxURL('\Demo\Ajax_demo@my_handler') ?>', { foo: 'bar', 'type': 'json'}, 'json')">
Проверить json
</button>
<template x-if="result">
<pre x-html="JSON.stringify(result, null, 2)"></pre>
</template>
</div>
Обратите внимание, что наш обработчик (\Demo\Ajax_demo@my_handler) меняет тип ответа от post-ключаtype. Это просто демо-пример для отладки.
Задание аргументов для функции-обработчика
Если в качестве AJAX-обработчика выступает php-функция, то есть возможность сразу передать в неё произвольные аргументы (до 10 штук) из post-запроса.
Простой пример, демонстрирующий эту возможность:
<div x-data="{ ...albireoForm(), result: '' }">
<button @click="result = await sendData('<?= ajaxURL('ajax_demo_args_handler') ?>', { foo: 'bar', 'type': 'text', '_handler_func_arg1': 'my1', '_handler_func_arg2': 'my2', '_handler_func_arg3': 'my3' }, 'text')">
Проверить text
</button>
<template x-if="result">
<div x-html="result"></div>
</template>
</div>
Обратите внимание на ключи _handler_func_argX (где X от 1 до 10). Такие данные будут автоматически обработаны Albireo CMS и сразу переданы в исполняемую php-функцию:
function ajax_demo_args_handler($a1, $a2, $a3)
{
pr($a1, $a2, $a3);
}
В этом примере будет выведено:
my1 my2 my3
Тоже самое будет работать и с обычной формой:
<form ... >
...
<?= formHandler('ajax_demo_args_handler') ?>
...
<!-- поля произвольного типа -->
<input type="text" name="_handler_func_arg1" value="my1">
<input type="text" name="_handler_func_arg2" value="my2">
<input type="text" name="_handler_func_arg3" value="my3">
...
Ключи _handler_func_argX должны указываться последовательно, начиная с 1 (_handler_func_arg1). Если пропустить следующий номер (например, если указаны _handler_func_arg1 и _handler_func_arg3), то система оставит только аргументы до пропущенного номера (будет передано только _handler_func_arg1, поскольку пропущен _handler_func_arg2). В самой форме _handler_func_argX могут указываться в произвольном порядке, главное, чтобы они попали в post-запрос.
Отправка формы через ajaxURL()
Форму также можно отправить через ajaxURL(), а не formHandler(). В этом случае нет необходимости указывать служебные hidden-поля формы. В остальном подход тот же самый.
<div x-data="{...albireoForm()}">
<form method="post" x-ref="form" @submit.prevent="submitForm('<?= ajaxURL('form_demo2_handler') ?>')">
<div class="">
<label class="flex flex-vcenter flex-wrap">
<div class="w20 w100-tablet">Your name</div>
<input class="w80 w100-tablet form-input" type="text" name="form[name]" value="<?= htmlspecialchars(sessionOld('form-name')) ?>" placeholder="your name...">
</label>
</div>
<div class="mar20-t">
<label class="flex flex-vcenter flex-wrap">
<div class="w20 w100-tablet">Your email</div>
<div class="w80 w100-tablet">
<input class="w100 form-input" type="email" name="form[email]" value="<?= htmlspecialchars(sessionOld('form-email')) ?>" placeholder="your email...">
</div>
</label>
</div>
<div class="mar20-t">
<div class="flex flex-wrap">
<div class="w20 w100-tablet"></div>
<div class="w80 w100-tablet">
<button class="button w100-tablet mar10-b" type="submit"><i class="bi-check"></i>Submit form</button>
</div>
</div>
</div>
</form>
<div x-ref="result"></div>
</div>
function form_demo2_handler() {
pr($_POST);
}
Функция sessionOld() сохраняет предыдущее состояние в php-сессии.
Демо-примеры в комплекте Albireo CMS
В качестве примеров, обратитесь к каталогу pages/specific, где размещены не только рабочие файлы, но и несколько демо-примеров по работе с AJAX.
Валидация данных
В php-обработчике желательно проверять принимаемые данные. Для этого можно использовать функцию arrayValidate().
Где лучше разместить обработчик?
Если у вас одиночная форма, то проще всего использовать обработчик в виде php-файла, который будет располагаться рядом с файлом страницы. Таким образом, если страница поменяет адрес и расположение, то достаточно будет переместить и файл обработчика, без его изменений.
Если у вас несколько форм, можно разместить обработчики в виде php-функций в service/functions. Таким образом можно отделить исходный html-код формы от его php-обработчика.
Если вы используете AJAX не для форм, то удобней разместить обработчики также в виде php-функций. Но, если у вас несколько html-компонентов, то удобней использовать php-класс с разными методами.
Демо-пример «Счетчик на сервере»
<div x-data="{ ...albireoForm(), count: 10 }">
<p>Текущее значение: <strong x-text="count"></strong></p>
<button @click="
let res = await sendData('<?= ajaxURL('counter_handler') ?>', { current: count }, 'json');
if (res) count = res.new_count;
">
Увеличить на сервере
</button>
</div>
function counter_handler()
{
$current = (int)($_POST['current'] ?? 0);
header('Content-Type: application/json');
echo json_encode(['new_count' => $current + 1]);
}
Демо-пример «Lazy Loading с задержкой»
<div x-data="{ ...albireoForm(), loading: true }"
x-init="setTimeout(async () => {
result = await sendData('<?= ajaxURL('lazy_load_handler') ?>', {}, 'text');
loading = false;
}, 500)">
<div x-show="loading" class="t-gray500 t90 animation-bounce">Загрузка данных через полсекунды...</div>
<template x-if="result">
<div x-html="result" class="animation-fade"></div>
</template>
</div>
function lazy_load_handler() {
echo '<ul>
<li>Новость 1: Albireo CMS обновилась</li>
<li>Новость 2: AJAX стал еще проще</li>
</ul>';
}
Если вы используете AJAX-запрос сразу после того, как загрузился HTML (как в этом примере), то делайте это с небольшой задержкой, иначе Albireo CMS выдаст ошибку «No timeout. Please refresh the page (F5)!». Так работает защита от автоматических роботов.
Демо-пример «Live Search»
<div x-data="{ ...albireoForm(), query: '' }">
<input type="text"
x-model="query"
@input.debounce.500ms="result = await sendData('<?= ajaxURL('search_handler') ?>', { s: query }, 'text')"
placeholder="Начните искать...">
<div class="mar10-tb bordered pad10" x-show="result">
<div x-html="result"></div>
</div>
</div>
function search_handler() {
$s = $_POST['s'] ?? '';
if (empty($s)) exit('Введите запрос...');
// Имитация поиска по базе или файлам
$data = ['Apple', 'Banana', 'Orange', 'Cherry'];
$filtered = array_filter($data, fn($item) => str_contains(strtolower($item), strtolower($s)));
if ($filtered) {
foreach ($filtered as $item) echo "<div>Found: $item</div>";
} else {
echo "Ничего не найдено";
}
}