Разные способы создания Ajax запросов

Различные способы создания Ajax запросов

Drupal Ajax API предоставляет удобный инструмент для создания запросов, с помощью которого не нужно лезть в js код. Тем не менее знать как самим создавать ajax запросы с помощью jQuery, считаю, лишним не будет. В этой статье соберем разные способы создания ajax запросов, как друпальных, так и кастомных. Все примеры, рассмотренные в статье достаточно простые, потому как наша цель не захватить все возможные кейсы при создании запросов, а понять как они работают и создаются.

Содержание

Простой ajax запрос с помощью Drupal AJAX API

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
 * Ajax example form.
 */
function MYMODULE_form($form, &$form_state) {
  // Описание элемента формы (выпадающего списка).
  $form['node_id'] = array(
    '#type' => 'select',
    '#title' => t('List of nodes'),
    '#options' => array('-Select node-') + MYMODULE_get_node_titles(NODE_PUBLISHED, 10),
    '#ajax' => array(
      'callback' => 'MYMODULE_form_ajax_callback',
      'wrapper' => 'node-preview',
      'method' => 'replace',
      'effect' => 'fade',
    ),
  );
  // Контейнер, который будет заменен отрендеренной нодой.
  $form['preview'] = array(
    '#type' => 'container',
    '#attributes' => array('id' => 'node-preview'),
  );
 
  return $form;
}

Функция MYMODULE_get_node_titles() принимает всего два параметра: флаг (опубликована нода или нет) и количество нод для вывода. Сама функция довольна проста.

1
2
3
4
5
6
7
8
9
10
11
/**
 * Returns the array of nodes titles.
 */
function MYMODULE_get_node_titles($published, $amount) {
  return db_select('node', 'n')
    ->fields('n', array('nid', 'title'))
    ->condition('n.status', $published)
    ->range(NULL, $amount)
    ->execute()
    ->fetchAllKeyed();
}

Теперь очередь MYMODULE_form_ajax_callback().

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/**
 * Ajax callback for MYMODULE_form().
 */
function MYMODULE_form_ajax_callback($form, $form_state) {
  // Получаем засабмитанные данные.
  $values = $form_state['values'];
  // Подгружаем ноду по идентификатору.
  $node = node_load($values['node_id']);
  // Кусок возвращаемой формы. Должен содержать тот же css id,
  // для того, чтобы можно было заменять превью ноды многократно.
  $form['preview'] = array(
    '#markup' => '',
    '#prefix' => '<div id="node-preview">',
    '#suffix' => '</div>',
  );
 
  // Если объект ноды успешно загружен, то получаем рендерный
  // массив превью ноды и рендерим его.
  if ($node) {
    $view = node_view($node, 'teaser');
    $form['preview']['#markup'] = drupal_render($view);
  }
 
  return $form['preview'];
}

Результат всех манипуляций приведен на изображениях ниже. Форма до выбора конкретной ноды.
форма до аяксового события
Выбор и появление отрендеренной ноды.
форма после отработки аяксового события
Для той же задачи есть второй вариант - размещения логики подгрузки ноды не в ajax колбеке, а в самой функции формы. Для этого потребуется привести функцию MYMODULE_form() к следующему виду:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/**
 * Ajax example form.
 */
function MYMODULE_form($form, &$form_state) {
  // Описание элемента формы (выпадающего списка).
  $form['node_id'] = array(
    '#type' => 'select',
    '#title' => t('List of nodes'),
    '#options' => array('-Select node-') + MYMODULE_get_node_titles(NODE_PUBLISHED, 10),
    '#ajax' => array(
      'callback' => 'MYMODULE_form_ajax_callback',
      'wrapper' => 'node-preview',
      'method' => 'replace',
      'effect' => 'fade',
    ),
  );
 
  $form['node'] = array(
    '#type' => 'container',
    '#attributes' => array('id' => 'node-preview'),
  );
 
  if (!empty($form_state['values']['node_id'])) {
    $node = node_load($form_state['values']['node_id']);
    $view = node_view($node, 'teaser');
    $form['node']['preview'] = array(
      '#markup' => drupal_render($view),
    );
  }
 
  return $form;
}

Соответственно аякс колбек примет вид

1
2
3
4
5
6
/**
 * Ajax callback for MYMODULE_form().
 */
function MYMODULE_form_ajax_callback($form, $form_state) {
  return $form['node'];
}

Ajax запрос с помощью функций AJAX Framework

