Создание динамических форм с использованием Drupal Ajax Framework

Для динамического изменения элементов формы в Drupal 7 можно использовать Drupal Ajax Framework.

Drupal Ajax Framework - это удобный инструмент который позволяет создавать формы с динамической перезагрузкой полей без перезагрузки страницы.

Рассмотрим пример:

Предположим что у нас есть сайт с базой данных фильмов по типу imdb.com или kinopoisk.ru.

Фильмы у нас будут сортироваться по жанру, для этой цели создадим словарь таксономии 'Жанр' и добавим туда несколько жанров кино как показано на картинке ниже.

Таксономия Жанр

Далее создадим тип материалов 'Фильм' с полем 'Жанр' типа 'ссылка на термин' (term reference).

Тим материалов Фильм

Для наглядности создадим несколько фильмов в каждом жанре. Например так:

  • Боевик:
    • Рембо
    • Терминатор
    • Крепкий орешек
  • Комедия:
    • Иван Васильевич меняет профессию
    • Бриллиантовая рука
  • Драма:
    • Пианист
    • Титаник

Следующим шагом создадим тип материалов 'Рецензия' на котором мы и будем демонстрировать работу Drupal Ajax Framework. Этот тип материала будет использоваться для создания рецензий к фильмам.

Тип материалов Рецензия

Этот тип материала будет содержать поле 'Жанр' по которому будут фильтроваться фильмы и поле 'Фильм' (для этого поля используем модуль Entity Reference для того чтобы создать ссылку на материал(ноду) типа 'Фильм') с помощью которого мы будем выбирать к какому фильму мы хотим написать рецензию.

На данный момент при создании новой рецензии в поле Фильм попадают фильмы всех жанров что не очень удобно.

Поле Фильм без ajax

Наша задача сделать так чтобы при выборе жанра в поле 'Фильм' подгружались только фильмы выбранного жанра.

Для решения этой задачи создадим новый модуль и назовем его film_review.

Листинг файла film_review.info:

name = Film Review
core = 7.x
package = Custom

Листинг файла film_review.module:

/**
 * Implements hook_form_alter().
 */
function film_review_form_alter(&$form, &$form_state, $form_id) {
  switch ($form_id) {
    case 'review_node_form':
      //Получаем идентификатор термина выбранного в поле Жанр
      $selected_genre = (isset($form_state['values']['field_genre']['und'][0]['tid'])) ? $form_state['values']['field_genre']['und'][0]['tid'] : NULL;
      //Добавляем обработчик ajax
      $form['field_genre']['und']['#ajax'] = array(
        'callback' => '_film_review_genre_ajax_callback', //Функция которая будет возвращять обновленное содержимое
        'wrapper' => 'films-replace-dropdown',            //id елемента в DOM содержимое которого будет обновлятся
        'event' => 'change',                              //jQuery событие по которому будет осуществлятся ajax перезагрузка
        'method' => 'replace',                            //метод с помощью которого будет происходить замена содержимого обертки (wrapper)
      );
      //Добавляем обертку полю Фильм в которую будет загружатся обновленное поле
      $form['field_film']['#prefix'] = '<div id="films-replace-dropdown">';
      $form['field_film']['#suffix'] = '</div>';
 
      //Добавляем состояние для полz Фимльм чтобы оно появлялось на форме только после выбора жанра
      $form['field_film']['#states'] = array(
        'invisible' => array(
          ':input[id=edit-field-genre-und]' => array('value' => '_none'),
        ),
      );
 
      //Обновляем содержимое поля Фильм если выбран жанр
      if ($selected_genre) {
        $form['field_film']['und']['#options'] = _film_review_get_films_options($selected_genre);
      }
      break;
  }  
}
 
/**
 * Ajax callback for field_genre.
 */
function _film_review_genre_ajax_callback(&$form, &$form_state) {
  //В этой функции просто возвращяем обновленное в hook_form_alter поле Фимльм
  return $form['field_film'];
}
 
/**
 *  Get film field options by selected genre.
 */
function _film_review_get_films_options($selected_genre) {
  //Получаем список всех фильмов для выбранного жанра
  $query = db_select('field_data_field_genre', 'fg');
  $query->innerJoin('node', 'n', 'n.nid = fg.entity_id');
  $query->fields('n', array('nid', 'title'));
  $query->condition('fg.bundle', 'film');
  $query->condition('fg.field_genre_tid', $selected_genre);
  $result = $query->execute();
 
  $options = array();
  foreach($result as $row) {
    $options[$row->nid] = $row->title;
  }
 
  return $options;
}

