Возникла недавно необходимость повторить функционал множественного поля из 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 можно задать любую необходимую структуру).
Добавить комментарий