AJAX Framework имеет в своем арсенале прекрасный набор функций для различных манипуляций (замены, вставки, удаления и т.д.). Они могут быть задействованы в случаях, когда варианты с возвратом элементов формы не подходят. Возьмем следующий простой пример: необходимо по клику на кнопку заменять текст на странице. Будем использовать все то же название формы, но уже с дугой структурой.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
 * Ajax example form.
 */
function MYMODULE_form($form, &$form_state) {
  $form['label'] = array(
    '#markup' => 'Initial Label',
    '#prefix' => '<div id="my-label">',
    '#suffix' => '</div>',
  );
 
  $form['change_button'] = array(
    '#type' => 'button',
    '#value' => t('Change the label'),
    '#ajax' => array(
      'callback' => 'MYMODULE_ajax_callback',
    ),
  );
 
  return $form;
}

label before ajax
Реализация функции MYMODULE_ajax_callback().

1
2
3
4
5
6
7
/**
 * Ajax callback for MYMODULE_form().
 */
function MYMODULE_ajax_callback() {
  $commands[] = ajax_command_replace('#my-label', 'The label is changed');
  return array('#type' => 'ajax', '#commands' => $commands);
}

Результат срабатывания аякса.
label after ajax

Ajax запрос с помощью jQuery.post()

Для реализации кастомного аякс запроса необходимо объявить url, на который будет приходить запрос. Рассмотрим пример: есть текстовый список, рядом с каждой строкой будет ссылка "удалить", по клику на которую будем удалять всю строку.
В MYMODULE.module объявим в хук меню урл для запросов и урл формы со списком.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
 * Implements hook_menu().
 */
function MYMODULE_menu() {
  $items['example-form'] = array(
    'title' => 'AJAX Examples',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('MYMODULE_form'),
    'access arguments' => array('access administration pages'),
    'type' => MENU_CALLBACK,
  );
 
  $items['requests/ajax'] = array(
    'page callback' => 'MYMODULE_page_ajax_callback',
    'access arguments' => array('access administration pages'),
    'type' => MENU_CALLBACK,
  );
 
  return $items;
}

Размещаем текстовый список с линками.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/**
 * AJAX example form.
 */
function MYMODULE_form() {
  // Массив с рандомным текстом.
  $list = array(
    'Lorem ipsum dolor sit amet. Dolorum fuga id, quod maxime placeat.',
    'Laborum et aut fugit, sed ut labore.',
    'Iste natus error sit voluptatem.',
    'Ducimus, qui laborum et dolore magnam.',
    'Sint et harum quidem rerum facilis est et quas.',
  );
 
  $items = array();
  $delete = l(t('delete'), 'javascript:void(0)', array(
    'external' => TRUE,
    'attributes' => array('onclick' => 'Drupal.behaviors.MYMODULE.removeRow(this);'),
  ));
 
  // Формируем список строк с ссылками "delete".
  foreach ($list as $str) {
    $items[] = $str . ' | ' . $delete;
  }
 
  // Подключаем js файл.
  $path = drupal_get_path('module', 'MYMODULE');
  $form['#attached']['js'][] = $path . '/js/MYMODULE.js';
 
  $form['list'] = array(
    '#markup' => theme('item_list', array('items' => $items)),
  );
 
  return $form;
}

items before ajax removing
При клике по ссылке "delete" будет отправлен ajax запрос, который вернет json. Файл MYMODULE.js располагается внутри модуля MYMODULE, в папке js.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
(function($) {
  /**
   * JS functions for MYMODULE module.
   */
  Drupal.behaviors.MYMODULE= {
    removeRow: function (el) {
      var url = Drupal.settings.basePath + 'requests/ajax';
      $.post(url, {'delete' : true}, function(data) {
        if (data.error == false) {
          $(el).parent('li').fadeOut(function() {
            $(this).remove();
          });
        }
      });
    }
  };
})(jQuery);

Логика MYMODULE_page_ajax_callback() также не будет отличаться сложностью, нужно лишь вернуть массив в фомате json.

1
2
3
4
5
6
7
8
9
10
11
/**
 * Ajax callback.
 */
function MYMODULE_page_ajax_callback($form, &$form_state) {
  $error = TRUE;
  if (!empty($_POST['delete'])) {
    $error = FALSE;
  }
  drupal_json_output(array('error' => $error));
  drupal_exit();
}

После нажатия 2-ой и 3-ей ссылки "delete" получаем вот такую картину
items after ajax removing

Ajax запрос с помощью jQuery.ajax()

