Возникла недавно необходимость повторить функционал множественного поля из Field API в самописной форме, чтобы можно было добавлять значения без перезагрузки страницы. В Form API подобного функционала нет, а толковых статей с примерами я не нашёл (в документации есть пример, демонстрирующий нечто подобное, однако, там используется обновление страницы). Но не беда, всё, что нужно, можно подсмотреть в модуле Field.
Создание множественного поля будем рассматривать на примере самописной формы конфигурации. Представим, что нужна возможность задать неограниченное число текстовых значений в одной из переменных модуля.
В модуле Field нас интересует файл field.form.inc - он содержит функцию field_multiple_value_form(), которая отвечает за вывод множественных полей в форме. Так как в параметрах данной функции надо передавать информацию о поле, то использовать функцию напрямую мы не можем. Первым делом напишем итоговый вид формы конфигурации.
/**
* Example config form with multiple field.
*/
function example_config_form($form, &$form_state) {
$element_name = 'example_config_items';
$item_element = array('#type' => 'textfield');
$items = variable_get($element_name, array());
$form[$element_name] = example_multiple_value_form($element_name, $item_element, $items, $form_state);
$form['#submit'][] = 'example_config_form_submit';
return system_settings_form($form);
}
/**
* Submit callback for example_config_form().
*/
function example_config_form_submit($form, &$form_state) {
form_state_values_clean($form_state);
$items = &$form_state['values']['example_config_items'];
$items = array_values(array_filter($items));
}
За создание множественного поля будет отвечать функция example_multiple_value_form(). В качестве параметров она принимает: уникальное имя элемента, структуру построения одиночного элемента, список значений множественного поля и текущее состояние формы. Реализуем данную функцию на основе кода функции field_multiple_value_form().
/**
* Custom version of field_multiple_value_form() for Form API elements.
*
* @param string $element_name
* The element name.
* @param array $item_element
* Build array of single item.
* @param array $items
* Element items values.
* @param array $form_state
* Current form state.
*
* @return array
* Multiple element build array.
*/
function example_multiple_value_form($element_name, $item_element, $items, &$form_state) {
if (!isset($form_state['storage']['example_multiple_value_form'][$element_name])) {
// Initialize storage for element items count.
$items_count = count($items);
$form_state['storage']['example_multiple_value_form'][$element_name] = $items_count;
}
$max = $form_state['storage']['example_multiple_value_form'][$element_name];
$wrapper_id = drupal_html_id($element_name . '-add-more-wrapper');
$element = array();
for ($delta = 0; $delta <= $max; $delta++) {
$element[$delta] = $item_element;
// Set default value, if exists.
if (isset($items[$delta])) {
$element[$delta]['#default_value'] = $items[$delta];
}
}
if ($element) {
// Set "add more" button.
$element['add_more'] = array(
'#type' => 'submit',
'#name' => strtr($element_name, '-', '_') . '_add_more',
'#value' => t('Add another item'),
'#limit_validation_errors' => array(),
'#submit' => array('example_add_more_submit'),
'#ajax' => array(
'callback' => 'example_add_more_js',
'wrapper' => $wrapper_id,
'effect' => 'fade',
),
);
$element += array(
'#tree' => TRUE,
'#theme' => 'example_multiple_value_form',
'#example_multiple_value_element_name' => $element_name,
'#prefix' => '<div id="' . $wrapper_id . '">',
'#suffix' => '</div>',
'#max_delta' => $max,
);
}
return $element;
}
Ряд комментариев по вышеуказанной функции:
- Текущее количество элементов в множественном поле будет хранится в $form_state (для этого требуется уникальность передаваемого имени);
- За добавление новых элементов будет отвечать кнопка "Add another item", которая будет обработана AJAX-фреймворком Drupal. Для кнопки определены два обработчика: example_add_more_submit() и example_add_more_js(). Первый отвечает за непосредственную работу с формой, а второй - за добавление HTML-контента при нажатии на кнопку;
- Свойство #limit_validation_errors предотвратит валидацию формы при добавлении новых элементов. Значение свойства можно оставить пустым, как в примере, либо перечислить элементы, валидацию которых нужно выполнить;
- Свойство #example_multiple_value_element_name понадобится для определения имени элемента при обработке AJAX-запроса;
- Для темизации определяется тема example_multiple_value_form.
Определим функцию темизации множественного поля. Она будет очень простой, так как будет содержать только последовательный рендеринг внутренних элементов.
/**
* Implements hook_theme().
*/
function example_theme($existing, $type, $theme, $path) {
return array(
// Custom theme for multiple value Form API element.
'example_multiple_value_form' => array(
'render element' => 'element',
),
);
}
/**
* Returns HTML for an multiple form element.
*
* @param array $variables
* An associative array containing:
* - element: A render element representing the form element.
*
* @return string
* The HTML output.
*/
function theme_example_multiple_value_form($variables) {
$element = $variables['element'];
$output = '';
foreach (element_children($element) as $key) {
$output .= drupal_render($element[$key]);
}
return $output;
}
Перейдём к обработчикам кнопки. Они похожи на функции field_add_more_submit() и field_add_more_js(), которые применяются в модуле Field, и которые напрямую также использовать нельзя из-за необходимости передачи информации о поле.
/**
* Clone of field_add_more_submit() for Form API elements.
*/
function example_add_more_submit($form, &$form_state) {
$button = $form_state['triggering_element'];
// Go one level up in the form, to the elements container.
$element = drupal_array_get_nested_value($form, array_slice($button['#array_parents'], 0, -1));
$element_name = $element['#example_multiple_value_element_name'];
// Increment the items count.
$form_state['storage']['example_multiple_value_form'][$element_name]++;
$form_state['rebuild'] = TRUE;
}
/**
* Clone of field_add_more_js() for Form API elements.
*/
function example_add_more_js($form, $form_state) {
$button = $form_state['triggering_element'];
// Go one level up in the form, to the elements container.
$element = drupal_array_get_nested_value($form, array_slice($button['#array_parents'], 0, -1));
// Add a DIV around the delta receiving the Ajax effect.
$delta = $element['#max_delta'];
$element[$delta]['#prefix'] = '<div class="ajax-new-content">' . (isset($element[$delta]['#prefix']) ? $element[$delta]['#prefix'] : '');
$element[$delta]['#suffix'] = (isset($element[$delta]['#suffix']) ? $element[$delta]['#suffix'] : '') . '</div>';
return $element;
}
На этом процесс добавления множественного поля завершён. Форма конфигурации будет иметь вид, подобный тому, что продемонстрирован на расположенном далее скриншоте. При желании можно добавить управление весами значений, как в виджете множественного поля из Field API, что позволит менять расположение значений. Отмечу, что с помощью функции example_multiple_value_form() можно создавать множественные поля с любым типом элемента (то есть в $item_element можно задать любую необходимую структуру).
Добавить комментарий