Дата поста: 29-10-2012
Для понимания, что такое AHAH, и более конкретно, что такое AHAH в Drupal, до прочтения этой части прочитайте пост Введение в формы AHAH на Drupal. В нем описывается, что вам нужно сделать в массиве $form, чтобы добавить элементы в динамическую форму. Здесь мы объясним следующие шаги в написании функции обратного вызова и обработки отправления формы без нарушений безопасности Form API.
1. Как должна работать ваша форма AHAH
После того как вы прикрепили аргумент # ahah к элементу формы, вам нужно сделать очень точные настройки для его наилучшего использования. Вот обзор необходимых шагов:
- Для того чтобы получить данные из $form_state, добавьте код в свою основную функцию. Это позволит использовать переданные пользователем данные для создания элементов формы и их значений в дополнение к данным, уже существующим в базе данных.
- Пишите функцию обратного вызова AHAH, которая будет вызываться из связки #ahah. Эта функция извлечет форму из кеша и проведет через процесс ее восстановления, который включает вызов соответствующих обработчиков.
- Обязательно создайте функцию обратного вызова AHAH, которая будет вызываться вашим #ahah. Обработчик должен сохранять пользовательское представление информацию в $ form_state, которое затем будет использоваться в процессе восстановления формы. Этот обработчик будет вызываться во время выполнения обратного вызова.
Большинство людей, делая первую попытку в построении формы AHAH, делают ошибку, используя обратный вызов AHAH для создания изменений в форме, например, для добавления нового элемента или изменения существующего. Это НЕ ТО, для чего предназначен обратный вызов AHAH. Обратный вызов просто находит, обрабатывает и перестраивает форму, и затем возвращает обновленную часть формы для замены первоначально представленной версии.
Итак, как же на самом деле сделать, чтобы ваша форма действительно изменилась?
"Изменить"; по отношению к вашей форме означает, что форма может быть построена различными способами в зависимости от того, что находится в $ form_state. Если вы можете это понять, то вы прошли 90% пути к подключению AHAH к Drupal.
2. Код, который нужен вам для этого
Существуют две трюка, выходящие за рамки простого создания формы, которые нужно сделать для того, чтобы AHAH работал хорошо и гладко при одновременном снижении дыр в безопасности.
Во-первых, форма, которую вы видите на экране и массив $form, который сохраняется в кеше, всегда должен быть одним и тем же. Во-вторых, функция, создающая форму, должна создавать новые элементы формы, основываясь на содержимом $form_state, которое заполняется предоставленным обработчиком формы, и воссоздавать всю форму, каждый раз основываясь только на $form_state и данных, уже существующих в базе данных. При построении формы эта функция не должна использовать данные из $_POST
или подобных массивов.
Как пример того, как должна начинаться ваша функция по созданию формы, здесь представлена небольшая выборка из верхней части функции node_form в node.pages.inc:
<?php
if (isset($form_state['node'])) {
$node = $form_state['node'] + (array)$node;
}
?>
Хотя это кажется сложным и трудным для понимания, на самом деле это достаточно просто: мы делаем вид, что отправленные пользователем данные уже сохранены в базе данных и мы просто показываем форму, которую затем получим при редактировании ноды.
Если следовать двум вышеприведенным трюкам, возникают красивый цикл:
- Генератор формы создает форму.
- Она сохраняется в кеше и обрабатывается.
- Действия пользователя запускают обратный вызов.
- Вы извлекаете форму из кеша.
- Вы обрабатываете ее при помощи drupal_process_form. Эта функция вызывает заданные обработчики, которые помещают все, что нужно сохранить, в $form_state.
- Вы вызываете drupal_rebuild_form, которая удаляет
$_POST
. - Вызывается функция построения формы и опять создается форма, но поскольку при ее создании использовалась $form_state, форма будет другой.
- Новая форма опять кешируется и обрабатывается, но поскольку
$_POST
удален, соответствующие обработчики вызываться не будут. - Ваш обратный вызов AHAH получает кусок формы и обрабатывает его.
Этот цикл приводит к хорошему и безопасному коду, и также отвечает стандартам AHAH Zen.
Ниже приведена функция обратного вызова из poll.module* как пример того, как нужно создавать правильно AHAH.
<?php
function poll_choice_js() {
// Форма создается в файле, который нужно включить вручную.
include_once 'modules/node/node.pages.inc';
//Начинаем шаг №3, готовимся к №4.
$form_state = array('storage' => NULL, 'submitted' => FALSE);
$form_build_id = $_POST['form_build_id'];
// Шаг №4.
$form = form_get_cache($form_build_id, $form_state);
// Подготовка к №5.
$args = $form['#parameters'];
$form_id = array_shift($args);
$form_state['post'] = $form['#post'] = $_POST;
$form['#programmed'] = $form['#redirect'] = FALSE;
// Шаг №5.
drupal_process_form($form_id, $form, $form_state);
// Шаги №№6, 7 и 8.
$form = drupal_rebuild_form($form_id, $form_state, $args, $form_build_id);
// Шаг №9.
$choice_form = $form['choice_wrapper']['choice'];
unset($choice_form['#prefix'], $choice_form['#suffix']);
$output = theme('status_messages') . drupal_render($choice_form);
// Финальное выполнение обратного вызова.
drupal_json(array('status' => TRUE, 'data' => $output));
}
?>
До и после включения drupal_rebuild_form ваш код не должен изменяться. Да, это должно быть служебной функцией, и это реализовано в D7.
Следующий пример, взятый из quicktabs.module, показывает, как вышеприведенный код работает с формами, не являющимися нодами. Административная форма quicktabs позволяет создать блоки с контентом в виде закладок, выбирая или существующий блок, или представление в виде закладки. В этой форме три элемента #ahah: кнопка, позволяющая добавить дополнительную закладку на форму (по сути, дополнительный набор элементов), кнопка для удаления закладки с формы и выпадающий список, который при изменении мгновенно обновляет варианты показа списка в другом выпадающем элементе.
Ниже приведен подключенный обработчик для кнопки "Добавить закладку";, создаваемый обратным вызовом AHAH для всех трех элементов AHAH:
<?php
/**
* Обработчик для кнопки submit "Add Tab"
*/
function qt_more_tabs_submit($form, &$form_state) {
unset($form_state['submit_handlers']);
form_execute_handlers('submit', $form, $form_state);
$quicktabs = $form_state['values'];
$form_state['quicktabs'] = $quicktabs;
$form_state['rebuild'] = TRUE;
if ($form_state['values']['tabs_more']) {
$form_state['qt_count'] = count($form_state['values']['tabs']) + 1;
}
return $quicktabs;
}
/**
* обратный вызов ahah
*/
function quicktabs_ahah() {
$form_state = array('storage' => NULL, 'submitted' => FALSE);
$form_build_id = $_POST['form_build_id'];
$form = form_get_cache($form_build_id, $form_state);
$args = $form['#parameters'];
$form_id = array_shift($args);
$form['#post'] = $_POST;
$form['#redirect'] = FALSE;
$form['#programmed'] = FALSE;
$form_state['post'] = $_POST;
drupal_process_form($form_id, $form, $form_state);
$form = drupal_rebuild_form($form_id, $form_state, $args, $form_build_id);
$qt_form = $form['qt_wrapper']['tabs'];
unset($qt_form['#prefix'], $qt_form['#suffix']); // Prevent duplicate wrappers.
$javascript = drupal_add_js(NULL, NULL, 'header');
drupal_json(array(
'status' => TRUE,
'data' => theme('status_messages') . drupal_render($qt_form),
'settings' => call_user_func_array('array_merge_recursive', $javascript['setting']),
));
}
?>
Здесь нам не нужно для доступа к $form включать modules/node/node.pages.inc, поскольку функция, создающая форму, уже определена в самом quicktabs.module. Отметим, что изменение будет применяться к форме в приложенном обработчике (увеличивающее количество вкладок на 1), который вызывается из drupal_process_form. Чтобы это работало, функция, создающая форму, должна реагировать на $ form_state:
<?php
// если форма создавалась обратным вызовом ahah, $form_state['quicktabs'] будет
// содержать сохраненные значения формы, а если это форма редактирования, контент
// $quicktabs будет извлекаться из базы данных
if (isset($form_state['quicktabs'])) {
$quicktabs = $form_state['quicktabs'] + (array)$quicktabs;
}
// сколько вкладок мы хотим создать - сначала проверяем $form_state
if (isset($form_state['qt_count'])) {
$qt_count = $form_state['qt_count'];
}
else {
$qt_count = max(2, empty($tabcontent) ? 2 : count($tabcontent));
}
?>
Опять, написав код $quicktabs = $form_state['quicktabs'] + (array)$quicktabs;
мы делаем вид, что представленные пользователем данные уже сохранены.
Вот, наконец, обработчик для выпадающего списка views:
<?php
/**
* обработчик для выпадающего Views
*/
function qt_get_displays_submit($form, &$form_state) {
unset($form_state['submit_handlers']);
form_execute_handlers('submit', $form, $form_state);
$quicktabs = $form_state['values'];
$form_state['quicktabs'] = $quicktabs;
$form_state['rebuild'] = TRUE;
return $quicktabs;
}
?>
Обратите внимание, это не похоже на то, как в случае, когда изменения затрагивают всю форму – форма будет просто восстановлена, но с другими значениями в выпадающем списке, и это изменит параметры в его выводе на экран. Его обратный вызов AHAH такой же, как и у кнопки удаления, но просто используется другой задаваемый обработчик.
Одной из других тем, достойных упоминания об обратном вызове AHAH, является использование массива $javascript. Этот трюк впервые использовал Wim Leers в AHAH Helper module и он задает повторное прикрепление поведения AHAH к элементам, сгенерированных формой при помощи AHAH. Обычно функционал jQuery всегда используется повторно, если он задан внутри Drupal.behaviors, но поведение AHAH подключается только к элементам, указанным в Drupal.settings.ahah. Этот трюк не обязателен для poll.module, поскольку элементы формы сами по себе (текстовые поля для ввода результатов голосования) не имеют никакой функциональности AHAH, но в случае Quick Tabs, модуля для создания нового выпадающего списка в модуле Views, выпадающий список вставляется с каждым новым набором элементов, которые добавляются кнопкой "Добавить вкладку";, и к нему нужно каждый раз подключать свойство AHAH.
Чтобы увидеть полный код примера, посетите http://drupal.org/project/quicktabs и загрузите релиз quicktabs-6.x-2.0-rc1. Для того чтобы увидеть форму в действии, перейдите сюда http://64.55.119.4/admin/build/quicktabs/add. Вам нужно изменить тип закладки на "View";, для того чтобы увидеть, как это работает.
Код из poll.module взят из версии D7, обратный вызов ahah еще не используется в D6. Патч находится здесь.
Предосторожности при работе с загрузкой файлов
Ничего из приведенного выше не работает с типом «файл». Все хорошо работает для добавления полей, но как только вы попытаетесь загрузить файл, любой старый броузер выдаст ошибку "HTTP error 0";.
Более подробно проблема обсуждается здесь: http://drupal.org/node/399676.
В общем, представленный здесь код работает прекрасно, если только тип поля не "file"