Включаем наш новый модуль:

Модуль films_review

Теперь при выборе жанра мы видим мгновенную перезагрузку поля Фильм.

Поле Фильм с ajax

Скачать исходник модуля можно здесь: Модуль film_review

Подведем итоги: Drupal Ajax Framework - это простой в использовании и очень мощный инструмент с помощью которого можно создавать динамические формы что очень упрощает пользовательский интерфейс сайта.

Поделись с друзьями:

Комментарии

Подскажите пожалуйста:
Нужно сделать форму добавления адреса с перезагрузкой полей.Например:при выборе поля "Город" в поле "Район " загружаются только районы этого города.
Я сделал следующее:
Создал тип материала "Город",тип материала "Район",а также тип материала "Заявка" где и нужно добавление адреса.В типе материала Заявка поля:город (city) и район (district).Эти поля с помощью Entiy reference ссылаются на соответствующий материал.
Скачал Ваш модуль и изменил его (здесь я уже новичок) получилось следующее:

<?php
/**
* Implementation of hook_form_alter
*/

function district_tiket_form_alter(&$form, &$form_state, $form_id) {
switch ($form_id) {
case 'tiket_node_form':
//Получаем идентификатор термина выбранного в поле Жанр
$selected_city = (isset($form_state['values']['field_city']['und'][0]['tid'])) ? $form_state['values']['field_city']['und'][0]['tid'] : NULL;
//Добавляем обработчик ajax
$form['field_city']['und']['#ajax'] = array(
'callback' => '_district_tiket_city_ajax_callback', //Функция которая будет возвращять обновленное содержимое
'wrapper' => 'district-replace-dropdown', //id елемента в DOM содержимое которого будет обновлятся
'event' => 'change', //jQuery событие по которому будет осуществлятся ajax перезагрузка
'method' => 'replace', //метод с помощью которого будет происходить замена содержимого обертки (wrapper)
);
//Добавляем обертку полю Фильм в которую будет загружатся обновленное поле
$form['field_district']['#prefix'] = '';
$form['field_district']['#suffix'] = '';

//Добавляем состояние для полz Фимльм чтобы оно появлялось на форме только после выбора жанра
$form['field_district']['#states'] = array(
'invisible' => array(
':input[id=edit-field-city-und]' => array('value' => '_none'),
),
);

//Обновляем содержимое поля Фильм если выбран жанр
if ($selected_city) {
$form['field_district']['und']['#options'] = _district_tiket_get_films_options($selected_city);
}
break;
}
}

/**
* Ajax callback for field_genre
*/

function _district_tiket_city_ajax_callback(&$form, &$form_state) {
//В этой функции просто возвращяем обновленное в hook_form_alter поле Фимльм
return $form['field_film'];
}

/**
* Get film field options by selected genre
*/

function _district_tiket_get_films_options($selected_city) {
//Получаем список всех фильмов для выбранного жанра
$query = db_select('field_data_field_city', 'fg');
$query->innerJoin('node', 'n', 'n.nid = fg.entity_id');
$query->fields('n', array('nid', 'title'));
$query->condition('fg.bundle', 'district');
$query->condition('fg.field_city_tid', $selected_city);
$result = $query->execute();

$options = array();
foreach($result as $row) {
$options[$row->nid] = $row->title;
}

return $options;
}

просто менял ваши данные на свои.
Модуль не работает и не выдает ошибок.

После того как закончите смеяться, сильно прошу помочь в этом вопросе.заранее спасибо
vgatapov@mail.ru

Ну во первых я бы советовал "Город" и "Район" делать словарями а не типами материалов. Ваш код не работает по тому что в моем примере Жанр это как раз таки поле типа "Ссылка на термин" а не Entity reference и просто переименование названия поля тут не сработает.

Решил задачу как бы.Однако есть загвоздка.Хочу вывести добавление материала "Заявка" в блок.Установил модуль Form block,включил.Форма выводится в блок,но перестал работать ваш film_review.Как вы думаете в чем дело?Спасибо

Не знаю. Теоретически если работает на странице добавления материала то должно работать и в блоке так как форма одна и таже. Возможно какие-то особенности работы модуля Form block. Надо копать.