Pull to refresh

Правильные ajax запросы в Drupal 7

Reading time 4 min
Views 41K
Многие по старинке шлют ajax запросы руками с помощью $.ajax(), в то время как в Drupal для этого есть достаточно гибкий механизм, который позволит переиспользовать готовый код из ядра, и сократить количество JS кода.
Для того что бы начать его использовать, необходимо разобраться с такими вещами как delivery callback, Drupal.ajax и JS-commands.

Delivery callback


Опишу коротко, потому что этот вопрос заслуживает отдельной темы.
В D6 ваш меню колбек должен был вернуть готовый html, который затем оборачивался в theme('page', $content), и вы получали страницу.
Что бы вернуть, например json-данные минуя page.tpl, приходилось вызывать drupal_json($result), а в конце функции колбека писать вызов exit().
В D7 ввели новое понятие — 'delivery callback'. Суть его в том, что меню колбек возвращает ответ в промежуточном виде — в виде массива в определенном формате (см. описание рендер-массивов на друпал.орг), а уже функция указанная в соответствующем элементе hook_menu как delivery callback, определит как отдать ответ клиенту — затемить как страницу или преобразовать в json.

Drupal.ajax


В Drupal существует стандартный способ отправления ajax-запросов.
Исходя из реализации, задумывался он для работы с формами (собственно в Form API он используется повсеместно), но ничего не мешает нам использовать его в любом месте.
Самый простой пример выглядит так:
var settings = {url : myUrl};
var ajax = new Drupal.ajax(false, false, settings);
ajax.eventResponse(ajax, {});

Третья строка нужна чтобы тут же послать запрос. Если ее пропустить, то запрос будет послан в момент, когда произойдет JS-событие settings.event (по умолчанию 'mousedown') над DOM/jQuery элементом переданным в качестве второго аргумента Drupal.ajax().
В нашем случае я просто хочу тут же послать запрос, поэтому все это пропущено.
Следует отметить, что Drupal.ajax объявлен в файле misc/ajax.js, так что не забудьте его подключить.

Если нужно как-то повлиять на поведение обработки запроса, необходимо просто отнаследоваться от Drupal.ajax, переопределить нужный метод (например success callback), а затем создавать объект уже своего класса.
Есть еще один способ повлиять на обработчик какого-то события — просто переопределить функцию с тем же именем ниже по коду (в скрипте который подключен позже ajax.js), хотя я не рекомендую такой подход, иногда его все же приходится реализовывать, например что бы повлиять на все места, где уже заведомо используется new Drupal.ajax, и вам туда не подлезть (скрипт чужого модуля).
Так же отмечу, что Drupal.ajax будет слать POST, и в качестве dataType будет json. Если вам это не подходит, то надо переопределять/наследоваться.

JS-commands


JS-команды это набор JS-функций, особенных тем, что факт их вызова и аргументы могут быть определены на стороне сервера, при генерации ajax-ответа.
Сделать это можно сформировав, как возвращаемое значение меню колбека для ajax-запроса, массив вида:
function your_module_ajax_menu_callback() {
  // ...
  $result = array(
    '#type' => 'ajax',
    '#commands' => array(
      array(
        'command' => $command_name,
      ),
    ),
  );
  return $result;
}

Где $command_name — имя свойства/JS-функции из объекта Drupal.ajax.prototype.commands.
В данном случае $result — один из вариантов рендер-массива, и для того что бы он корректно преобразовался в json-данные, с нужными заголовками, необходимо элементу hook_menu, соответствующему этому колбеку, поставить в качестве 'delivery callback' — 'ajax_deliver':
function your_module_menu() {
  // ...
  $items['ajax/your-module/path'] = array(
    'title' => 'Get content by AJAX',
    'page callback' => 'your_module_ajax_menu_callback',
    'page arguments' => array(),
    'access arguments' => array('access content'),
    'type' => MENU_CALLBACK,
    'delivery callback' => 'ajax_deliver',
  );
  return $items;
}

Ответ сформировали, но кто же вызовет наши команды? И тут на помощь приходит третий компонент — Drupal.ajax.
Отсылка ajax-запроса с его помощью означает что и обработчик success события уже объявлен, он переберет все установленные команды из ответа, и вызовет соответствующие функции.

Рассмотрим пример — получение контента с помощью ajax-запроса и вставка в какой-то контейнер на странице при клике на элемент.
Обычно мы бы написали:
$('#somen-link').click(function () {
  $.ajax({
    type: 'GET',
    url: myUrl,
    dataType: 'html',
    success: function (data) {
      // Set up new content.
      $('div.container').html(data);
    }
  });
});

С помощью нашего подхода, мы заменим это так:
var ajax = new Drupal.ajax(false, '#somen-link', {url : myUrl});
ajax.eventResponse(ajax, {});

И такой код в меню колбеке (не забываем про ajax_deliver в качестве delivery callback):
$result = array('#type' => 'ajax');
$result['#commands'][] = ajax_command_insert('div.container', $html);
return $result;

Таким образом при нашем ajax запросе мы сформировали массив команд, который в ajax_deliver() будет преобразован в json, а затем все команды будут вызваны в Drupal.ajax.prototype.success как только браузер получит ответ.

Смысл функции ajax_command_insert() — просто сформировать массив с именем и параметрами JS-команды.
В данном случае для вставки контента из ключа 'data' в контейнер на странице с селектором $selector:
array(
  'command' => 'insert',
  'method' => NULL,
  'selector' => $selector,
  'data' => $html,
  'settings' => $settings,
);

В ядре определен ряд ajax_command_* функций для быстрого формирования предопределенных команд из ajax.js.

Можно свободно определять свои команды, просто добавив функцию в прототип Drupal.ajax.prototype.commands:
/**
 * Ajax delivery command to switch among tabs by ID.
 */
Drupal.ajax.prototype.commands.gotoTab = function (ajax, response, status) {
  // response.data is a value setted in 'data' key of command on PHP side.
  if (response.data) {

    // ...
  }
};

Для того что бы ее вызвать, нужно просто в качестве значения ключа 'command', установить ее имя — gotoTab:
$result['#commands'][] = array(
   'command' => 'gotoTab',
);

Все дополнительные ключи этого массива будут доступны в нашей JS-функции как response.YOUR_KEY, например response.selector в случае с командой insert. Таким образом мы можем передавать любые аргументы.

Что нам все это дает?
Во первых есть уже готовый набор команд, и мы можем вообще избежать ручной обработки success события.
Список команд предоставленных ядром можно найти в ajax.js, там где объявляется Drupal.ajax.prototype.commands.

Во вторых, мы можем единожды написать реализацию своих команд, а затем на стороне сервера управлять набором команд (или например дать кому-то изменять их через hook_alter) для каждого случая, не изменяя код скриптов.
Tags:
Hubs:
+9
Comments 3
Comments Comments 3

Articles