Ajax

Модуль elgg/Ajax (введён в Elgg 2.1) предоставляет набор методов для общения с сервером кратким и единообразным способом, что позволяет плагинам совместно работать над данными запроса, ответом сервера и возвращаемыми клиентскими данными.

Обзор

Все методы ajax выполняют следующее:

  1. На стороне клиента опция data (если передана как объект) фильтруется хуком ajax_request_data.

  2. Запрос отправляется на сервер, либо отображая представление или форму, вызывая действие, либо загружая путь.

  3. Метод возвращает объект jqXHR, который можно использовать как Promise.

  4. Контент, эхом возвращённый сервером, преобразуется в объект

  5. Объект фильтруется событием ajax_results.

  6. Объект используется для создания HTTP-ответа.

  7. На стороне клиента данные ответа фильтруются хуком ajax_response_data.

  8. Promise jqXHR разрешается, и вызываются любые обратные вызовы success.

Дополнительные примечания:

  • Все хуки имеют тип в зависимости от метода и первого аргумента. См. ниже.

  • По умолчанию модуль elgg/spinner автоматически используется во время запросов.

  • Пользовательские сообщения, сгенерированные elgg_register_success_message() и elgg_register_error_message(), собираются и отображаются на клиенте.

  • Elgg предоставляет вам обработчик ошибок по умолчанию, который показывает общее сообщение, если вывод не удался.

  • Исключения PHP или отказанный ресурс возвращают коды ошибок HTTP, что приводит к использованию клиентского обработчика ошибок.

  • Метод HTTP по умолчанию — POST для действий, в противном случае GET. Вы можете установить его через options.method.

  • Если передан непустой options.data, метод по умолчанию всегда POST.

  • Для клиентского кэширования установите options.method в "GET" и options.data.elgg_response_ttl в желаемый max-age в секундах.

  • Чтобы сохранить системные сообщения для следующей загрузки страницы, установите options.data.elgg_fetch_messages = 0. Вы можете захотеть сделать это, если намерены перенаправить пользователя на основе ответа.

  • Чтобы остановить требование клиентским API модулей, требуемых на стороне сервера с elgg_import_esm(), установите options.data.elgg_fetch_deps = 0.

  • Все методы принимают строку запроса в первом аргументе. Это передаётся в URL fetch, но не появляется в типах хуков.

Выполнение действий

Рассмотрим это действие:

// in myplugin/actions/do_math.php

elgg_ajax_gatekeeper();

$arg1 = (int)get_input('arg1');
$arg2 = (int)get_input('arg2');

// will be rendered client-side
elgg_register_success_message('We did it!');

echo json_encode([
    'sum' => $arg1 + $arg2,
    'product' => $arg1 * $arg2,
]);

Для его выполнения используйте ajax.action('<action_name>', options):

var Ajax = require('elgg/Ajax');
var ajax = new Ajax();

ajax.action('do_math', {
    data: {
        arg1: 1,
        arg2: 2
    },
}).done(function (output, statusText, jqXHR) {
    alert(output.sum);
    alert(output.product);
});

Примечания для действий:

  • Все хуки имеют тип action:<action_name>. Таким образом, в этом случае будут запущены три хука:
    • клиентский "ajax_request_data", "action:do_math" для фильтрации данных запроса (перед отправкой)

    • серверный "ajax_results", "action:do_math" для фильтрации ответа (после выполнения действия)

    • клиентский "ajax_response_data", "action:do_math" для фильтрации данных ответа (перед получением вызывающим кодом)

  • Токены CSRF добавляются в данные запроса.

  • Метод по умолчанию — POST.

  • Абсолютный URL действия может быть указан вместо имени действия.

Примечание

При установке data используйте ajax.objectify($form) вместо $form.serialize(). Это позволяет сработать хуку плагина ajax_request_data и другим плагинам изменять/присоединяться к запросу.

Получение данных