Более развернутый вариант может быть с использованием $.ajax. Для этого достаточно переписать функцию removeRow() в MYMODULE.js.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
(function($) {
  /**
   * JS functions for MYMODULE module.
   */
  Drupal.behaviors.MYMODULE= {
    removeRow: function(el) {
      var url = Drupal.settings.basePath + 'requests/ajax';
      $.ajax({
        type: "POST",
        url: url,
        data: {delete: true},
        success: function (data) {
          if (data.error == false) {
            $(el).parent('li').fadeOut(function() {
              $(this).remove();
            });
          }
        }
      });
    }
  };
})(jQuery);

Ajax запрос с использованием ajax_deliver

Данный вариант предполагает использование в качестве delivery callback функцию ajax_deliver(), которая является аналогом drupal_deliver_html_page(), но для аякс запросов. С помощью ajax_deliver будет доступен полный список аякс функций из фреймворка.
Будем использовать все предыдущие наработки, внося незначительные изменения. В hook_menu url и колбеки для формы MYMODULE_form() оставим такие же, как в предыдущем примере за исключением $items['requests/ajax']. В массив определения допишем одну строчку для delivery callback. В результате получим следующее:

1
2
3
4
5
6
$items['requests/ajax'] = array(
  'page callback' => 'MYMODULE_page_ajax_callback',
  'delivery callback' => 'ajax_deliver',
  'access arguments' => array('access administration pages'),
  'type' => MENU_CALLBACK,
);

Функция MYMODULE_page_ajax_callback() будет возвращать массив аяксовых команд.

1
2
3
4
5
6
7
8
9
10
/**
 * Ajax callback.
 */
function MYMODULE_page_ajax_callback() {
  $commands = array();
  if (!empty($_POST['delete'])) {
    $commands[] = ajax_command_data('', 'error', FALSE);
  }
  return array('#type' => 'ajax', '#commands' => $commands);
}

В данном случае, также можно было применить функцию ajax_command_remove(), но для этого нужно раздать селектор каждому элементу li в списке, потому как именно он и будет удален по клику. Ответ от сервера придет немного в другом виде, поэтому нужно подкорректировать MYMODULE.js.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
(function($) {
  /**
   * JS functions for MYMODULE module.
   */
  Drupal.behaviors.MYMODULE = {
    removeRow: function(el) {
      var url = Drupal.settings.basePath + 'requests/ajax';
      $.ajax({
        type: "POST",
        url: url,
        data: {delete: true},
        success: function (data) {
          if (typeof data[1] != 'undefined' && data[1].name == 'error' && data[1].value == false) {
            $(el).parent('li').fadeOut(function() {
              $(this).remove();
            });
          }
        }
      });
    }
  };
})(jQuery);

Дополнительная информация по статье

  1. https://api.drupal.org/api/drupal/includes!ajax.inc/group/ajax/7 - Drupal 7 AJAX Framework.
  2. https://api.drupal.org/api/drupal/includes!ajax.inc/group/ajax_commands/7 - Ajax framework команды.
  3. http://api.jquery.com/jquery.post/ - описание функции jQuery.post().
  4. http://api.jquery.com/jquery.ajax/ - описание функции jQuery.ajax().
  5. https://api.drupal.org/api/drupal/includes!ajax.inc/function/ajax_deliver/7 - описание ajax_deliver.

2 Комментария

Аватар пользователя Владислав Головин

Лично я использую способ,

Лично я использую способ, который подсмотрел здесь:

https://www.drupal.org/node/2192041

Нечто среднее между самописной обработкой запроса в jQuery и использованием Ajax Framework.

Смысл в том, что Ajax Framework с его освобождением от лишней работы привязан в основном к Form Api.

В этой статье показано, как можно создать любой элемент, "наделить его магией ajax", как любой select или button в форме и загружать с помощью него контент.

Плюсы (насколько я понимаю):

  1. подтянутся все Drupal.behaviors
    Хотя конечно мы и сами можем при работе через jQuery.ajax() -
    Drupal.attachBehaviors(new_content, settings);
  2. Ajax Framework делает многое за нас
  3. Гибкость и независимость от элементов формы

Я лично использую этот подход при подгрузке товаров в самописной корзине.

Аватар пользователя Kirill

Многие по старинке шлют ajax

Многие по старинке шлют ajax запросы руками с помощью $.ajax(), в то время как в Drupal для этого есть достаточно гибкий механизм, который позволит переиспользовать готовый код из ядра, и сократить количество JS кода.

Drupal.ajax

В Drupal существует стандартный способ отправления ajax-запросов.
Исходя из реализации, задумывался он для работы с формами (собственно в Form API он используется повсеместно), но ничего не мешает нам использовать его в любом месте.
Самый простой пример выглядит так:

1
2
3
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. Если вам это не подходит, то надо переопределять/наследоваться.

Более подробно можно узнать тут http://habrahabr.ru/post/164443/