
Этот урок создан специально для начинающих и средне-продвинутых Друпал-разработчиков. Он должен быстро дать понятие об азах Forms API, а также показать возможность создаия более сложных вещей на примере пошаговых форм (№8).
Когда я только начинал подготовку этого урока, у меня был соблазн поставить под каждым куском кода ссылку для скачивания готового примера, но в послествии, я отказался от этого. Будет намного полезнее, если вы сами будете вставлять код в свои модули, тестируя и набираясь опыта в реальных условиях.
И прежде чем начать, я расскажу вам как все-таки заставить любой из этих кусков кода работать. Предположим, вы уже имеете установленный тестовый сайт на Друпал 6. Вам прийдется проделать следующие действия:
name = My module
description = Module for form api tutorial
core = 6.x
<?php
// Здесь мы просто говорим Друпалу, что по адресу 'my_module/form' должен
// выводиться результат работы функции 'my_module_form' (см. ниже), но только
// если пользователь имеет права просмотра содержимого сайта ('access content').
// Особо любопытным — читать описание хука hook_menu() здесь:
// http://api.drupal.ru/api/function/hook_menu
function my_module_menu() {
$items = array();
$items['my_module/form'] = array(
'title' => 'My form',
'page callback' => 'my_module_form',
'access arguments' => array('access content'),
'description' => 'My form',
'type' => MENU_CALLBACK,
);
return $items;
}
// Эта функция вызывается каждый раз, когда мы посещаем страницу 'my_module/form'.
// Функция генерирует и возвращает нашу форму.
function my_module_form() {
// Форма конструируется при помощи функции drupal_get_form(),
// в которую нам нужно передать название "функции-строителя" формы.
return drupal_get_form('my_module_my_form');
}
// Функция-строитель нашей формы.
// Notice it takes one argument, the $form_state
function my_module_my_form($form_state) {
// Наш первый элемент формы — тестовое поле с заголовком "Name".
// Обратите внимание, что 'Name' обернуто в функцию t(). Это
// обеспечит дальнейший перевод слова 'Name', чтобы, например,
// при включенной русской локализации поле называлось 'Имя'.
$form['name'] = array(
'#type' => 'textfield',
'#title' => t('Name'),
);
return $form;
}
<?php
function my_module_menu() {
$items = array();
$items['my_module/form'] = array(
'title' => 'My form',
'page callback' => 'my_module_form',
'access arguments' => array('access content'),
'description' => 'My form',
'type' => MENU_CALLBACK,
);
return $items;
}
function my_module_form() {
return drupal_get_form('my_module_my_form');
}
function my_module_my_form($form_state) {
$form['name'] = array(
'#type' => 'textfield',
'#title' => t('Name'),
);
// Добавим в форму простую кнопку отправки. Обратите внимание на то,
// что при нажатии на кнопку, вы вернетесь обратно на форму, а все ее
// поля будут очищены. Это стандартное поведение форм.
$form['submit'] = array(
'#type' => 'submit',
'#value' => t('Submit'),
);
return $form;
}
<?php
function my_module_menu() {
$items = array();
$items['my_module/form'] = array(
'title' => 'My form',
'page callback' => 'my_module_form',
'access arguments' => array('access content'),
'description' => 'My form',
'type' => MENU_CALLBACK,
);
return $items;
}
function my_module_form() {
return drupal_get_form('my_module_my_form');
}
function my_module_my_form($form_state) {
// Мы создаем элемент "набор полей" и помещаем в него два текстовых
// поля — для имени, фамилии и отчества.
//
// При внимательном рассмотрении этого кода, вы можете заметить, что имя, фамилия
// и отчество объявляются как под-масссивы внутри $form['name']. Это говорит друпалу,
// что эти элементы нужно поместить внутрь набора полей 'Name'.
$form['name'] = array(
'#type' => 'fieldset',
'#title' => t('Name'),
);
$form['name']['first'] = array(
'#type' => 'textfield',
'#title' => t('First name'),
);
$form['name']['middle'] = array(
'#type' => 'textfield',
'#title' => t('Middle name'),
);
$form['name']['last'] = array(
'#type' => 'textfield',
'#title' => t('Last name'),
);
$form['submit'] = array(
'#type' => 'submit',
'#value' => t('Submit'),
);
return $form;
}
<?php
function my_module_menu() {
$items = array();
$items['my_module/form'] = array(
'title' => 'My form',
'page callback' => 'my_module_form',
'access arguments' => array('access content'),
'description' => 'My form',
'type' => MENU_CALLBACK,
);
return $items;
}
function my_module_form() {
return drupal_get_form('my_module_my_form');
}
function my_module_my_form($form_state) {
// Делаем набор полей распахивающимся
$form['name'] = array(
'#type' => 'fieldset',
'#title' => t('Name'),
'#collapsible' => TRUE, // распахивающийся
'#collapsed' => FALSE, // и не схопнутый по-умолчанию
);
// Делаем эти поля обязательными
$form['name']['first'] = array(
'#type' => 'textfield',
'#title' => t('First name'),
'#required' => TRUE, // добавлено обязательное заполнение
);
$form['name']['middle'] = array(
'#type' => 'textfield',
'#title' => t('Middle name'),
'#required' => TRUE, // добавлено обязательное заполнение
);
$form['name']['last'] = array(
'#type' => 'textfield',
'#title' => t('Last name'),
'#required' => TRUE, // добавлено обязательное заполнение
);
$form['submit'] = array(
'#type' => 'submit',
'#value' => t('Submit'),
);
return $form;
}
<?php
function my_module_menu() {
$items = array();
$items['my_module/form'] = array(
'title' => 'My form',
'page callback' => 'my_module_form',
'access arguments' => array('access content'),
'description' => 'My form',
'type' => MENU_CALLBACK,
);
return $items;
}
function my_module_form() {
return drupal_get_form('my_module_my_form');
}
function my_module_my_form($form_state) {
$form['name'] = array(
'#type' => 'fieldset',
'#title' => t('Name'),
'#collapsible' => TRUE,
'#collapsed' => FALSE,
);
// Демонстрация дополнительных аттрибутов текстовых полей.
//
// Полный список элементов форм и их аттрибутов можно глянуть здесь:
// http://api.drupal.ru/api/file/developer/topics/forms_api_reference.html
//
// Обратите внимание, что в аттрибутах типа #description следует
// стараться использовать английские значения, обернутые в t().
// Это облегчит вам дальнейшую жизнь, если в иной день вы
// захотите сделать многоязычную версию сайта. Если же вы уверены,
// что локализации не будет, или ваш английский оставляет желать лучшего,
// совсем не запрещено указывать там значения прямо на русском.
// Однако, в этом случае КАТЕГОРИЧЕСКИ ЗАПРЕЩЕНО оборачивать их в t().
$form['name']['first'] = array(
'#type' => 'textfield',
'#title' => t('First name'),
'#required' => TRUE,
'#default_value' => t("First name"), // добавлено значение по-умолчанию
'#description' => t("Please enter your first name."), // добавлена подпись
'#size' => 20, // добавлена ширина поля
'#maxlength' => 20, // добавлена максимальная длина строки ввода
);
$form['name']['middle'] = array(
'#type' => 'textfield',
'#title' => t('Middle name'),
'#required' => TRUE,
);
$form['name']['last'] = array(
'#type' => 'textfield',
'#title' => t('Last name'),
'#required' => TRUE,
);
$form['submit'] = array(
'#type' => 'submit',
'#value' => t('Submit'),
);
return $form;
}
<?php
function my_module_menu() {
$items = array();
$items['my_module/form'] = array(
'title' => 'My form',
'page callback' => 'my_module_form',
'access arguments' => array('access content'),
'description' => 'My form',
'type' => MENU_CALLBACK,
);
return $items;
}
function my_module_form() {
return drupal_get_form('my_module_my_form');
}
function my_module_my_form($form_state) {
$form['name'] = array(
'#type' => 'fieldset',
'#title' => t('Name'),
'#collapsible' => TRUE,
'#collapsed' => FALSE,
);
$form['name']['first'] = array(
'#type' => 'textfield',
'#title' => t('First name'),
'#required' => TRUE,
'#default_value' => t("First name"),
'#description' => t("Please enter your first name."),
'#size' => 20,
'#maxlength' => 20,
);
$form['name']['middle'] = array(
'#type' => 'textfield',
'#title' => t('Middle name'),
'#required' => TRUE,
);
$form['name']['last'] = array(
'#type' => 'textfield',
'#title' => t('Last name'),
'#required' => TRUE,
);
// Новое поле — год рождения. Мы произведем проверку значения
// этого поля в функции валидации формы.
$form['year_of_birth'] = array(
'#type' => 'textfield',
'#title' => t('Year of birth'),
'#description' => t('Format is "YYYY"'),
);
$form['submit'] = array(
'#type' => 'submit',
'#value' => t('Submit'),
);
return $form;
}
// Добавляем функцию валидации формы. В ней мы будем проверять
// значение поля "год рождения", чтобы быть уверенными, что оно
// находится между 1900 и 2000. Если нет, будет выбрасываться ошибка.
//
// Обратите внимание на название функции. Это просто название
// функции-строителя формы с '_validate' на конце. Названная таким
// образом функция, будет служить валидатором формы.
function my_module_my_form_validate($form, &$form_state) {
$year_of_birth = $form_state['values']['year_of_birth'];
if (!$year_of_birth || ($year_of_birth < 1900 || $year_of_birth > 2000)) {
form_set_error('year_of_birth', t('Enter a year between 1900 and 2000.'));
}
}
<?php
function my_module_menu() {
$items = array();
$items['my_module/form'] = array(
'title' => 'My form',
'page callback' => 'my_module_form',
'access arguments' => array('access content'),
'description' => 'My form',
'type' => MENU_CALLBACK,
);
return $items;
}
function my_module_form() {
return drupal_get_form('my_module_my_form');
}
function my_module_my_form($form_state) {
$form['name'] = array(
'#type' => 'fieldset',
'#title' => t('Name'),
'#collapsible' => TRUE,
'#collapsed' => FALSE,
);
$form['name']['first'] = array(
'#type' => 'textfield',
'#title' => t('First name'),
'#required' => TRUE,
'#default_value' => t("First name"),
'#description' => t("Please enter your first name."),
'#size' => 20,
'#maxlength' => 20,
);
$form['name']['middle'] = array(
'#type' => 'textfield',
'#title' => t('Middle name'),
'#required' => TRUE,
);
$form['name']['last'] = array(
'#type' => 'textfield',
'#title' => t('Last name'),
'#required' => TRUE,
);
$form['year_of_birth'] = array(
'#type' => 'textfield',
'#title' => t('Year of birth'),
'#description' => t('Format is "YYYY"'),
);
$form['submit'] = array(
'#type' => 'submit',
'#value' => t('Submit'),
);
return $form;
}
function my_module_my_form_validate($form, &$form_state) {
$year_of_birth = $form_state['values']['year_of_birth'];
if (!$year_of_birth || ($year_of_birth < 1900 || $year_of_birth > 2000)) {
form_set_error('year_of_birth', t('Enter a year between 1900 and 2000.'));
}
}
// Правило создание обработчика почти такое же как и в случае с валидатором.
// Отличие только в том, что вместо '_validate', к названию функции-строителя
// нужно добавить '_submit'.
//
// Обычно в обработчике стоит что-то делать с данными формы, (которые поступают
// поступают внутри переменной $form_state), например, сохранить их в базу.
// Но сейчас мы ограничимся только лишь выводом сообщения на экран, чтобы
// вы убедились, что обработчик действительно работает.
function my_module_my_form_submit($form, &$form_state) {
drupal_set_message(t('The form has been submitted.'));
}
<?php
function my_module_menu() {
$items = array();
$items['my_module/form'] = array(
'title' => 'My form',
'page callback' => 'my_module_form',
'access arguments' => array('access content'),
'description' => 'My form',
'type' => MENU_CALLBACK,
);
return $items;
}
function my_module_form() {
return drupal_get_form('my_module_my_form');
}
function my_module_my_form($form_state) {
$form['name'] = array(
'#type' => 'fieldset',
'#title' => t('Name'),
'#collapsible' => TRUE,
'#collapsed' => FALSE,
);
// Мы убрали базовую валидацию (#required) у всех полей, чтобы
// проверять их только в валидаторе формы.
$form['name']['first'] = array(
'#type' => 'textfield',
'#title' => t('First name'),
'#default_value' => t("First name"),
'#description' => t("Please enter your first name."),
'#size' => 20,
'#maxlength' => 20,
);
$form['name']['middle'] = array(
'#type' => 'textfield',
'#title' => t('Middle name'),
);
$form['name']['last'] = array(
'#type' => 'textfield',
'#title' => t('Last name'),
);
$form['year_of_birth'] = array(
'#type' => 'textfield',
'#title' => t('Year of birth'),
'#description' => t('Format is "YYYY"'),
);
$form['submit'] = array(
'#type' => 'submit',
'#value' => t('Submit'),
);
// Добавляем кнопку очистки формы. Свойство #validate приказывает
// форме использовать особые валидаторы при нажатии данной
// кнопки, вместо стандартного.
$form['clear'] = array(
'#type' => 'submit',
'#value' => t('Reset form'),
'#validate' => array('my_module_my_form_clear'),
);
return $form;
}
// Добавляем проверку наших полей здесь вместо стандартной базовой.
// Теперь, если пользователь не заполнит поле, он получит сообщение
// об ошибке, а незаполеннное поле отметится красным.
// Если бы мы оставили прежний способ проверки значений (#required),
// то получали бы ошибки тогда, когда пользователь жал бы кнопку Reset
// на форме с пустыми значениями. Это потому, что базовые валидаторы
// выполняются перед валидаторами полей и формы.
function my_module_my_form_validate($form, &$form_state) {
$year_of_birth = $form_state['values']['year_of_birth'];
$first_name = $form_state['values']['first'];
$middle_name = $form_state['values']['middle'];
$last_name = $form_state['values']['last'];
if (!$first_name) {
form_set_error('first', t('Please enter your first name.'));
}
if (!$middle_name) {
form_set_error('middle', t('Please enter your middle name.'));
}
if (!$last_name) {
form_set_error('last', t('Please enter your last name.'));
}
if (!$year_of_birth || ($year_of_birth < 1900 || $year_of_birth > 2000)) {
form_set_error('year_of_birth', t('Enter a year between 1900 and 2000.'));
}
}
function my_module_my_form_submit($form, &$form_state) {
drupal_set_message(t('The form has been submitted.'));
}
// Новый валидатор для кнопки Reset. Выставляя здесь значение $form_state['rebuild']
// в TRUE, мы даем указание пропускать обработчик формы. А пропуская обработчик,
// мы выполняем стандартное действией формы — возвращаемся на нее же с пустыми
// значениями (см пример 2).
function my_module_my_form_clear($form, &$form_state) {
$form_state['rebuild'] = TRUE;
}
<?php
function my_module_menu() {
$items = array();
$items['my_module/form'] = array(
'title' => 'My form',
'page callback' => 'my_module_form',
'access arguments' => array('access content'),
'description' => 'My form',
'type' => MENU_CALLBACK,
);
return $items;
}
function my_module_form() {
return drupal_get_form('my_module_my_form');
}
function my_module_my_form($form_state) {
$form['name'] = array(
'#type' => 'fieldset',
'#title' => t('Name'),
'#collapsible' => TRUE,
'#collapsed' => FALSE,
);
// Мы изменяем значение по-умолчанию с расчетом на то,
// что на втором шаге формы здесь должны быть ранее
// заполненные значения, а не приглашение к заполнению.
$form['name']['first'] = array(
'#type' => 'textfield',
'#title' => t('First name'),
'#default_value' => $form_state['values']['first'], // изменено
'#description' => t("Please enter your first name."),
'#size' => 20,
'#maxlength' => 20,
);
$form['name']['middle'] = array(
'#type' => 'textfield',
'#title' => t('Middle name'),
'#default_value' => $form_state['values']['middle'], // добавлено
);
$form['name']['last'] = array(
'#type' => 'textfield',
'#title' => t('Last name'),
'#default_value' => $form_state['values']['last'], // добавлено
);
$form['year_of_birth'] = array(
'#type' => 'textfield',
'#title' => t('Year of birth'),
'#description' => t('Format is "YYYY"'),
'#default_value' => $form_state['values']['year_of_birth'], // добавлено
);
// Добавляем новые поля на форму
if (isset($form_state['storage']['new_name'])) { // Это поле заполняется
// при нажатии кнопки "Add new name"
$form['name2'] = array(
'#type' => 'fieldset',
'#title' => t('Name #2'),
'#collapsible' => TRUE,
'#collapsed' => FALSE,
);
$form['name2']['first2'] = array(
'#type' => 'textfield',
'#title' => t('First name'),
'#description' => t('Please enter your first name.'),
'#size' => 20,
'#maxlength' => 20,
'#default_value' => $form_state['values']['first2'],
);
$form['name2']['middle2'] = array(
'#type' => 'textfield',
'#title' => t('Middle name'),
'#default_value' => $form_state['values']['middle2'],
);
$form['name2']['last2'] = array(
'#type' => 'textfield',
'#title' => t('Last name'),
'#default_value' => $form_state['values']['last2'],
);
$form['year_of_birth2'] = array(
'#type' => 'textfield',
'#title' => t('Year of birth'),
'#description' => t('Format is "YYYY"'),
'#default_value' => $form_state['values']['year_of_birth2'],
);
}
$form['submit'] = array(
'#type' => 'submit',
'#value' => t('Submit'),
);
$form['clear'] = array(
'#type' => 'submit',
'#value' => t('Reset form'),
'#validate' => array('my_module_my_form_clear'),
);
// Мы добавляем кнопку "Add another name" только если она еще
// не была кликнута (т.е. на первом шаге). У кнопкий свой валидатор.
if (empty($form_state['storage']['new_name'])) { // Это поле заполняется
// при нажатии кнопки "Add new name"
$form['new_name'] = array(
'#type' => 'submit',
'#value' => t('Add another name'),
'#validate' => array('my_module_my_form_new_name'),
);
}
return $form;
}
// В этом валидаторе мы выставляем значение $form_state['storage']['new_name'],
// чтобы потом в функции-строителе определить, что форма была нажата.
// На этом этапе вы могли уже и заметить, что переменная $form_state
// передается по ссылке, а это означает, что ее изменения будут видны
// и из функции строителя или из обработчика, который обычно выполняется
// после валидатора.
function my_module_my_form_new_name($form, &$form_state) {
$form_state['storage']['new_name'] = TRUE;
$form_state['rebuild'] = TRUE; // Как вы помните, выставление этой
// переменной повлечет перестройку формы.
// Однако текущий $form_state будет доступен
// в функции-строителе.
}
function my_module_my_form_clear($form, &$form_state) {
unset($form_state['values']); // Принудительно очищаем возможные
unset($form_state['storage']); // значения в памяти.
$form_state['rebuild'] = TRUE;
}
// Добавляем дополнительную логику проверки новых полей.
function my_module_my_form_validate($form, &$form_state) {
$year_of_birth = $form_state['values']['year_of_birth'];
$first_name = $form_state['values']['first'];
$middle_name = $form_state['values']['middle'];
$last_name = $form_state['values']['last'];
if (!$first_name) {
form_set_error('first', t('Please enter your first name.'));
}
if (!$middle_name) {
form_set_error('middle', t('Please enter your middle name.'));
}
if (!$last_name) {
form_set_error('last', t('Please enter your last name.'));
}
if (!$year_of_birth || ($year_of_birth < 1900 || $year_of_birth > 2000)) {
form_set_error('year_of_birth', t('Enter a year between 1900 and 2000.'));
}
if ($form_state['storage']['new_name']) {
$year_of_birth = $form_state['values']['year_of_birth2'];
$first_name = $form_state['values']['first2'];
$middle_name = $form_state['values']['middle2'];
$last_name = $form_state['values']['last2'];
if (!$first_name) {
form_set_error('first2', t('Please enter your first name.'));
}
if (!$middle_name) {
form_set_error('middle2', t('Please enter your middle name.'));
}
if (!$last_name) {
form_set_error('last2', t('Please enter your last name.'));
}
if (!$year_of_birth || ($year_of_birth < 1900 || $year_of_birth > 2000)) {
form_set_error('year_of_birth2', t('Enter a year between 1900 and 2000.'));
}
}
}
// Если закномментировать вызов unset() ниже, после добавления на
// форму новых элементов и последующей ее отправки, вы заметите,
// что форма не очистится, так как в $form_state['storage'] будет
// присутствовать выставленное ранее значение. Поэтому, нам нужно
// принудительно очистить буфер значений формы.
function my_module_my_form_submit($form, &$form_state) {
unset($form_state['storage']);
drupal_set_message(t('The form has been submitted.'));
}
<?php
function my_module_menu() {
$items['my_module/form'] = array(
'title' => 'My form',
'page callback' => 'my_module_form',
'access arguments' => array('access content'),
'description' => 'My form',
'type' => MENU_CALLBACK,
);
return $items;
}
function my_module_form() {
return drupal_get_form('my_module_my_form');
}
// Добавляем дополнительную логику в нашу функцию строитель,
// чтобы обеспечить разделение на в две страницы. Мы проверяем
// значение в $form_state['storage'], чтобы узнать какую страницу
// нужно отображать в данный момент.
function my_module_my_form($form_state) {
// Показываем страницу-2, если выставлено значение $form_state['storage']['page_two']
if (isset($form_state['storage']['page_two'])) {
return my_module_my_form_page_two();
}
// Страница-1 отображается, если не выставленно значение $form_state['storage']['page_two']
$form['name'] = array(
'#type' => 'fieldset',
'#title' => t('Name'),
'#collapsible' => TRUE,
'#collapsed' => FALSE,
);
$form['name']['first'] = array(
'#type' => 'textfield',
'#title' => t('First name'),
'#default_value' => $form_state['values']['first'],
'#description' => t("Please enter your first name."),
'#size' => 20,
'#maxlength' => 20,
);
$form['name']['middle'] = array(
'#type' => 'textfield',
'#title' => t('Middle name'),
'#default_value' => $form_state['values']['middle'],
);
$form['name']['last'] = array(
'#type' => 'textfield',
'#title' => t('Last name'),
'#default_value' => $form_state['values']['last'],
);
$form['year_of_birth'] = array(
'#type' => 'textfield',
'#title' => t('Year of birth'),
'#description' => t('Format is "YYYY"'),
'#default_value' => $form_state['values']['year_of_birth'],
);
// Добавляем дополнительные поля имени
if (!empty($form_state['storage']['new_name'])) {
$form['name2'] = array(
'#type' => 'fieldset',
'#title' => t('Name #2'),
'#collapsible' => TRUE,
'#collapsed' => FALSE,
);
$form['name2']['first2'] = array(
'#type' => 'textfield',
'#title' => t('First name'),
'#description' => t('Please enter your first name.'),
'#size' => 20,
'#maxlength' => 20,
'#default_value' => $form_state['values']['first2'],
);
$form['name2']['middle2'] = array(
'#type' => 'textfield',
'#title' => t('Middle name'),
'#default_value' => $form_state['values']['middle2'],
);
$form['name2']['last2'] = array(
'#type' => 'textfield',
'#title' => t('Last name'),
'#default_value' => $form_state['values']['last2'],
);
$form['year_of_birth2'] = array(
'#type' => 'textfield',
'#title' => t('Year of birth'),
'#description' => t('Format is "YYYY"'),
'#default_value' => $form_state['values']['year_of_birth2'],
);
}
$form['clear'] = array(
'#type' => 'submit',
'#value' => t('Reset form'),
'#validate' => array('my_module_my_form_clear'),
);
if (empty($form_state['storage']['new_name'])) {
$form['new_name'] = array(
'#type' => 'submit',
'#value' => t('Add another name'),
'#validate' => array('my_module_my_form_new_name'),
);
}
$form['next'] = array(
'#type' => 'submit',
'#value' => t('Next »'),
);
return $form;
}
// Новая функция делает код более универсальным и управляемым.
// На втором шаге у нас будет выбор любимого цвета.
function my_module_my_form_page_two() {
$form['color'] = array(
'#type' => 'textfield',
'#title' => t('Favorite color'),
);
$form['submit'] = array(
'#type' => 'submit',
'#value' => t('Submit'),
);
return $form;
}
function my_module_my_form_new_name($form, &$form_state) {
$form_state['storage']['new_name'] = TRUE;
$form_state['rebuild'] = TRUE; // Пропускаем обработчик.
}
function my_module_my_form_clear($form, &$form_state) {
unset($form_state['values']); // Принудительно очищаем возможные
unset($form_state['storage']); // значения в памяти.
$form_state['rebuild'] = TRUE;
}
// Добавляем валидацию второго шага.
function my_module_my_form_validate($form, &$form_state) {
// Здесь делаем проверку второй страницы.
if (isset($form_state['storage']['page_two'])) {
$color = $form_state['values']['color'];
if (!$color) {
form_set_error('color', t('Please enter a color.'));
}
return;
}
$year_of_birth = $form_state['values']['year_of_birth'];
$first_name = $form_state['values']['first'];
$middle_name = $form_state['values']['middle'];
$last_name = $form_state['values']['last'];
if (!$first_name) {
form_set_error('first', t('Please enter your first name.'));
}
if (!$middle_name) {
form_set_error('middle', t('Please enter your middle name.'));
}
if (!$last_name) {
form_set_error('last', t('Please enter your last name.'));
}
if (!$year_of_birth || ($year_of_birth < 1900 || $year_of_birth > 2000)) {
form_set_error('year_of_birth', t('Enter a year between 1900 and 2000.'));
}
if ($form_state['storage']['new_name']) {
$year_of_birth = $form_state['values']['year_of_birth2'];
$first_name = $form_state['values']['first2'];
$middle_name = $form_state['values']['middle2'];
$last_name = $form_state['values']['last2'];
if (!$first_name) {
form_set_error('first2', t('Please enter your first name.'));
}
if (!$middle_name) {
form_set_error('middle2', t('Please enter your middle name.'));
}
if (!$last_name) {
form_set_error('last2', t('Please enter your last name.'));
}
if (!$year_of_birth || ($year_of_birth < 1900 || $year_of_birth > 2000)) {
form_set_error('year_of_birth2', t('Enter a year between 1900 and 2000.'));
}
}
}
// Изменяем обработчик так, чтобы он правильно работал в зависимости
// от того, на каком шаге была отправлена форма. Если мы на первом шаге,
// то устанавливаем $form_state['storage']['page_two'], после чего форма
// перегрузится и будет знать, что нужно отображать второй шаг.
// Если же форма была отправлена на втором шаге, то следует показать
// пользователю сообщение об успешном завершении операции и
// переместить его на другую страницу.
function my_module_my_form_submit($form, &$form_state) {
// Обработка первого шага
if ($form_state['clicked_button']['#id'] == 'edit-next') {
$form_state['storage']['page_two'] = TRUE; // Устанавливаем флаг для функции-строителя
// Запихиваем значения первого шага в $form_state['storage'],
// чтобы иметь к ним доступ в финальном обработчике на втором шаге.
$form_state['storage']['page_one_values'] = $form_state['values'];
}
// Второй шаг, финальный обработчик.
else {
// Как я уже говорил, на этом этапе обычно производится сохранение
// данный в базу данных. Но мы ограничимся показом сообщения об
// успешном завершении операции.
drupal_set_message(t('Your form has been submitted'));
// Это значение должно быть очищено, чтобы редирект состоялся.
// Дело в том, что если оно не пустое, $form_state['rebuild'] автоматически
// устанавливается в TRUE (см. первый пункт документации:
// http://drupal.org/node/144132
unset ($form_state['storage']);
// Отправляем пользователя на главную страницу
$form_state['redirect'] = '';
}
}
Я старался сильно не отходить от оригинала, исправляя только мелкие неточности и ошибки. Однако, замечу, что с приходом шестого друпала, нет никакой необходимости пихать все проверки в общий валидатор формы. Для каждого элемента можно определять свою функцию-валидатор. Например:
$form['year_of_birth'] = array(
'#type' => 'textfield',
'#title' => t('Year of birth'),
'#description' => t('Format is "YYYY"'),
'#default_value' => $form_state['values']['year_of_birth'],
'#element_validate' => array('is_valid_year'),
);
// ...
function is_valid_year($form_element, &$form_state) {
$year_of_birth = $form_element['#value'];
if (!$year_of_birth || ($year_of_birth < 1900 || $year_of_birth > 2000)) {
form_set_error($form_element['#name'], t('Enter a year between 1900 and 2000.'));
}
}
Но стоит помнить, что валидаторы элементов выполняются после валидатора формы.
Кроме того, возможно, кто-то спросит, почему не оборачиваются в t() заголовок и описание пункта меню. Отвечаю заранее — в шестом друпале это делается автоматически.
# | Nikit
Сокращення форма:
function my_module_menu() {
$items = array();
$items['my_module/form'] = array(
'title' => 'My form',
'page callback' => 'drupal_get_form',
'page arguments' => array('my_module_form'),
'access arguments' => array('access content'),
'description' => 'My form',
'type' => MENU_CALLBACK,
);
return $items;
}
function my_module_form($form_state) {
$form['name'] = array(
'#type' => 'textfield',
'#title' => t('Name'),
);
return $form;
}
# | Dobryak
Вопрос относительно нескольких форм на одной странице? Или наоборот... одна и та же форма но один из параметров будет иметь другое значение? как тут быть... не делать же 10 разных форм?
Несколько на одной странице:
// ...
// (это коллбэк меню)
function my_module_form() {
$output .= drupal_get_form('my_module_my_form1');
$output .= drupal_get_form('my_module_my_form2');
return $output;
}
//...
function my_module_my_form1($form_state) {
// определяем первую форму
}
function my_module_my_form2($form_state) {
// определяем вторую форму
}Реюзаем одну форму с параметрами:
function my_module_menu() {
$items = array();
// как обычно
$items['my_module/form'] = array(
'title' => 'My form',
'page callback' => 'drupal_get_form',
'page arguments' => array('my_module_my_form),
'access arguments' => array('access content'),
'description' => 'My form',
'type' => MENU_CALLBACK,
);
// допустим, нужно забрать из адреса второй аргумент
// (в динамических местах следует ставить процентик)
// Здесь, если юзер пойдет по адресу my_module/form/my_var, то
// получит на вход функции формы строку 'my_var'.
$items['my_module/form/%'] = array(
'title' => 'My form',
'page callback' => 'drupal_get_form',
'page arguments' => array('my_module_my_form', 2), // двойка означает второй аргумент из адреса
'access arguments' => array('access content'),
'description' => 'My form',
'type' => MENU_CALLBACK,
);
// или можно еще с загрузчиком, но это уже чуть более высшие материи ;)
// Здесь, если юзер пойдет по адресу my_module/form/23, то
// если существует нода с айдишником 23, она будет загружена и
// полный объект передасться в функцию формы.
$items['my_module/form/%node'] = array(
'title' => 'My form',
'page callback' => 'drupal_get_form',
'page arguments' => array('my_module_my_form', 2), // двойка означает второй аргумент из адреса
'access arguments' => array('access content'),
'description' => 'My form',
'type' => MENU_CALLBACK,
);
return $items;
}
// В параметры этой функции после $form_state будет подаваться все,
// что вы передете из коллбэка меню, т.е. грубо говоря все из массива
// 'page arguments' что после 'my_module_my_form'.
//
// '= NULL' нужно для того, чтобы форма работала, если в нее ничего
// не передается
function my_module_my_form($form_state, $custom_param = NULL) {
// ...
}Поцтветка почему-то на этом примере обломалась, но, надеюсь, и так понятно.
# | fog!
Спасибо большое за разъяснение. Очень полезно.
Было бы еще круто если б было что-то наподобие объяснения того как потом данные формы выводить (допустим в виде гостевой), и какие для этого нужны хуки.
В submit-функции у вас в $form_state['values'] храняться все значения, введенные юзером. В примерах четко написано, что на том этапе следует сохранять данные в базу. Поэтому, ваш вопрос "как потом данные формы выводить", следует перефразировать на "как потом данные из базы выводить", на который уже можно ответить в другой статье. Сейчас же могу посоветовать глянуть вот на этот пример. Там хоть и на английском, но при большом желании, можно понять что к чему.
# | fog!
ну логично что данные будут выводиться из базы, в которую мы их запихнем.
согласен с тем что это уже сабж другой статьи)
спасибо за ссылку)
Подскажите - можно ли в зависимости от роли пользователя и прав этой роли - форму генерировать по разному?
Я пишу небольшой модуль. Например - есть два юзера - Вася и Коля. Оба - зарегистрированные пользователи, но Коля - ещё обладает ролью "Ext form" у которой стоит галка напротив - "Show extended form" в моём модуле который называется "spec_forms". И вот Коля должен видеть более развернутую форму с доп. полями.
Как это сделать? Это надо как-то функции которая генерит форму - передавать что у Коли есть соответствующая роль и в ней включена "Show extended form". Но как это сделать, не совсем понятно... :(
Как-то так, сорри за косноязычность... Заранее спасибо!
В конструкторе формы можно задать все возможные поля, а на hook_form_alter() делать ансет (unset() ) ненужных конкретному пользователю полей. Либо делать этот ансет в темизации формы. Чтобы определить, какие поля ансетить, вам надо проверить роль пользователя прямо внутри условия - определите объект global $user, в нем есть поле $user->roles - этой массив ролей, которые назначены пользователю. Смотрите, какие в нем роли, и соответственное изменяете форму.
Спасибо огромное!
Я ещё покопался и использовал функции друпала типа user_..., а конкретно user_access. А с массивом попозже нашёл, тоже здорово :)
# | fog!
спасибо еще раз за ссылку, с выводом разобрался.
еще вот интересен был бы пример того, как реализовать аяксовые зависимые select поля, чтоб не перезагружать страницу несколько раз.
# | kinosura
позвольте отослать вас к следующему примеру (используется jQuery): http://www.ajaxray.com/blog/2007/11/08/jquery-controlled-dependent-or-ca... здесь же найдёте демо. а вот конкретное использование описано в предыдущей версии вот здесь: http://php4bd.wordpress.com/2007/07/15/javascript-controlled-dependent-o...
# | Фантик
Спасибо за статью, а если нужно на странице (с другим адресом, но объявленной в том же модуле) вывести данные из $form_state['values'], что делать? использовать таблицы при этом не хочется?
Сначала сохранить в базу $form_state['values'], потом вывести из базы на другой странице.
Статья хорошая. Могу добавить следующее: можно жестко указать порядок элементов в форме, указывая у них свойство #weight. Чем меньше значение, тем выше элемент. Это используется, например, в тех случаях, когда в вашей форме присутствуют элементы заданные сторонними модулями, имеющие фиксированный #weight, т.о. они отображаются не там, где бы вам хотелось.
Есть еще такая полезная функция, как theme_form(). Её используют для того, чтобы изменить стандартный друпаловский вид формы, типа "накидано всё подряд". Можно определить любое расположение элементов для формы, скажем, ввиде таблицы или просто нескольких колонок - в общем, как требует дизайн.
Для примера, допустим мы определили такую форму:
function my_module_my_form($form_state) {
$form['description'] = array(
'#type' => 'item',
'#value' => t('Enter your cridentials:'),
);
$form['first'] = array(
'#type' => 'textfield',
'#title' => t('First name'),
);
$form['middle'] = array(
'#type' => 'textfield',
'#title' => t('Middle name'),
);
$form['last'] = array(
'#type' => 'textfield',
'#title' => t('Last name'),
);
$form['submit'] = array(
'#type' => 'submit',
'#value' => t('Submit'),
);
return $form;
}
Нам нужно, чтобы она выводилась ввиде таблички: сначала строка с элементом описания, потом с тремя элементами имени и третья - с кнопкой.
Пишем функцию темизации (Drupal 5):
function theme_my_module_my_form($form)
{
$output = drupal_render($form["description"]);
$output .= '<table class="my_module_form">';
$output .= '<tr><td>'. drupal_render($form["first"]) .'</td><td>'. drupal_render($form["middle"]) .'</td><td>'. drupal_render($form["last"]) .'</td></tr>';
$output .= '<tr><td>'. drupal_render($form["submit"]) .'</td></tr>';
$output .= '</table>';
$output .= drupal_render($form);
return $output;
}
Последний вызов drupal_render($form) вообще нужен, чтобы сгенерировались все не указанные явно элементы. Мы могли задать больше элементов в форме, и не темизировать их в ручную, тогда они бы сами появились на форме при вызове этой функции.
Еще можно добавить про такой полезный параметр, как $form["#redirect] - с помощью него можно явно указать, на какую страницу нужно перейти после сабмита формы.
Темизация форм была описана мною ранее. За остальное спасибо, будет полезно незнающим.
# | digital_sword
Строка $items['my_module/form'] = array( . Если заменить 'my_module/form' на что угодно, например на на user/1 форма все равно будет выводиться на странице my_module/form. Как вывести ее на другой странице?
# | digital_sword
update.php. А как сделать так, чтобы форма добавилась к элементам? (в случае указания $items['user'] она просто окажется по этому адресу, а то, что там раньше было не покажется.
# | digital_sword
А как сделать так, чтобы форма добавилась к элементам на уже существующей странице?
И в блок как ее засунуть?
# | Dobryak
И все-таки хотел уточнить относительно нескольких форм на странице
т.е. на странице надо 5 форм
отличаются они только скрытым значением. Как нибудь это возможно сделать.
Или писать именно пять форм, просто они будут идентичны.
Хочу сказать насчёт примера номер 10. Ваш код в десятом примере не работает - я долго не мог понять почему, а потом натолкнулся вот на это место
<?php
$form['next'] = array(
'#type' => 'submit',
'#value' => t('Next »'),
);
return $form;
?>
Что же здесь неверно? А дело в том, что значение #value должно быть записано вот так вот: '#value' => t('Next >>'), а не так, как у Вас '#value' => t('Next »'),
Вот такая вот мелочь совершеннейшая - но и-за неё в десятом примере не работает вообще ничего, даже валидация самой первой формы.
Вот. Но это конечно мелочь. Ваш пост очень классный.
да я понимаю. Но пример из оригинала данной статьи у меня ведь работает, даже и в файле не в UTF-8.
ну вообще да - это скорее я виноват.
Отличная статья. После работы с ZEND_FORM, Drupal forms мне тоже нравятся. Вот только как вставить обработку яваскриптом? подобно в зенде :
->setAttrib('onClick', "if (!checker1()) return false;");
Думаю найду. Иду читать дальше.
$form['element']['#attributes']['onClick'] = "if (!checker1()) return false;";# | Макс
Здравствуйте!
Подскажите, пожалуйста, как сделать такой же редирект после сабмита, но в друпале 5? Уже гугл замучал, но не могу найти ничего.
По идее, стоит всего лишь возвратить путь, например:
return 'your/url';Но это не всегда работает, и бывает приходится писать такое:
drupal_goto('your/url');# | rodman
Отличная статья! Спасибо за то что вы помогаете разобраться во всех тонкостях Drupal.
Пример №6:
Добавление нового элемента - сразу подумал что будет рассмотрена реализация hook_elements(), т.е. создание нового элемена формы.
Пример №10:
Обратите внимание, что пользователь во время работы с формой остается на том же адресе — 'my_module/form', так как по-умолчанию все это работает на POST запросах. - значения форм в друпал передается только с помощью POST запросов GET в api form не поддерживается.
Примечания:
Кроме того, возможно, кто-то спросит, почему не оборачиваются в t() заголовок и описание пункта меню. Отвечаю заранее — в шестом друпале это делается автоматически. - полагаю что не совсем так. Просто функция t() - это callback по умолчанию для title, и строка ниже добавляется автоматически когда строится форма.
'title callback' => 't ', Теоретически, форму можно использовать и с GET'ом, для этого нужно выставить:
$form['#method'] = 'get';В 90% случаев оно того не стоит и об этом умалчивают.
# | Владимир
Прекрасный материал, сильно помог разобраться, даже более того, помог понять логику drupal. Спасибо.
Далее конечно не совсем в тему материала, но возможно сможете помочь:
function my_module_my_form_submit($form, &$form_state) {
... тут мы обрабатываем переменные формы...
}
А как в этой функции работать с файлами отправляемыми той же формой?
# | muky
Если исходить из первого примера, то в функции my_module_form можно сделать так:
return drupal_get_form('my_module_my_form') . drupal_get_form('my_module_my_form_submit');То есть показываем сразу две формы. У второй формы естественно должно быть так:
$form['#attributes'] = array('enctype' => "multipart/form-data");Также в 6 друпале можно на каждую сабмит кнопку вешать свою функцию обработчик (иногда может быть удобно вместо проверки в общей функции сабмита какая кнопка нажата).
# | Владимир
form['#attributes'] = array('enctype' => "multipart/form-data");
с этим я справился, у меня проблемы с:
$_FILES["myfile"]["tmp_name"] - Имя временного файла
$_FILES["myfile"]["name"] - Имя файла на компьютере пользователя
$_FILES["myfile"]["size"] - Размер файла в байтах
$_FILES["myfile"]["type"] - MIME-тип файла
$_FILES["myfile"]["error"] - код ошибки.
не могу получить эти значения и все тут.
# | muky
Обращении к $_FILES не обязательно, file_save_upload дает результатом файловый объект - http://api.drupal.ru/api/function/file_save_upload/6 и пример http://www.computerminds.co.uk/file-uploads-drupal-6-part-1
# | muky
Пошаговая форма. В первой чекбоксами выбираются файлы (их имена если точнее, файл fb2 - xml формат). После перехода на вторую некоторые поля формы уже должны быть заполнены данными из выбранных файлов. В обработчике сабмита первой формы (выбора файлов) можно получить нужные данные из выбранных файлов и записать их в $form_state, при построении второй формы взять эти данные и заполнить ими поля. При сабмите второго шага в случае не прохода валидации данные снова беруться из $form_state, а нужно введенные пользователем (он может менять изначальные данные из файла). В общем как грамотно реализовать такую задачу?
# | dvryltsov
Очень полезные уроки!
Подскажите, как реализовать отправку на e-mail.
Или дайте ссылочку где почитать.
# | vizor
Здравствуйте помогите пожалуйста решить проблему или подскажите в какую сторону рыть.
Есть Select после нажатия кнопки надо вытащить значение этого Select-а, именно значение а не индекс. Модуль для Drupal 6.
Отличный материал, спасибо.
Поддерживаю dvryltsov не хватает шага в котором мы всю красоту введенную в форму отправляем на определенный e-mail.
# | kinosura
спасибо, отличный полезный материал!
у меня остался вопрос: я создала аяксовую форму, всё отлично работает. но теперь мне необходимо внутрь этой формы внедрить ещё одну, с её собственным сабмитом. и это не "несколько форм на странице" - это одна форма, внутри которой существует подформа с группой полей и своим сабмитом, при нажатии на который я передаю в основную форму какие-то значения и возвращаю пустую подформу.
например, мне нужно создать тип контента "Университет", а внутри него - множество "факультетов" с названием, описанием и пр полями. в форме "Университет" заполняем поля: название, описание, адрес.... и в этой же форме создаём множество факультетов, каждый раз нажимая на кнопку сабмит/добавить.
подскажите, пожалуйста, как это можно реализовать?
# | kinosura
забыла уточнить, что, вероятно, мне бы помогли модули "Subform Element" и "Related Subforms", но они написаны для Drupal 5.х, а я пытаюсь разобраться с FormAPI Drupal 6.х
# | guide
Не могли бы вы подсказать как сделать динамические формы? Скажем с кнопкой "Добавить новый элемент". При нажатии на которой добавляется новое текстовое поле. В просты скриптах я делал так:
<input type="text" name="fields[]">Затем проверял $_POST['fields'] на элементы, а тут не могу понять.
# | oleg
Здравствуйте.
Можно ли добавить возможность распечатать анкету сразу после ввода?
Очень полезная статья, особенно для новичков как я.
Еще хотелось бы увидеть один шаг, как сделать, чтобы можно было довалять "бесконечное" количество дополнительных полей, подобно fieldset name2. Например как добавляются в модуле imagefield новые поля для изображений при нажатии на кнопку "Add anover item". Желательно чтобы это работало посредством Ajax.
# | Kostyan
Спасибо большое за статью, пригодится несомненно. Удачи вам и вашим проектам!
# | анонимус
Очень хорошая статья, действительно помогла,
но появился вопрос(я новичек , не судите строго) возможно он глупый но как сделать что бы после нажатия на кнопку Sumbit эта вся информация отправлялась на два разный е-малиа?
# | Евгений
Возможно будут полезны краткие примеры, типа такого:
<?php
function my_module_form() {
return drupal_get_form('my_module_my_form');
}
function my_module_my_form($form_state) {
$form['first_name'] = array(
'#type' => 'textfield',
'#title' => t('First name'),
);
$form['submit'] = array(
'#type' => 'submit',
'#value' => t('Submit'),
);
return $form;
}
function my_module_my_form_submit($form, &$form_state) {
drupal_set_message("First name: " . $form_state['values']['first_name']);
}
Ссылки с других сайтов
Все молчат как партизаны.