Рассмотрим этот PHP-скрипт, который работает по адресу http://example.org/myplugin_time.

// in myplugin/elgg-plugin.php
return [
    'routes' => [
        'default:myplugin:time' => [
            'path' => '/myplugin_time',
            'resource' => 'myplugin/time',
        ],
    ],
];

// in myplugin/views/default/resources/myplugin/time.php
elgg_ajax_gatekeeper();

echo json_encode([
    'rfc2822' => date(DATE_RFC2822),
    'day' => date('l'),
]);

return true;

Для получения его вывода используйте ajax.path('<url_path>', options).

var Ajax = require('elgg/Ajax');
var ajax = new Ajax();

ajax.path('myplugin_time').done(function (output, statusText, jqXHR) {
    alert(output.rfc2822);
    alert(output.day);
});

Примечания для путей:

  • 3 хука (см. Действия выше) будут иметь тип path:<url_path>. В данном случае «path:myplugin_time».

  • Если обработчик страницы эхом возвращает обычную веб-страницу, output будет строкой, содержащей HTML.

  • Абсолютный URL может быть указан вместо имени пути.

Получение представлений

Рассмотрим это представление:

// in myplugin/views/default/myplugin/get_link.php

if (empty($vars['entity']) || !$vars['entity'] instanceof ElggObject) {
    return;
}

$object = $vars['entity'];
/* @var ElggObject $object */

echo elgg_view('output/url', [
    'text' => $object->getDisplayName(),
    'href' => $object->getUrl(),
    'is_trusted' => true,
]);

Поскольку это файл PHP, мы должны сначала зарегистрировать его для Ajax:

// in myplugin_init()
elgg_register_ajax_view('myplugin/get_link');

Для получения представления используйте ajax.view('<view_name>', options):

var Ajax = require('elgg/Ajax');
var ajax = new Ajax();

ajax.view('myplugin/get_link', {
    data: {
        guid: 123 // querystring
    },
}).done(function (output, statusText, jqXHR) {
    $('.myplugin-link').html(output);
});

Примечания для представлений:

  • 3 хука (см. Действия выше) будут иметь тип view:<view_name>. В данном случае «view:myplugin/get_link».

  • output будет строкой с отрисованным представлением.

  • Данные запроса внедряются в $vars в представлении.

  • Если данные запроса содержат guid, система устанавливает $vars['entity'] в соответствующую сущность или false, если её нельзя загрузить.

Предупреждение

В представлениях и формах ajax обратите внимание, что $vars может быть заполнено клиентским вводом. Данные фильтруются как get_input(), но могут быть не того типа, который вы ожидаете, или иметь неожиданные ключи.

Получение форм

Предположим, у нас есть представление формы. Мы регистрируем его для Ajax:

// in myplugin_init()
elgg_register_ajax_view('forms/myplugin/add');

Для получения этого используйте ajax.form('<action_name>', options).

var Ajax = require('elgg/Ajax');
var ajax = new Ajax();

ajax.form('myplugin/add').done(function (output, statusText, jqXHR) {
    $('.myplugin-form-container').html(output);
});

Примечания для форм:

  • 3 хука (см. Действия выше) будут иметь тип form:<action_name>. В данном случае «form:myplugin/add».

  • output будет строкой с отрисованным представлением.

  • Данные запроса внедряются в $vars в вашем представлении формы.

  • Если данные запроса содержат guid, система устанавливает $vars['entity'] в соответствующую сущность или false, если её нельзя загрузить.

Примечание

Только данные запроса передаются в запрашиваемое представление формы (т.е. как третий параметр, принимаемый elgg_view_form()). Если вам нужно передать атрибуты или параметры элемента формы, отображаемого представлением input/form (т.е. обычно передаваемые как второй параметр в elgg_view_form()), используйте серверное событие view_vars, input/form.

Предупреждение

