УрокПерезагрузка форм на AJAX в Drupal 7

В Drupal 7 работа с AJAX в формах (и не только) сделана на порядок мощнее, чем в 6й версии ядра. Добавились многочисленные AJAX команды, расширились возможности его интеграции с разными элементами сайта. Но сегодня я решил написать пример интеграции форм с ajax через Forms API, тем более что совсем недавно я с этим столкнулся лично.

В моём примере данные загружаются динамично, поэтому возможность перезагружать формы по частям в Drupal 7 оказалась как нельзя кстати.

Постановка задачи

Имеется форма с двумя выпадающими списками и кнопкой. Первый список содержит все типы материалов на сайте. Второй список - последние 10 материлов из выбранного типа в первом списке. При выборе другого типа материала во второй список должны загружаться ноды этого типа. При нажатии на кнопку должно загружаться полное представление материала (такое же, как на странице node/номер). Всё это должно работать без перезагрузки страницы. Рисунок для наглядности:
Перезагрузка форм на AJAX в Drupal 7

Решение

Естественно, я сразу взялся за написание модуля, который я обозвал form_ajax. Итак, по порядку:

form_ajax.info

name = Form AJAX example
description = Example of AJAX Forms API in Drupal 7
version = VERSION
core = 7.x

Здесь ничего особенного - обычное описание модуля.

form_ajax.module

<?php
 
/**
 * Implements hook_menu().
 */
function form_ajax_menu() {
  $items = array();
  $items['ajax_page'] = array(
    'title' => t('Ajax form example'),
    'page callback' => 'drupal_get_form',
    'page arguments' => array('form_ajax_form'),
    'access callback' => TRUE,
    'file' => 'form_ajax.forms.inc',
  );
  return $items;
}
 
/**
 * Get last 10 nodes from selected node type
 */
function form_ajax_get_last_nodes($type, $amount = 10) {
  // Загружаем из базы $amount последних материалов. Здесь никакой магии не происходит.
  $result = db_query_range('SELECT nid, title FROM {node} WHERE type = :type', 0, $amount, array(':type' => $type));
  $nodes = array();
  foreach ($result as $node) {
    $nodes[$node->nid] = check_plain($node->title);
  }
  return $nodes;
}

Функция form_ajax_menu() добавляет на сайт страницу ajax_page, на которой будет располагаться моя форма.
Функция form_ajax_get_last_nodes() возвращает массив из последних нод определённого типа материала. Она понадобится нам чуть дальше.

form_ajax.forms.inc

Магия находится здесь. Все комментарии непосредственно в самом коде.

<?php
 
/**
 * Form with AJAX example
 */
function form_ajax_form($form, &$form_state) {
  // Загружаем все существующие типы материала
  $options = node_type_get_names();
  // Создаём в форме выпадающий список, элементами которого являются типы материалов.
  $form['type'] = array(
    '#type' => 'select',
    '#title' => t('Node types'),
    '#options' => $options,
    // Здесь подключаем ajax. При выборе элемента из выпадающего списка данные формы
    // будут переданы в функцию form_ajax_form_load_nodes(). Отработав, функция вернёт
    // данные, которые будут помещены в <div id = "form-ajax-nodes"></div>, заменив при этом
    // все данные, которые до этого там были (чтобы не плодить большое количество данных на странице).
    // В нашем случае функция вернёт эту же форму, но с выбранными элементами.
    '#ajax' => array(
      'callback' => 'form_ajax_form_load_nodes',
      'wrapper' => 'form-ajax-nodes',
      'method' => 'replace',
      'effect' => 'fade',
    ),
  );
 
  // Если сработал AJAX - в $form_state будут переданы все значения формы.
  // Поэтому здесь проверка - если сработал AJAX - то выбранный тип материала берётся из
  // отправленной формы. А если не сработал - то тип будет первым элементом массива всех типов материалов.
  if (isset($form_state['values']['type'])) {
    $type = $form_state['values']['type'];
  }
  else {
    $type = array_shift($options);
  }
 
  // Устанавливаем значение по умолчанию для выпадающего списка с типами материалов.
  $form['type']['#default_value'] = $type;
 
  // Загружаем 10 последних материалов выбранного типа материала.
  // Здесь ключом является nid ноды, а значением - её заголовок.
  // Так же оборачиваем этот элемент в div, в который будет помещаться
  // обновлённая часть формы
  $options = form_ajax_get_last_nodes($type);
  $form['node'] = array(
    '#type' => 'select',
    '#title' => t('Nodes from !type type',  array('!type' => $type)),
    '#options' => $options,
    '#prefix' => '<div id = "form-ajax-nodes">',
    '#suffix' => '</div>',
  );
 
  // Кнопка, при нажатии на которую будет отправлен AJAX запрос, обрабатываемый
  // функцией form_ajax_form_load_node_content(). Она вернёт полное содержимое выбранной ноды
  // и поместит его в <div id = "form-ajax-node-content"></div>, заменив предыдущее содержимое,
  // если оно было.
  $form['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Load content'),
    '#ajax' => array(
      'callback' => 'form_ajax_form_load_node_content',
      'wrapper' => 'form-ajax-node-content',
      'method' => 'replace',
      'effect' => 'fade',
    ),
  );
 
  // Создаём в форме элемент страницы, куда будет помещено полное отображение ноды
  $form['markup'] = array(
    '#prefix' => '<div id = "form-ajax-node-content">',
    '#suffix' => '</div>',
    '#markup' => '',
  );
 
  return $form;
}
 
