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

    Многие по старинке шлют 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) для каждого случая, не изменяя код скриптов.
    • +9
    • 33,4k
    • 3
    Поделиться публикацией
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама
    Комментарии 3
    • 0
      странная тишина в коментах.
      спасибо
      • 0
        Если в комментариях тишина, значит в статье все верно написано и без орфографических ошибок.
      • 0
        странная тишина, т.к. этот грёбаный друпал никому не нужен

        Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.