В представлениях и формах ajax обратите внимание, что $vars может быть заполнено клиентским вводом. Данные фильтруются как get_input(), но могут быть не того типа, который вы ожидаете, или иметь неожиданные ключи.

Отправка форм

Для отправки формы с помощью Ajax просто передайте параметр ajax с переменными формы:

echo elgg_view_form('login', ['ajax' => true]);

Перенаправления

Используйте ajax.forward() для запуска спиннера и перенаправления пользователя на новое назначение.

var Ajax = require('elgg/Ajax');
var ajax = new Ajax();
ajax.forward('/activity');

Присоединение к запросу Ajax

Клиентский хук ajax_request_data можно использовать для добавления или фильтрации данных, отправляемых запросом elgg/Ajax.

Допустим, когда получается представление foo, мы хотим также отправить серверу некоторые данные:

// in your boot module
var Ajax = require('elgg/Ajax');
var hooks = require('elgg/hooks');

var ajax = new Ajax();

hooks.register(Ajax.REQUEST_DATA_HOOK, 'view:foo', function (name, type, params, data) {
    // send some data back
    data.bar = 1;
    return data;
});

Эти данные можно прочитать на стороне сервера через get_input('bar');.

Примечание

Если данные были переданы как строка (например, $form.serialize()), хуки запроса не запускаются.

Примечание

Форма будет объективирована как FormData, и тип содержимого запроса будет определён соответствующим образом.

Присоединение к ответу Ajax

Серверное событие ajax_results можно использовать для добавления или фильтрации данных ответа (или метаданных).

Допустим, когда получается представление foo, мы хотим также отправить клиенту дополнительные данные:

function myplugin_append_ajax(\Elgg\Event $event) {

    /* @var $data /stdClass */
    $data = $event->getValue();

    // alter the value being returned
    $data->value .= " hello";

    // send some metadata back
    $data->myplugin_alert = 'Listen to me!';

    return $data;
}

// in myplugin_init()
elgg_register_event_handler('ajax_results', 'view:foo', 'myplugin_append_ajax');

Для захвата метаданных, отправляемых обратно клиенту, мы используем клиентский хук ajax_response_data:

// in your boot module
var Ajax = require('elgg/Ajax');
var hooks = require('elgg/hooks');

hooks.register(Ajax.RESPONSE_DATA_HOOK, 'view:foo', function (name, type, params, data) {

    // the return value is data.value

    // the rest is metadata

    alert(data.myplugin_alert);

    return data;
});

Примечание

Только data.value возвращается в функцию success или доступно через интерфейс Deferred.

Примечание

Elgg использует те же хуки для доставки системных сообщений через ответы elgg/Ajax.

Обработка ошибок

Ответы в основном делятся на три категории:

  1. Успех HTTP (200) со статусом 0. На сервере не было вызовов elgg_register_error_message().

  2. Успех HTTP (200) со статусом -1. Был вызван elgg_register_error_message().

  3. Ошибка HTTP (4xx/5xx). Например, вызов действия с устаревшими токенами или исключение сервера. В этом случае обратные вызовы done не вызываются.

Первый и третий случаи являются наиболее распространёнными в системе. Используйте обратные вызовы done и fail для дифференциации поведения при успехе и ошибке.

ajax.action('entity/delete?guid=123').done(function (value, statusText, jqXHR) {
    // remove element from the page
}).fail(function() {
    // handle error condition if needed
});

Требование модулей ES

Каждый ответ от сервиса Ajax будет содержать список модулей ES, требуемых на стороне сервера с elgg_import_esm(). Когда данные ответа распаковываются, эти модули будут загружены асинхронно — плагины не должны ожидать, что эти модули будут загружены в их обработчиках $.done() и $.then(), и должны использовать import для любых модулей, от которых они зависят. Кроме того, модули не должны ожидать, что DOM был изменён запросом Ajax при их загрузке — события DOM должны быть делегированы, а манипуляции с элементами DOM должны быть отложены до разрешения всех запросов Ajax.