/**
 * AJAX callback for loading node list
 */
function form_ajax_form_load_nodes($form, $form_state) {
  // Возвращаем элемент формы, который должен быть перезагружен.
  // В данном случае надо перезагрузить выпадающий список с материалами.
  return $form['node'];
}
 
/**
 * Return full view of selected node
 */
function form_ajax_form_load_node_content($form, $form_state) {
  // Возвращаем элемент формы, который должен быть перезагружен.
  // В данном случае надо перезагрузить элемент, который содержит представление ноды.
  if (isset($form_state['values']['node'])) {
    // Если есть в форме выбранный материал - загружаем его,
    // загружаем полное представление ноды, и отдаём отрендереное представление в качестве элемента формы.
    $nid = $form_state['values']['node'];
    $node = node_load($nid);
    $view = node_view($node);
    $form['markup']['#markup'] = render($view);
  }
  return $form['markup'];
}

Если убрать комментарии - код выглядит гораздо менее устрашаюшим и меньшим по размеру :)

В итоге получается вот такая страница (с загруженным контентом):

example2.pngСкачать исходники

Комментарии

Аватар пользователя andypost
andypost написал:

Типы материала для select лучше делать при помощь node_type_get_names() (без цикла)

function form_ajax_form($form, &$form_state) {
  // Создаём в форме выпадающий список, элементами которого являются типы материалов.
  $form['type'] = array(
    '#type' => 'select',
    '#title' => t('Node types'),
    '#options' => node_type_get_names(),
?>
18.10.2011 04:05
Аватар пользователя Spleshka
Spleshka написал:

Уху, поправил.

18.10.2011 14:43
Аватар пользователя waw
waw написал:

Подскажите пожалуйста как все это установить на друпал. Модуль вроде подключился... а вот страницу не нахожу.

05.01.2012 16:17
Аватар пользователя Spleshka
Spleshka написал:

Перейдите на страницу /ajax_page

05.01.2012 16:57
Аватар пользователя waw
waw написал:

в том то и дело, что пишет "Страница не найдена
Страница "/eurotyre.com.ua/ajax_page" не найдена." у меня друпал на в корне сайта установлен.

05.01.2012 17:17
Аватар пользователя Spleshka
Spleshka написал:

Если модуль установлен - страница должна была создаться, по другому не может быть.

05.01.2012 17:34
Аватар пользователя waw
waw написал:

модуль установлен, но страницу не находит почему то. Drupal 7.9.

05.01.2012 17:53
Аватар пользователя Spleshka
Spleshka написал:

Можно сайт посмотреть в интернете? Я в это не верю)

05.01.2012 18:02
Аватар пользователя waw
waw написал:

http://pilipok.ru/eurotyre.com.ua/ajax_page вот. модуль точно установлен.

05.01.2012 18:07
Аватар пользователя Spleshka
Spleshka написал:

Очень странно. Кэш на сайте сбрасывали?

05.01.2012 18:36
Аватар пользователя waw
waw написал:

Да, сбрасывал. Тут еще такое дело... у меня не создается страница для моей настройки... в одном из модулей.
В вообще вопрос был у меня такой... у меня есть фильтр, который реализован в виде отдельного скприпта. Но этот скрипт выводит результат работы в на отдельную страницу. Хотел объединить резельтат его работы и view фильтрацию для магазина.

05.01.2012 18:45
Аватар пользователя waw
waw написал:

Поставил на другом сайте. На сайте Drupal 7.9? но без интернет магазина... видимо какой глюк одном из моделей... который все портит....
ссылка http://pilipok.ru/ajax_page Так что пример работает. Извините за беспокойство... хотя мне другое немного надо было сделать....

05.01.2012 20:05
Аватар пользователя Spleshka
Spleshka написал:

Без проблем. А что вам именно надо сделать?

05.01.2012 20:25
Аватар пользователя waw
waw написал:

Огромное Вам спасибо. Очень классный у Вас пример! Действительно работает! Очень прозрачный и понятный!

Сделать нужно поиск нодов и их отображение. Уже сделал по образу и подобию Вашей формы, форму выбора условий, но вопрос в том, как отобразить больше одной ноды? У вас отображается только одна. Попробовал выводить через рендеринг 2 ноды, но не очень красиво выходит. Можноли как то через view отобразить несколько нод?

10.01.2012 11:13
Аватар пользователя Spleshka
Spleshka написал:

Чтобы вывести данные через вьюс - создайте на сайте вьюху, которая выводит ноды, а потом просто вместо node_view($node) передайте views_embed_view с параметрами. (http://www.drupal.ru/node/29439 первый коммент)

10.01.2012 13:37
Аватар пользователя waw
waw написал:

Если честно. Непонятно. У меня есть список nid, можно в массив его добавить., как вывести в определенный вид не понимаю. views_embed_view($name, $display_id="page_1"); если смотреть вот в этом примере... то как связать это со списком nid-ов.

11.01.2012 13:32
Аватар пользователя Spleshka
Spleshka написал:

Так понятнее?

$nids = array(1, 2, 5, 10, 512);
print views_embed_view($name, 'page_1', $nids);
11.01.2012 13:50
Аватар пользователя waw
waw написал:

Вообще то... не прояснилось:)
$nids = array(node_load(26320), node_load(26321));
$name_view = 'podbor';
$views = views_embed_view($name_view, 'page_1', $nids);
print $views;
$form['markup']['#markup'] = render($views);
Этот вариант ничего не выводит на экран...
$nids = array(26320, 26321); - так тоже пусто..
Только вот так выводит:
$node = node_load(26320);
$node1 = node_load(26321);
$view = node_view($node);
$view1 = node_view($node1);
$form['markup']['#markup'] = ''.render($view).''.render($view1);

11.01.2012 17:41
Аватар пользователя Spleshka
Spleshka написал:

Не надо в первом случае делать node_load(). Просто передайте им строку из nid материалов.

$nids = array(26320, 26321);
$views = views_embed_view('podbor', 'page_1', $nids);
$form['markup']['#markup'] = $views;
12.01.2012 13:51
Аватар пользователя Max
Max написал:

Супер статья, Спасибо!

27.01.2012 11:59
Аватар пользователя Frantsuzzz
Frantsuzzz написал:

Спасибо за статью. Как раз то, что искал!

02.02.2012 19:07
Аватар пользователя Serge
Serge написал:

Добрый день.
Отличная статья.
Есть вопрос: при выводе методом подобным указанному в статье (друпал 6) в браузерах Опера 11.60 и выше не закрывается окошко селекта
Похожий глюк наблюдается и в примере (ссылка http://pilipok.ru/ajax_page)
Как победить оперу?

22.02.2012 19:43
Аватар пользователя Руслан
Руслан написал:

Привет. Большое спасибо за статью. Подскажите пожалуйста как решить следующую задачу. Есть форма. В конструкторе, обьявляется элемент как button и в папаметрах указывается аякс, калбек и ид элемента в который добавлям то что вернет калбек. Метод append.
Далее идет textfield который выводит одно поле (их далее нужно добавлять, типа кнопка add). Этот инпют обернут в соответствующий див.
Проблема в том что я никак не могу допиреть, как мне узнавать имя последнего добавленного элемента и делать ключ следующему +1.
Брать этот "следующий" и выводить его.

function server_management_questions_add_answers($form, &$form_state) {     $form['type'] = array(
        '#type' => 'button',
        '#value' => t('Add'),
        '#ajax' => array(
            'callback' => 'form_ajax_form_load_nodes',
            'wrapper' => 'form-ajax-nodes',
            'method' => 'append',
            'effect' => 'fade',
        ),
    );
 
    $form['answer_1'] = array(
        '#type' => 'textfield',
        '#title' => t('Nodes from'),
        '#prefix' => '<div id = "form-ajax-nodes">',
        '#suffix' => '</div>',
        '#required' => true,
    );
 
    if (!empty($form_state['input']['answer_1'])) {
 
        $form['answer_add'] = array(
            '#type' => 'textfield',
            '#title' => t('Nodes from 2'),
            '#required' => true,
            '#value' => '',
        );
 
    }     $form['submit'] = array(
        '#type' => 'submit',
        '#value' => t('Add answers'),
    );     return $form;
} function form_ajax_form_load_nodes($form, &$form_state) {
 
    return $form['answer_add'];
}
07.08.2012 20:12
Аватар пользователя Гость
Гость написал:

Откройте модуль Poll (в ядре) и посмотрите как это реализовано там.

09.08.2012 13:00
Аватар пользователя Дмитрий
Дмитрий написал:

Здравствуйте, спасибо за статью, очень помогает :) подскажите, пожалуйста,
как после запуска колбека 'form_ajax_form_load_nodes' и размещения содержимого в нужном месте умудриться ещё запустить скрипт типа $("select").uniform();

05.12.2012 11:38
Аватар пользователя Дмитрий
Дмитрий написал:

Основываясь на вашей лекции (кстати спасибо за доклад) решил "по-хитрому" так:

  $commands[] = ajax_command_replace('#form-cities', drupal_render($form['city']));
  $commands[] = ajax_command_append('#form-cities', '<script type="text/javascript">$("#form-cities select").uniform();</script>');

а код $commands[] = ajax_command_invoke('#form-cities select', 'uniform'); почему то не сработал :(

05.12.2012 15:21
Аватар пользователя Spleshka
Spleshka написал:

Более кошерный вариант был бы создать свою ajax команду и вызвать её.

05.12.2012 21:04
Аватар пользователя kirminator2
kirminator2 написал:
21.05.2013 20:38
Аватар пользователя kirminator2
kirminator2 написал:

А подобный модуль для страницы контактов как будет выглядеть? у меня через колорбокс немного глючит, вот пример страницы http://scn-realty.ru/users/160 с незаполненными полями открывается документ. как сюда ajax прикрутить? может бывают готовые решения?

21.05.2013 20:38
Аватар пользователя kevinus
kevinus написал:

в function form_ajax_menu() вы забыли указать 'access arguments' => array('access for module')

11.02.2015 14:11
Аватар пользователя Energy
Energy написал:

подскажите пожалуйста, как сделать без кнопки, чтобы при выборе элемента сразу выдавался запрос.
форму кнопки закоментил и сделал вот так:
$form['node'] = array(
'#type' => 'select',
'#title' => t('Nodes from !type type', array('!type' => $type)),
'#options' => $options,
'#prefix' => '',
'#suffix' => '',
'#ajax' => array(
'callback' => 'form_ajax_form_load_node_content',
'wrapper' => 'form-ajax-node-content',
'method' => 'replace',
'effect' => 'fade',
),
);
но ничего не выдает(((

24.05.2015 15:03

Комментировать