Для понимания, что такое 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;
  }
  ?>

Хотя это кажется сложным и трудным для понимания, на самом деле это достаточно просто: мы делаем вид, что отправленные пользователем данные уже сохранены в базе данных и мы просто показываем форму, которую затем получим при редактировании ноды.

Если следовать двум вышеприведенным трюкам, возникают красивый цикл:

  1. Генератор формы создает форму.
  2. Она сохраняется в кеше и обрабатывается.
  3. Действия пользователя запускают обратный вызов.
  4. Вы извлекаете форму из кеша.
  5. Вы обрабатываете ее при помощи drupal_process_form. Эта функция вызывает заданные обработчики, которые помещают все, что нужно сохранить, в $form_state.
  6. Вы вызываете drupal_rebuild_form, которая удаляет $_POST.
  7. Вызывается функция построения формы и опять создается форма, но поскольку при ее создании использовалась $form_state, форма будет другой.
  8. Новая форма опять кешируется и обрабатывается, но поскольку $_POST удален, соответствующие обработчики вызываться не будут.
  9. Ваш обратный вызов 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. Для того чтобы увидеть форму в действии, щелкните здесь. Вам нужно изменить тип закладки на “View”;, для того чтобы увидеть, как это работает.

Код из poll.module взят из версии D7, обратный вызов ahah еще не используется в D6. Патч находится здесь.

Предосторожности при работе с загрузкой файлов

Ничего из приведенного выше не работает с типом «файл». Все хорошо работает для добавления полей, но как только вы попытаетесь загрузить файл, любой старый броузер выдаст ошибку “HTTP error 0”;.

Более подробно проблема обсуждается здесь: http://drupal.org/node/399676.

В общем, представленный здесь код работает прекрасно, если только тип поля не “; file “;

Оригинал статьи

Комментарии

comments powered by Disqus