Быстрое введение в Forms API
Внимание - эта страница была лишь частично обновлена для API Drupal 6.x
До тех пор, пока она не будет обновлена полностью, руководствуйтесь также и этим документом: Drupal 5.x to 6.x FormAPI changes
Form API предоставляет практически неограниченные возможности для изменения внешнего вида форм с помощью пользовательских тем (темизация), проверки данных отправляемых через форму (валидации) и отправки форм. Более того - любая форма (даже системная, входящая в ядро) при необходимости может быть изменена до неузнаваемости - элементы могут быть удалены, могут быть добавлены новые элементы, изменены существующие... Последующие страницы, конечно же, не полное руководство по этой функциональности, но послужат хорошей отправной точкой. В частности будет рассмотрено создание форм, темизация, валидация и отправка форм.
Создание форм
Элементы формы в настоящее время объявляются в виде массива с иерархической структурой, где в качестве элементов массива элементов (которые могут быть вложенными) выступают элементы формы. Каждый элемент формы также представляет собой массив, описывающий свойства данного элемента. Ключ данного массива - название свойства, а значение элемента - значение свойства. Например, вот как может быть описано текстовое поле (textfield):
<?php $form['foo'] = array( '#type' => 'textfield', '#title' => t('bar'), '#default_value' => $object['foo'], '#size' => 60, '#maxlength' => 64, '#description' => t('baz'), ); ?>
и кнопка submit:
обратите внимание на следующие моменты:
- Свойство элемента
name
определено массиве$form
, в самом конце дерева массива. Для примера, если элемент в дереве формы был определен, как:
<?php $form['account_settings']['username'] ?>
...то имя этого элемента будет 'username'-- и будет доступно по этому ключу в массиве
$form_state['values']
в функциях валидации и обработки формы. Код формы структурируется таким образом перед тем, как передать пары ключ/значение.
NOTE: Позже будет рассмотрено как можно получить полную структуру массива в$form_state['values']
- Тип элемента формы определяется как атрибут с свойством
#type
. - Ключи свойств/атрибутов определяются окруженными в кавычки, и начинаются со знака '#'. Значения строковые.
- Порядок определения свойств/атрибутов не имеет значения, и любые свойства которые Вам не нужны, Вы можете не определять. Многие свойства/атрибуты имеют значение по-умолчанию, если не определены явно.
- Не используйте атрибут
#value
для любых элементов формы, которые могут быть изменены пользователем, вместо этого используйте#default_value
. Не заполняйте форму из массива$form_state['values']
(или$_POST
)! FormsAPI сделает это автоматически; заполняйте только оригинальные значения поля.
Одним из преимуществ этой системы является то, что по конкретно указанным ключам сделать расшифровку элемента формы намного легче.
Давайте посмотрим на работающий участок кода, написанный с использованием API:
<?php function test_form($form_state) { // Access log settings: $options = array('1' => t('Enabled'), '0' => t('Disabled')); $form['access'] = array( '#type' => 'fieldset', '#title' => t('Access log settings'), '#tree' => TRUE, ); $form['access']['log'] = array( '#type' => 'radios', '#title' => t('Log'), '#default_value' => variable_get('log', 0), '#options' => $options, '#description' => t('The log.'), ); $period = drupal_map_assoc(array(3600, 10800, 21600, 32400, 43200, 86400, 172800, 259200, 604800, 1209600, 2419200, 4838400, 9676800), 'format_interval'); $form['access']['timer'] = array( '#type' => 'select', '#title' => t('Discard logs older than'), '#default_value' => variable_get('timer', 259200), '#options' => $period, '#description' => t('The timer.'), ); // Description $form['details'] = array( '#type' => 'fieldset', '#title' => t('Details'), '#collapsible' => TRUE, '#collapsed' => TRUE, ); $form['details']['description'] = array( '#type' => 'textarea', '#title' => t('Describe it'), '#default_value' => variable_get('description', ''), '#cols' => 60, '#rows' => 5, '#description' => t('Log description.'), ); $form['details']['admin'] = array( '#type' => 'checkbox', '#title' => t('Only admin can view'), '#default_value' => variable_get('admin', 0), ); $form['name'] = array( '#type' => 'textfield', '#title' => t('Name'), '#size' => 30, '#maxlength' => 64, '#description' => t('Enter the name for this group of settings'), ); $form['hidden'] = array('#type' => 'value', '#value' => 'is_it_here'); $form['submit'] = array('#type' => 'submit', '#value' => t('Save')); return $form; } function test_page() { return drupal_get_form('test_form'); } ?>
Этот пример демонстрирует, как элементы формы могут быть построены в иерархическую структуру за счет расширения массива формы. Здесь использованы две функции - функция, которая строит форму, и функция, которая отображает форму, используя drupal_get_form()
. Обратите внимание, что функция построения формы всегда получает $form_state
в качестве первого аргумента, не смотря на то, что для базового использования (например, как в этом примере) он не используется.
Заметьте, что первый слой образован двумя группами формы, 'access' и 'details', и уже внутри каждой из групп слоем ниже расположены различные элементы формы. Важен порядок описания конструкции формы, так как ошибочный код массива $form
может быть не принят во время построения формы (также данный вопрос будет обсуждаться позднее в главе посвященной использованию тем).
Для создания групп формы атрибуту объекта #type
присваивается значение fieldset
. Посмотрите как группа формы details
была расширена с помощью нескольких добавленных атрибутов.
Все группы и элементы были определены функцией построения в родительком массиве $form
.
Функция drupal_get_form
является ключевой для Forms API. Она проста в использовании, принимает всего один обязательный параметр - строку с именем функции, в которой определен массив $form
. Функция drupal_get_form
кроме обязательного параметра может принимать необязательные, которые просто будут переданы в функцию создания массива $form
. Имя функции, создающей массив $form
, должно состоять из идентификатора формы и собственно имени функции и иметь следующий вид ИдентификаторФормы_ИмяФункции
drupal_get_form
производит следующие действия:
- Запускает механизм построения форм получая
$form
из функции определения формы - Переводит элементы вида
$form['name']
в HTML-элементы формы - Проверяет вводимые данные на правильность заполнения, и выполняет частные функции обработки если они были определены
- Обрабатывает форму если имеется функция обработки и если пользователь запрашивает эту обработку
- Вызывает любые объявленные функции темы
- Возвращает HTML строку которая содержит актуальную форму.
Для более подробной информации смотрите API страницу для drupal_get_form()
Важная информация на заметку: заметьте что $form['access']
имеет атрибут '#tree' => TRUE
. Эта опция включает древовидную структуру всех дочерних элементов, используется при работе с $form_state['values']
.
Темизация форм
API позволяет темизировать все формы, включая прописанные в ядре. Это возможно благодаря абстракции сложных элементов темизации, так что они могут быть переписаны при создании формы. Абстрагирование выполняется при помощи двух методов:
- Непосредственным включением разметки в массив $form в качестве элемента:
- Атрибуты
#prefix
и#suffix
вставляют разметку до и после элемента формы соответственно, например код
<?php $form['access'] = array( '#type' => 'fieldset', '#title' => t('Access log settings'), '#prefix' => '<div class="foo">', '#suffix' => '</div>', ); ?>
...поместит тег div перед и после группы формы, т. е. элементы формы, входящие в группу, также будут включены в div. Если же поместить эти атрибуты в элемент внутри группы формы, тогда теги будут включать только этот отдельный элемент, и т. д.
- Тип
#markup
можно вставлять в любоме месте формы, и его значение будет выводиться в этом месте в иерархии при выполнении формы, например в случае:
<?php $form['div_tag'] = array('#type' => 'markup', '#value' => '<div class="foo">'); ?> </div> <p>Доступ к этому элементу и его изменение осуществляется по имени в массиве 'div_tag'</p> <p><em>ПРИМЕЧАНИЕ:</em> не нужно специально объявлить тип разметки #markup, поскольку он задан по умолчанию.</p> </li> </ul> </li> <li>Выделение разметки в отдельную функцию темизации. Этот метод предпочтителен при сколько-нибудь сложной разметке. Он осущетсвляется с помощью функции, начинающейся с <em>theme_</em> перед ID формы. Если нужно использовать эту функцию для разных форм, можно указать параметр вызова (callback arg) в <code>drupal_get_form
--третий аргумент
drupal_get_form
будет строка с именем функции, которую вызовет форма, а функцией темизацией будет эта строка с theme_ вначале.
Пример:
Для формы выше мы создаем следующую функцию темизации:<?php function theme_test_form($form) { $output = ''; $output .= drupal_render($form['name']); $output .= '<div class="foo">'; $output .= drupal_render($form['access']); $output .= '<div class="bar">'; $output .= drupal_render($form['details']); $output .= '</div></div>'; $output .= drupal_render($form); return $output; } ?>
Обратите внимание:
- В функции темизации есть прописан один аргумент, являющийся массивом темизируемой формы
- Выводимая строка строится и возвращается так же, как и в обычной функции темизации
- Элементы формы обрабатываются функцией
drupal_render
- При вызове функции
drupal_render
и передаче ей массива элементов (как в группе полей) будут обработаны все элементы массива в том же порядке, в каком они заданы в массиве формы. - По умолчанию порядок обработки формы такой же, в каком она построена, но его можно изменить в функции темизации вызвав
drupal_render
в нужном месте. В предыдущем примере это было сделано с помощью$form['name']
. - Код обработки отслеживает обработанные элементы, чтобы не допустить повторной обработки. Обратите внимание, что
drupal_render
вызывается для всего массива формы в конце функции темизации, но обработает только оставшиеся необработанными элементы, в данном случае кнопку отправки.drupal_render($form)
обычно вызывается в конце функции темизации, чтобы обработать кнопки отправки и/или скрытые поля, объявленные заданные в частном случае.
- Атрибуты
Проверка(валидация) форм
В form API присутствует механизм валидации, применяемый ко всем отправляемым формам. Если Вы хотите добавить дополнительную валидацию к отправляемой форме, то Вы можете создать функцию валидации. Имя функции валидации состоит из form ID с добавлением _validate. У функции два аргумента: $form
и $form_state
. $form
- это массив отправляемой формы, а $form_state['values']
содержит значения формы, которые Вы хотите проверить. (Замечание - у нескольких форм может совпадать _validate или _submit функция - поэтому если потребуется form's ID, его можно получить из $form['form_id']['#value']
или $form_state['values']['form_id']
.)
Приведем пример функции валидации для нашей формы:
<?php function test_form_validate($form, &$form_state) { if ($form_state['values']['name'] == '') { form_set_error('', t('You must select a name for this group of settings.')); } } ?>
Отправка форм
Нормальный метод отправки формы с использованием AIP заключается в использовании функции отправки. Это функция, имеющая такое же имя и аргументы, как функция проверки, за исключением того, что в конце используется префикс _submit (вместо _validate). Any forms which are submitted from a button of '#type' => 'submit'
will be passed to their corresponding submit function if it is available.
example:
<?php function test_form_submit($form, &$form_state) { db_query("INSERT INTO {table} (name, log, hidden) VALUES ('%s', %d, '%s')", $form_state['values']['name'], $form_state['values']['access']['log'], $form_state['values']['hidden']); drupal_set_message(t('Your form has been saved.')); } ?>
Несколько замечаний:
- Функция отправки вызывается только в том случае, если существует значение кнопки submit в массиве $_POST, и форма прошла проверку (валидацию).
- Массив
$form_state['values']
НЕ использует обычно иерархическую структуру в таком виде, как она определена в массиве$form
(Из-за flattening, обсуждавшегося ранее), so be aware of what arrays have been flattened, and what arrays have retained their hierarchy by use of the'#tree' => TRUE
attribute. notice above that 'statistics_enable_access_log' belongs to a tree'd array, and the full array structure must be used to access the value. - If a form has a submit function, then hidden form values are not needed. Instead, any values that you need to pass to
$form_state['values']
can be declared in the$form
array as such:
<?php $form['foo'] = array('#type' => 'value', '#value' => 'bar') ?>
This is accessed in
$form_state['values']['foo']
, with a value of bar. This method is preferred because the values are not sent to the browser. - To determine where the user should be sent after the form is processed, the _submit function can place a path or URL in
$form_state['redirect']
which will be the target of a drupal_goto; every form is redirected after a submit. If you store nothing in$form_state['redirect']
, the form will simply be redirected to itself after a submit. It is polite to usedrupal_set_message()
to explain to the user that the submission was successful.
Понимание процесса
An important concept with Forms API compared to using raw HTML forms (as in Drupal 4.6 and before) is that the drupal_get_form()
function handles both presenting and responding to the form. What this means is that the $form array you construct in your function will be built first when the form is presented, and again when the form is submitted.
The practical upshot to this is that many developers immediately find themselves asking the question of "where does my data get stored?". The answer is simply that it doesn't. You put your $form data together, perhaps loading your object from the database and filling in #default_values, the form builder then checks this against what was posted. What you gain from this, however, is that the FormsAPI can deal with your data securely. Faking a POST is much harder since it won't let values that weren't actually on the form come through to the $form_state['values'] in your submit function, and in your 'select' types, it will check to ensure that the value actually existed in the select and reject the form if it was not. In addition, Drupal adds, by default, a security token to each form that will protect against cross-site forgery.
- Войдите или зарегистрируйтесь, чтобы получить возможность отправлять комментарии
жаль что переведено мало :(
Учите латынь! На том свете с Вами по-русски никто разговаривать не будет! (с)
Спасибо за перевод. В некоторых местах стало доступнее для понимания.