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

Множественное поле в кастомной форме Drupal 7

Андрей Тымчук

Добавить комментарий