class WebformMultiple in Webform 6.x
Same name and namespace in other branches
- 8.5 src/Element/WebformMultiple.php \Drupal\webform\Element\WebformMultiple
Provides a webform element to assist in creation of multiple elements.
Plugin annotation
@FormElement("webform_multiple");
Hierarchy
- class \Drupal\Component\Plugin\PluginBase implements DerivativeInspectionInterface, PluginInspectionInterface
- class \Drupal\Core\Plugin\PluginBase uses DependencySerializationTrait, MessengerTrait, StringTranslationTrait
- class \Drupal\Core\Render\Element\RenderElement implements ElementInterface
- class \Drupal\Core\Render\Element\FormElement implements FormElementInterface
- class \Drupal\webform\Element\WebformMultiple
- class \Drupal\Core\Render\Element\FormElement implements FormElementInterface
- class \Drupal\Core\Render\Element\RenderElement implements ElementInterface
- class \Drupal\Core\Plugin\PluginBase uses DependencySerializationTrait, MessengerTrait, StringTranslationTrait
Expanded class hierarchy of WebformMultiple
11 #type uses of WebformMultiple
- RemotePostWebformHandler::buildConfigurationForm in src/
Plugin/ WebformHandler/ RemotePostWebformHandler.php - Form constructor.
- View::form in src/
Plugin/ WebformElement/ View.php - Gets the actual configuration webform array to be built.
- WebformAccessGroupForm::form in modules/
webform_access/ src/ WebformAccessGroupForm.php - Gets the actual form array to be built.
- WebformAdminConfigFormsForm::buildForm in src/
Form/ AdminConfig/ WebformAdminConfigFormsForm.php - Form constructor.
- WebformElementComposite::processWebformElementComposite in src/
Element/ WebformElementComposite.php - Processes a webform element composite (builder) element.
File
- src/
Element/ WebformMultiple.php, line 18
Namespace
Drupal\webform\ElementView source
class WebformMultiple extends FormElement {
/**
* Value indicating a element accepts an unlimited number of values.
*/
const CARDINALITY_UNLIMITED = -1;
/**
* {@inheritdoc}
*/
public function getInfo() {
$class = get_class($this);
return [
'#input' => TRUE,
'#access' => TRUE,
'#key' => NULL,
'#header' => NULL,
'#header_label' => '',
'#element' => [
'#type' => 'textfield',
'#title' => t('Item value'),
'#title_display' => 'invisible',
'#placeholder' => t('Enter value…'),
],
'#cardinality' => FALSE,
'#min_items' => NULL,
'#item_label' => $this
->t('item'),
'#no_items_message' => $this
->t('No items entered. Please add items below.'),
'#empty_items' => 1,
'#add_more' => TRUE,
'#add_more_items' => 1,
'#add_more_button_label' => $this
->t('Add'),
'#add_more_input' => TRUE,
'#add_more_input_label' => $this
->t('more items'),
'#sorting' => TRUE,
'#operations' => TRUE,
'#add' => TRUE,
'#ajax_attributes' => [],
'#table_attributes' => [],
'#table_wrapper_attributes' => [],
'#remove' => TRUE,
'#process' => [
[
$class,
'processWebformMultiple',
],
],
'#theme_wrappers' => [
'form_element',
],
// Add '#markup' property to add an 'id' attribute to the form element.
// @see template_preprocess_form_element()
'#markup' => '',
];
}
/**
* {@inheritdoc}
*/
public static function valueCallback(&$element, $input, FormStateInterface $form_state) {
if ($input === FALSE) {
if (!isset($element['#default_value'])) {
return [];
}
elseif (!is_array($element['#default_value'])) {
return [
$element['#default_value'],
];
}
else {
return $element['#default_value'];
}
}
elseif (is_array($input) && isset($input['items'])) {
return static::convertValuesToItems($element, $input['items']);
}
else {
return [];
}
}
/**
* Process items and build multiple elements widget.
*/
public static function processWebformMultiple(&$element, FormStateInterface $form_state, &$complete_form) {
// Set tree.
$element['#tree'] = TRUE;
// Remove 'for' from the element's label.
$element['#label_attributes']['webform-remove-for-attribute'] = TRUE;
// Set min items based on when the element is required.
if (!isset($element['#min_items']) || $element['#min_items'] === '') {
$element['#min_items'] = empty($element['#required']) ? 0 : 1;
}
// Make sure min items does not exceed cardinality.
if (!empty($element['#cardinality']) && $element['#min_items'] > $element['#cardinality']) {
$element['#min_items'] = $element['#cardinality'];
}
// Make sure empty items does not exceed cardinality.
if (!empty($element['#cardinality']) && $element['#empty_items'] > $element['#cardinality']) {
$element['#empty_items'] = $element['#cardinality'];
}
// If the number of default values exceeds the min items and has required
// sub-elements, set empty items to 0.
if (isset($element['#default_value']) && is_array($element['#default_value']) && count($element['#default_value']) >= $element['#min_items'] && static::hasRequireElement($element['#element'])) {
$element['#empty_items'] = 0;
}
// Add validate callback that extracts the array of items.
$element += [
'#element_validate' => [],
];
array_unshift($element['#element_validate'], [
get_called_class(),
'validateWebformMultiple',
]);
// Wrap this $element in a <div> that handle #states.
WebformElementHelper::fixStatesWrapper($element);
// Get unique key used to store the current number of items.
$number_of_items_storage_key = static::getStorageKey($element, 'number_of_items');
// Store the number of items which is the number of
// #default_values + number of empty_items.
if ($form_state
->get($number_of_items_storage_key) === NULL) {
if (empty($element['#default_value']) || !is_array($element['#default_value'])) {
$number_of_default_values = 0;
}
else {
$number_of_default_values = count($element['#default_value']);
}
$number_of_empty_items = (int) $element['#empty_items'];
$number_of_items = $number_of_default_values + $number_of_empty_items;
// Make sure number of items is greated than min items.
$min_items = (int) $element['#min_items'];
$number_of_items = $number_of_items < $min_items ? $min_items : $number_of_items;
// Make sure number of (default) items does not exceed cardinality.
if (!empty($element['#cardinality']) && $number_of_items > $element['#cardinality']) {
$number_of_items = $element['#cardinality'];
}
$form_state
->set($number_of_items_storage_key, $number_of_items);
}
$number_of_items = $form_state
->get($number_of_items_storage_key);
$table_id = implode('_', $element['#parents']) . '_table';
// Disable add operation when #cardinality is met
// and make sure to limit the number of items.
if (!empty($element['#cardinality']) && $number_of_items >= $element['#cardinality']) {
$element['#add'] = FALSE;
$number_of_items = $element['#cardinality'];
$form_state
->set($number_of_items_storage_key, $number_of_items);
}
// Add wrapper to the element.
$ajax_attributes = $element['#ajax_attributes'];
$ajax_attributes['id'] = $table_id;
$element += [
'#prefix' => '',
'#suffix' => '',
];
$element['#ajax_prefix'] = '<div' . new Attribute($ajax_attributes) . '>';
$element['#ajax_suffix'] = '</div>';
$element['#prefix'] = $element['#prefix'] . $element['#ajax_prefix'];
$element['#suffix'] = $element['#ajax_suffix'] . $element['#suffix'];
// DEBUG:
// Disable Ajax callback by commenting out the below callback and wrapper.
$ajax_settings = [
'callback' => [
get_called_class(),
'ajaxCallback',
],
'wrapper' => $table_id,
'progress' => [
'type' => 'none',
],
];
// Initialize, prepare, and finalize sub-elements.
static::initializeElement($element, $form_state, $complete_form);
// Build (single) element header.
$header = static::buildElementHeader($element);
// Build (single) element rows.
$row_index = 0;
$weight = 0;
$rows = [];
if (!$form_state
->isProcessingInput() && isset($element['#default_value']) && is_array($element['#default_value'])) {
$default_values = $element['#default_value'];
}
elseif ($form_state
->isProcessingInput() && isset($element['#value']) && is_array($element['#value'])) {
$default_values = $element['#value'];
}
else {
$default_values = [];
}
// When adding/removing elements we don't need to set any default values.
$action_key = static::getStorageKey($element, 'action');
if ($form_state
->get($action_key)) {
$form_state
->set($action_key, FALSE);
$default_values = [];
}
foreach ($default_values as $key => $default_value) {
// If #key is defined make sure to set default value's key item.
if (!empty($element['#key']) && !isset($default_value[$element['#key']])) {
$default_value[$element['#key']] = $key;
}
$rows[$row_index] = static::buildElementRow($table_id, $row_index, $element, $default_value, $weight++, $ajax_settings);
$row_index++;
}
while ($row_index < $number_of_items) {
$rows[$row_index] = static::buildElementRow($table_id, $row_index, $element, NULL, $weight++, $ajax_settings);
$row_index++;
}
// Build table.
$table_wrapper_attributes = $element['#table_wrapper_attributes'];
$table_wrapper_attributes['class'][] = 'webform-multiple-table';
if (count($element['#element']) > 1) {
$table_wrapper_attributes['class'][] = 'webform-multiple-table-responsive';
}
$element['items'] = [
'#prefix' => '<div' . new Attribute($table_wrapper_attributes) . '>',
'#suffix' => '</div>',
] + $rows;
// Display table if there are any rows.
if ($rows) {
$element['items'] += [
'#type' => 'table',
'#header' => $header,
'#attributes' => $element['#table_attributes'],
] + $rows;
// Add sorting to table.
if ($element['#sorting']) {
$element['items']['#tabledrag'] = [
[
'action' => 'order',
'relationship' => 'sibling',
'group' => 'webform-multiple-sort-weight',
],
];
}
}
elseif (!empty($element['#no_items_message'])) {
$element['items'] += [
'#type' => 'webform_message',
'#message_message' => $element['#no_items_message'],
'#message_type' => 'info',
'#attributes' => [
'class' => [
'webform-multiple-table--no-items-message',
],
],
];
}
// Build add more actions.
if ($element['#add_more'] && (empty($element['#cardinality']) || $number_of_items < $element['#cardinality'])) {
$element['add'] = [
'#prefix' => '<div class="webform-multiple-add js-webform-multiple-add container-inline">',
'#suffix' => '</div>',
];
$element['add']['submit'] = [
'#type' => 'submit',
'#value' => $element['#add_more_button_label'],
'#limit_validation_errors' => [],
'#submit' => [
[
get_called_class(),
'addItemsSubmit',
],
],
'#ajax' => $ajax_settings,
'#name' => $table_id . '_add',
];
$max = $element['#cardinality'] ? $element['#cardinality'] - $number_of_items : 100;
$element['add']['more_items'] = [
'#type' => 'number',
'#title' => $element['#add_more_button_label'] . ' ' . $element['#add_more_input_label'],
'#title_display' => 'invisible',
'#min' => 1,
'#max' => $max,
'#default_value' => $element['#add_more_items'],
'#field_suffix' => $element['#add_more_input_label'],
'#error_no_message' => TRUE,
'#access' => $element['#add_more_input'],
];
}
$element['#attached']['library'][] = 'webform/webform.element.multiple';
return $element;
}
/**
* Initialize element.
*
* @param array $element
* The element.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
* @param array $complete_form
* An associative array containing the structure of the form.
*/
protected static function initializeElement(array &$element, FormStateInterface $form_state, array &$complete_form) {
// Track element child keys.
$element['#child_keys'] = Element::children($element['#element']);
if (!$element['#child_keys']) {
// Apply multiple element's required/optional #states to the
// individual element.
if (isset($element['#_webform_states'])) {
$element['#element'] += [
'#states' => [],
];
$element['#element']['#states'] = array_intersect_key(WebformElementHelper::getStates($element), [
'required' => 'required',
'optional' => 'optional',
]);
}
}
else {
// Initialize, prepare, and finalize composite sub-elements.
// Get composite element required/options states from visible/hidden states.
$required_states = WebformElementHelper::getRequiredFromVisibleStates($element);
static::initializeElementRecursive($element, $form_state, $complete_form, $element['#element'], $required_states);
}
}
/**
* Initialize, prepare, and finalize composite sub-elements recursively.
*
* @param array $element
* The main element.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
* @param array $complete_form
* An associative array containing the structure of the form.
* @param array $sub_elements
* The sub element.
* @param array $required_states
* An associative array of required states from the main element's
* visible/hidden states.
*/
protected static function initializeElementRecursive(array $element, FormStateInterface $form_state, array &$complete_form, array &$sub_elements, array $required_states) {
$child_keys = Element::children($sub_elements);
// Exit immediate if the sub elements has no children.
if (!$child_keys) {
return;
}
// Determine if the sub elements are the main element for each table cell.
$is_root = $element['#element'] === $sub_elements ? TRUE : FALSE;
/** @var \Drupal\webform\Plugin\WebformElementManagerInterface $element_manager */
$element_manager = \Drupal::service('plugin.manager.webform.element');
foreach ($child_keys as $child_key) {
$sub_element =& $sub_elements[$child_key];
$element_plugin = $element_manager
->getElementInstance($sub_element);
// If the element's #access is FALSE, apply it to all sub elements.
if (isset($element['#access']) && $element['#access'] === FALSE) {
$sub_element['#access'] = FALSE;
}
// If #header and root input then hide the sub element's #title.
if ($element['#header'] && ($is_root && $element_plugin
->isInput($sub_element)) && !isset($sub_element['#title_display'])) {
$sub_element['#title_display'] = 'invisible';
}
// Initialize the composite sub-element.
$element_manager
->initializeElement($sub_element);
// Build the composite sub-element.
$element_manager
->buildElement($sub_element, $complete_form, $form_state);
// Custom validate required sub-element because they can be hidden
// via #access or #states.
// @see \Drupal\webform\Element\WebformCompositeBase::validateWebformComposite
if ($required_states && !empty($sub_element['#required'])) {
unset($sub_element['#required']);
$sub_element['#_required'] = TRUE;
if (!isset($sub_element['#states'])) {
$sub_element['#states'] = [];
}
$sub_element['#states'] += $required_states;
}
if (is_array($sub_element)) {
static::initializeElementRecursive($element, $form_state, $complete_form, $sub_element, $required_states);
}
}
}
/**
* Build a single element header.
*
* @param array $element
* The element.
*
* @return array
* A render array containing inputs for an element's header.
*/
protected static function buildElementHeader(array $element) {
$table_id = implode('-', $element['#parents']) . '-table';
$colspan = 0;
if ($element['#sorting']) {
$colspan += 3;
}
if ($element['#operations']) {
$colspan += 1;
}
if (empty($element['#header'])) {
if (!empty($element['#header_label'])) {
$header_label = $element['#header_label'];
}
elseif (!empty($element['#title'])) {
$header_label = WebformAccessibilityHelper::buildVisuallyHidden($element['#title']);
}
else {
$header_label = [];
}
return [
[
'data' => $header_label,
'colspan' => $colspan + 1,
],
];
}
elseif (is_array($element['#header'])) {
$header = [];
if ($element['#sorting']) {
$header[] = [
'data' => WebformAccessibilityHelper::buildVisuallyHidden(t('Re-order')),
'class' => [
"{$table_id}--handle",
'webform-multiple-table--handle',
],
];
}
$header = array_merge($header, $element['#header']);
if ($element['#sorting']) {
$header[] = [
'data' => [
'#markup' => t('Weight'),
],
'class' => [
"{$table_id}--weight",
'webform-multiple-table--weight',
],
];
}
if ($element['#operations']) {
$header[] = [
'data' => WebformAccessibilityHelper::buildVisuallyHidden(t('Operations')),
'class' => [
"{$table_id}--handle",
'webform-multiple-table--operations',
],
];
}
return $header;
}
elseif (is_string($element['#header'])) {
return [
[
'data' => $element['#header'],
'colspan' => $element['#child_keys'] ? count($element['#child_keys']) + $colspan : $colspan + 1,
],
];
}
elseif (!empty($element['#header_label'])) {
return [
[
'data' => $element['#header_label'],
'colspan' => $element['#child_keys'] ? count($element['#child_keys']) + $colspan : $colspan + 1,
],
];
}
else {
$header = [];
if ($element['#sorting']) {
$header['_handle_'] = [
'data' => WebformAccessibilityHelper::buildVisuallyHidden(t('Re-order')),
'class' => [
"{$table_id}--handle",
"webform-multiple-table--handle",
],
];
}
if ($element['#child_keys']) {
foreach ($element['#child_keys'] as $child_key) {
if (static::isHidden($element['#element'][$child_key])) {
continue;
}
$child_element = $element['#element'][$child_key];
// Build element title.
$header[$child_key] = [
'data' => static::buildElementTitle($child_element),
];
// Append label attributes to header.
if (!empty($child_element['#label_attributes'])) {
$header[$child_key] += $child_element['#label_attributes'];
}
$header[$child_key]['class'][] = "{$table_id}--{$child_key}";
$header[$child_key]['class'][] = "webform-multiple-table--{$child_key}";
}
}
else {
$header['item'] = [
'data' => isset($element['#element']['#title']) ? $element['#element']['#title'] : '',
'class' => [
"{$table_id}--item",
"webform-multiple-table--item",
],
];
}
if ($element['#sorting']) {
$header['weight'] = [
'data' => t('Weight'),
'class' => [
"{$table_id}--weight",
"webform-multiple-table--weight",
],
];
}
if ($element['#operations']) {
$header['_operations_'] = [
'data' => WebformAccessibilityHelper::buildVisuallyHidden(t('Operations')),
'class' => [
"{$table_id}--operations",
"webform-multiple-table--operations",
],
];
}
return $header;
}
}
/**
* Build an element's title with help.
*
* @param array $element
* An element.
*
* @return array
* A render array containing an element's title with help.
*/
protected static function buildElementTitle(array $element) {
$title = !empty($element['#title']) ? $element['#title'] : '';
$build = [];
$build['title'] = [
'#markup' => $title,
];
if (!empty($element['#required']) || !empty($element['#_required'])) {
$build['title'] += [
'#prefix' => '<span class="form-required">',
'#suffix' => '</span>',
];
}
if (!empty($element['#help'])) {
$build['help'] = [
'#type' => 'webform_help',
'#help' => $element['#help'],
'#help_title' => $title,
];
}
return $build;
}
/**
* Build a single element row.
*
* @param string $table_id
* The element's table id.
* @param int $row_index
* The row index.
* @param array $element
* The element.
* @param string $default_value
* The default value.
* @param int $weight
* The weight.
* @param array $ajax_settings
* An array containing Ajax callback settings.
*
* @return array
* A render array containing inputs for an element's value and weight.
*/
protected static function buildElementRow($table_id, $row_index, array $element, $default_value, $weight, array $ajax_settings) {
if ($element['#child_keys']) {
static::setElementRowDefaultValueRecursive($element['#element'], (array) $default_value);
}
else {
static::setElementDefaultValue($element['#element'], $default_value);
}
$hidden_elements = [];
$row = [];
if ($element['#sorting']) {
$row['_handle_'] = [
'#wrapper_attributes' => [
'class' => [
'webform-multiple-table--handle',
],
],
];
}
if ($element['#child_keys'] && !empty($element['#header'])) {
// Set #parents which is used for nested elements.
// @see \Drupal\webform\Element\WebformMultiple::setElementRowParentsRecursive
$parents = array_merge($element['#parents'], [
'items',
$row_index,
]);
$hidden_parents = array_merge($element['#parents'], [
'items',
$row_index,
'_hidden_',
]);
foreach ($element['#child_keys'] as $child_key) {
// Store hidden element in the '_handle_' column.
// @see \Drupal\webform\Element\WebformMultiple::convertValuesToItems
if (static::isHidden($element['#element'][$child_key])) {
$hidden_elements[$child_key] = $element['#element'][$child_key];
// ISSUE:
// All elements in _handle_ with #access: FALSE are losing
// their values.
//
// Moving these #access: FALSE and value elements outside of the
// table does not work. What is even move baffling is manually adding
// a 'value' element does work.
//
// $element['hidden'][$row_index][$child_key] = $element['#element'][$child_key];
// $element['hidden'][1000]['test'] = ['#type' => 'value', '#value' => 'test'];
//
// WORKAROUND:
// Convert element to rendered hidden element.
if (Element::isVisibleElement($element)) {
$hidden_elements[$child_key]['#type'] = 'hidden';
// Unset #access, #element_validate, and #pre_render.
// @see \Drupal\webform\Plugin\WebformElementBase::prepare()
// Unset #options to prevent An illegal choice has been detected.
// @see \Drupal\Core\Form\FormValidator::performRequiredValidation
unset($hidden_elements[$child_key]['#access'], $hidden_elements[$child_key]['#element_validate'], $hidden_elements[$child_key]['#pre_render'], $hidden_elements[$child_key]['#options']);
}
static::setElementRowParentsRecursive($hidden_elements[$child_key], $child_key, $hidden_parents);
}
else {
$row[$child_key] = $element['#element'][$child_key];
static::setElementRowParentsRecursive($row[$child_key], $child_key, $parents);
}
}
}
else {
$row['_item_'] = $element['#element'];
}
if ($element['#sorting']) {
$row['weight'] = [
'#type' => 'weight',
'#delta' => 1000,
'#title' => t('Item weight'),
'#title_display' => 'invisible',
'#attributes' => [
'class' => [
'webform-multiple-sort-weight',
],
],
'#wrapper_attributes' => [
'class' => [
'webform-multiple-table--weight',
],
],
'#default_value' => $weight,
];
}
// Allow users to add & remove rows if cardinality is not set.
if ($element['#operations']) {
$row['_operations_'] = [
'#wrapper_attributes' => [
'class' => [
'webform-multiple-table--operations',
],
],
];
if ($element['#add'] && $element['#remove']) {
$row['_operations_']['#wrapper_attributes']['class'][] = 'webform-multiple-table--operations-two';
}
if ($element['#add']) {
$row['_operations_']['add'] = [
'#type' => 'image_button',
'#title' => t('Add new @item after @item @number', [
'@number' => $row_index + 1,
'@item' => $element['#item_label'],
]),
'#src' => drupal_get_path('module', 'webform') . '/images/icons/plus.svg',
'#limit_validation_errors' => [],
'#submit' => [
[
get_called_class(),
'addItemSubmit',
],
],
'#ajax' => $ajax_settings,
// Issue #1342066 Document that buttons with the same #value need a unique
// #name for the Form API to distinguish them, or change the Form API to
// assign unique #names automatically.
'#row_index' => $row_index,
'#name' => $table_id . '_add_' . $row_index,
];
}
if ($element['#remove']) {
$row['_operations_']['remove'] = [
'#type' => 'image_button',
'#title' => t('Remove @item @number', [
'@number' => $row_index + 1,
'@item' => $element['#item_label'],
]),
'#src' => drupal_get_path('module', 'webform') . '/images/icons/minus.svg',
'#limit_validation_errors' => [],
'#submit' => [
[
get_called_class(),
'removeItemSubmit',
],
],
'#ajax' => $ajax_settings,
// Issue #1342066 Document that buttons with the same #value need a unique
// #name for the Form API to distinguish them, or change the Form API to
// assign unique #names automatically.
'#row_index' => $row_index,
'#name' => $table_id . '_remove_' . $row_index,
];
}
}
// Add hidden element as a hidden row.
if ($hidden_elements) {
$row['_hidden_'] = $hidden_elements + [
'#wrapper_attributes' => [
'style' => 'display: none',
],
];
}
if ($element['#sorting']) {
$row['#attributes']['class'][] = 'draggable';
$row['#weight'] = $weight;
}
return $row;
}
/**
* Determine if an element is hidden.
*
* @param array $element
* The element.
*
* @return bool
* TRUE if the element is hidden.
*/
protected static function isHidden(array $element) {
return !Element::isVisibleElement($element);
}
/**
* Set element row default value recursively.
*
* @param array $element
* The element.
* @param array $default_value
* The default values.
*/
protected static function setElementRowDefaultValueRecursive(array &$element, array $default_value) {
foreach (Element::children($element) as $child_key) {
if (isset($default_value[$child_key])) {
static::setElementDefaultValue($element[$child_key], $default_value[$child_key]);
}
static::setElementRowDefaultValueRecursive($element[$child_key], $default_value);
}
}
/**
* Set element row default value recursively.
*
* @param array $element
* The element.
* @param mixed $default_value
* The default value.
*/
protected static function setElementDefaultValue(array &$element, $default_value) {
if ($element['#type'] === 'value') {
$element['#value'] = $default_value;
}
else {
$element['#default_value'] = $default_value;
// Set default value.
// @see \Drupal\webform\Plugin\WebformElementInterface::setDefaultValue
// @see \Drupal\webform\Plugin\WebformElement\DateBase::setDefaultValue
/** @var \Drupal\webform\Plugin\WebformElementManagerInterface $element_manager */
$element_manager = \Drupal::service('plugin.manager.webform.element');
$element_plugin = $element_manager
->getElementInstance($element);
$element_plugin
->setDefaultValue($element);
}
}
/**
* Set element row parents recursively.
*
* This allow elements/columns to contain nested sub-elements.
*
* @param array $element
* The child element.
* @param string $element_key
* The child element's key.
* @param array $parents
* The main element's parents.
*/
protected static function setElementRowParentsRecursive(array &$element, $element_key, array $parents) {
$element['#parents'] = array_merge($parents, [
$element_key,
]);
foreach (Element::children($element) as $child_key) {
static::setElementRowParentsRecursive($element[$child_key], $child_key, $parents);
}
}
/****************************************************************************/
// Callbacks.
/****************************************************************************/
/**
* Webform submission handler for adding more items.
*
* @param array $form
* An associative array containing the structure of the form.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
*/
public static function addItemsSubmit(array &$form, FormStateInterface $form_state) {
// Get the webform list element by going up two levels.
$button = $form_state
->getTriggeringElement();
$element =& NestedArray::getValue($form, array_slice($button['#array_parents'], 0, -2));
// Add more items to the number of items.
$number_of_items_storage_key = static::getStorageKey($element, 'number_of_items');
$number_of_items = $form_state
->get($number_of_items_storage_key);
$more_items = (int) $element['add']['more_items']['#value'];
$form_state
->set($number_of_items_storage_key, $number_of_items + $more_items);
// Reset values.
$items = !empty($element['items']['#value']) ? array_values($element['items']['#value']) : [];
$element['items']['#value'] = $items;
$form_state
->setValueForElement($element['items'], $items);
NestedArray::setValue($form_state
->getUserInput(), $element['items']['#parents'], $items);
$action_key = static::getStorageKey($element, 'action');
$form_state
->set($action_key, TRUE);
// Rebuild the form.
$form_state
->setRebuild();
}
/**
* Webform submission handler for adding an item.
*
* @param array $form
* An associative array containing the structure of the form.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
*/
public static function addItemSubmit(array &$form, FormStateInterface $form_state) {
$button = $form_state
->getTriggeringElement();
$element = NestedArray::getValue($form, array_slice($button['#array_parents'], 0, -4));
// Add item.
$values = [];
foreach ($element['items']['#value'] as $row_index => $value) {
$values[] = $value;
if ($row_index === $button['#row_index']) {
$values[] = [];
}
}
// Add one item to the 'number of items'.
$number_of_items_storage_key = static::getStorageKey($element, 'number_of_items');
$number_of_items = $form_state
->get($number_of_items_storage_key);
$form_state
->set($number_of_items_storage_key, $number_of_items + 1);
// Reset values.
$form_state
->setValueForElement($element['items'], $values);
NestedArray::setValue($form_state
->getUserInput(), $element['items']['#parents'], $values);
$action_key = static::getStorageKey($element, 'action');
$form_state
->set($action_key, TRUE);
// Rebuild the form.
$form_state
->setRebuild();
}
/**
* Webform submission handler for removing an item.
*
* @param array $form
* An associative array containing the structure of the form.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
*/
public static function removeItemSubmit(array &$form, FormStateInterface $form_state) {
$button = $form_state
->getTriggeringElement();
$element = NestedArray::getValue($form, array_slice($button['#array_parents'], 0, -4));
$values = $element['items']['#value'];
// Remove item.
unset($values[$button['#row_index']]);
$values = array_values($values);
// Remove one item from the 'number of items'.
$number_of_items_storage_key = static::getStorageKey($element, 'number_of_items');
$number_of_items = $form_state
->get($number_of_items_storage_key);
// Never allow the number of items to be less than #min_items.
if ($number_of_items > $element['#min_items']) {
$form_state
->set($number_of_items_storage_key, $number_of_items - 1);
}
// Reset values.
$form_state
->setValueForElement($element['items'], $values);
NestedArray::setValue($form_state
->getUserInput(), $element['items']['#parents'], $values);
$action_key = static::getStorageKey($element, 'action');
$form_state
->set($action_key, TRUE);
// Rebuild the form.
$form_state
->setRebuild();
}
/**
* Webform submission Ajax callback the returns the list table.
*/
public static function ajaxCallback(array &$form, FormStateInterface $form_state) {
$button = $form_state
->getTriggeringElement();
$parent_length = isset($button['#row_index']) ? -4 : -2;
$element = NestedArray::getValue($form, array_slice($button['#array_parents'], 0, $parent_length));
// Make sure only the ajax prefix and suffix is used.
$element['#prefix'] = $element['#ajax_prefix'];
$element['#suffix'] = $element['#ajax_suffix'];
// Disable states and flexbox wrapper.
// @see \Drupal\webform\Plugin\WebformElementBase::preRenderFixFlexboxWrapper
$element['#webform_wrapper'] = FALSE;
return $element;
}
/**
* Validates webform multiple element.
*/
public static function validateWebformMultiple(&$element, FormStateInterface $form_state, &$complete_form) {
// IMPORTANT: Must get values from the $form_states since sub-elements
// may call $form_state->setValueForElement() via their validation hook.
// @see \Drupal\webform\Element\WebformEmailConfirm::validateWebformEmailConfirm
// @see \Drupal\webform\Element\WebformOtherBase::validateWebformOther
$values = NestedArray::getValue($form_state
->getValues(), $element['#parents']);
$number_of_items_storage_key = static::getStorageKey($element, 'number_of_items');
$number_of_items = $form_state
->get($number_of_items_storage_key);
if (!empty($values['items']) && ($number_of_items || $element['#cardinality'])) {
$items = $values['items'];
// Validate unique keys.
if ($error_message = static::validateUniqueKeys($element, $items)) {
$form_state
->setError($element, $error_message);
return;
}
// Convert values to items and validate duplicate keys.
$items = static::convertValuesToItems($element, $items);
// Validate required items.
if (!empty($element['#required']) && empty($items)) {
WebformElementHelper::setRequiredError($element, $form_state);
}
}
else {
$items = [];
}
$element['#value'] = $items;
$form_state
->setValueForElement($element, $items);
}
/****************************************************************************/
// Helper functions.
/****************************************************************************/
/**
* Get unique key used to store the number of items for an element.
*
* @param array $element
* An element.
* @param string $name
* The storage key's name.
*
* @return string
* A unique key used to store the number of items for an element.
*/
public static function getStorageKey(array $element, $name) {
return 'webform_multiple__' . $element['#name'] . '__' . $name;
}
/**
* Convert an array containing of values (elements or _item_ and weight) to an array of items.
*
* @param array $element
* The multiple element.
* @param array $values
* An array containing of item and weight.
*
* @return array
* An array of items.
*
* @throws \Exception
* Throws unique key required validation error message as an exception.
*/
public static function convertValuesToItems(array $element, array $values = []) {
// Sort the item values.
if ($element['#sorting']) {
uasort($values, [
'Drupal\\Component\\Utility\\SortArray',
'sortByWeightElement',
]);
}
// Now build the associative array of items.
$items = [];
foreach ($values as $value) {
$item = static::convertValueToItem($value);
// Never add an empty item.
if (static::isEmpty($item)) {
continue;
}
// If #key is defined use it as the $items key.
if (!empty($element['#key']) && isset($item[$element['#key']])) {
$key_name = $element['#key'];
$key_value = $item[$key_name];
unset($item[$key_name]);
$items[$key_value] = $item;
}
else {
$items[] = $item;
}
}
return $items;
}
/**
* Convert value array containing (elements or _item_ and weight) to an item.
*
* @param array $value
* The multiple value array.
*
* @return array
* An item array.
*/
public static function convertValueToItem(array $value) {
if (isset($value['_item_'])) {
return $value['_item_'];
}
else {
// Get hidden (#access: FALSE) elements in the '_handle_' column and
// add them to the $value.
// @see \Drupal\webform\Element\WebformMultiple::buildElementRow
if (isset($value['_hidden_']) && is_array($value['_hidden_'])) {
$value += $value['_hidden_'];
}
unset($value['weight'], $value['_operations_'], $value['_hidden_']);
return $value;
}
}
/**
* Validate composite element has unique keys.
*
* @param array $element
* The multiple element.
* @param array $values
* An array containing of item and weight.
*
* @return null|string
* NULL if element has unique keys, else an error message with
* the duplicate key.
*/
protected static function validateUniqueKeys(array $element, array $values) {
// Only validate if the element's #key is defined.
if (!isset($element['#key'])) {
return NULL;
}
$unique_keys = [];
foreach ($values as $value) {
$item = static::convertValueToItem($value);
$key_name = $element['#key'];
$key_value = $item[$key_name];
// Skip empty key and item.
unset($item[$key_name]);
if (empty($key_value) && static::isEmpty($item)) {
continue;
}
if (isset($unique_keys[$key_value])) {
$elements = WebformElementHelper::getFlattened($element['#element']);
$key_title = isset($elements[$key_name]['#title']) ? $elements[$key_name]['#title'] : $key_name;
$t_args = [
'@key' => $key_value,
'%title' => $key_title,
];
return t("The %title '@key' is already in use. It must be unique.", $t_args);
}
$unique_keys[$key_value] = $key_value;
}
return NULL;
}
/**
* Check if array is empty.
*
* @param string|array $value
* An item.
*
* @return bool
* FALSE if item is an empty string or an empty array.
*/
public static function isEmpty($value = NULL) {
if (is_null($value)) {
return TRUE;
}
elseif (is_string($value)) {
return $value === '' ? TRUE : FALSE;
}
elseif (is_array($value)) {
return !array_filter($value, function ($item) {
return !static::isEmpty($item);
});
}
else {
return FALSE;
}
}
/**
* Determine if any sub-element is required.
*
* @param array $element
* An element.
*
* @return bool
* TRUE if any sub-element is required.
*/
protected static function hasRequireElement(array $element) {
$required_properties = [
'#required' => TRUE,
'#_required' => TRUE,
];
return WebformElementHelper::hasProperties($element, $required_properties);
}
}
Members
Name | Modifiers | Type | Description | Overrides |
---|---|---|---|---|
DependencySerializationTrait:: |
protected | property | ||
DependencySerializationTrait:: |
protected | property | ||
DependencySerializationTrait:: |
public | function | 2 | |
DependencySerializationTrait:: |
public | function | 2 | |
FormElement:: |
public static | function | Adds autocomplete functionality to elements. | |
FormElement:: |
public static | function | #process callback for #pattern form element property. | |
FormElement:: |
public static | function | #element_validate callback for #pattern form element property. | |
MessengerTrait:: |
protected | property | The messenger. | 27 |
MessengerTrait:: |
public | function | Gets the messenger. | 27 |
MessengerTrait:: |
public | function | Sets the messenger. | |
PluginBase:: |
protected | property | Configuration information passed into the plugin. | 1 |
PluginBase:: |
protected | property | The plugin implementation definition. | 1 |
PluginBase:: |
protected | property | The plugin_id. | |
PluginBase:: |
constant | A string which is used to separate base plugin IDs from the derivative ID. | ||
PluginBase:: |
public | function |
Gets the base_plugin_id of the plugin instance. Overrides DerivativeInspectionInterface:: |
|
PluginBase:: |
public | function |
Gets the derivative_id of the plugin instance. Overrides DerivativeInspectionInterface:: |
|
PluginBase:: |
public | function |
Gets the definition of the plugin implementation. Overrides PluginInspectionInterface:: |
2 |
PluginBase:: |
public | function |
Gets the plugin_id of the plugin instance. Overrides PluginInspectionInterface:: |
|
PluginBase:: |
public | function | Determines if the plugin is configurable. | |
PluginBase:: |
public | function | Constructs a \Drupal\Component\Plugin\PluginBase object. | 98 |
RenderElement:: |
public static | function | Adds Ajax information about an element to communicate with JavaScript. | |
RenderElement:: |
public static | function | Adds members of this group as actual elements for rendering. | |
RenderElement:: |
public static | function | Form element processing handler for the #ajax form property. | 1 |
RenderElement:: |
public static | function | Arranges elements into groups. | |
RenderElement:: |
public static | function |
Sets a form element's class attribute. Overrides ElementInterface:: |
|
StringTranslationTrait:: |
protected | property | The string translation service. | 4 |
StringTranslationTrait:: |
protected | function | Formats a string containing a count of items. | |
StringTranslationTrait:: |
protected | function | Returns the number of plurals supported by a given language. | |
StringTranslationTrait:: |
protected | function | Gets the string translation service. | |
StringTranslationTrait:: |
public | function | Sets the string translation service to use. | 2 |
StringTranslationTrait:: |
protected | function | Translates a string to the current language or to a given language. | |
WebformMultiple:: |
public static | function | Webform submission handler for adding more items. | |
WebformMultiple:: |
public static | function | Webform submission handler for adding an item. | |
WebformMultiple:: |
public static | function | Webform submission Ajax callback the returns the list table. | |
WebformMultiple:: |
protected static | function | Build a single element header. | |
WebformMultiple:: |
protected static | function | Build a single element row. | |
WebformMultiple:: |
protected static | function | Build an element's title with help. | |
WebformMultiple:: |
constant | Value indicating a element accepts an unlimited number of values. | ||
WebformMultiple:: |
public static | function | Convert an array containing of values (elements or _item_ and weight) to an array of items. | |
WebformMultiple:: |
public static | function | Convert value array containing (elements or _item_ and weight) to an item. | |
WebformMultiple:: |
public | function |
Returns the element properties for this element. Overrides ElementInterface:: |
|
WebformMultiple:: |
public static | function | Get unique key used to store the number of items for an element. | |
WebformMultiple:: |
protected static | function | Determine if any sub-element is required. | |
WebformMultiple:: |
protected static | function | Initialize element. | |
WebformMultiple:: |
protected static | function | Initialize, prepare, and finalize composite sub-elements recursively. | |
WebformMultiple:: |
public static | function | Check if array is empty. | |
WebformMultiple:: |
protected static | function | Determine if an element is hidden. | |
WebformMultiple:: |
public static | function | Process items and build multiple elements widget. | 1 |
WebformMultiple:: |
public static | function | Webform submission handler for removing an item. | |
WebformMultiple:: |
protected static | function | Set element row default value recursively. | |
WebformMultiple:: |
protected static | function | Set element row default value recursively. | |
WebformMultiple:: |
protected static | function | Set element row parents recursively. | |
WebformMultiple:: |
protected static | function | Validate composite element has unique keys. | |
WebformMultiple:: |
public static | function | Validates webform multiple element. | 1 |
WebformMultiple:: |
public static | function |
Determines how user input is mapped to an element's #value property. Overrides FormElement:: |