class YamlFormMultiple in YAML Form 8
Provides a form element to assist in creation of multiple elements.
Plugin annotation
@FormElement("yamlform_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\yamlform\Element\YamlFormMultiple
- 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 YamlFormMultiple
2 #type uses of YamlFormMultiple
- YamlFormElementOptions::processYamlFormElementOptions in src/
Element/ YamlFormElementOptions.php - Processes a form element options element.
- YamlFormOptions::processYamlFormOptions in src/
Element/ YamlFormOptions.php - Process options and build options widget.
File
- src/
Element/ YamlFormMultiple.php, line 16
Namespace
Drupal\yamlform\ElementView source
class YamlFormMultiple extends FormElement {
/**
* {@inheritdoc}
*/
public function getInfo() {
$class = get_class($this);
return [
'#input' => TRUE,
'#label' => t('item'),
'#labels' => t('items'),
'#header' => NULL,
'#element' => [
'#type' => 'textfield',
'#title' => t('Item value'),
'#title_display' => 'invisible',
'#placeholder' => t('Enter value'),
],
'#cardinality' => FALSE,
'#empty_items' => 1,
'#add_more' => 1,
'#process' => [
[
$class,
'processYamlFormMultiple',
],
],
'#theme_wrappers' => [
'form_element',
],
];
}
/**
* {@inheritdoc}
*/
public static function valueCallback(&$element, $input, FormStateInterface $form_state) {
if ($input === FALSE) {
return isset($element['#default_value']) ? $element['#default_value'] : [];
}
elseif (is_array($input) && isset($input['items'])) {
return $input['items'];
}
else {
return NULL;
}
}
/**
* Process items and build multiple elements widget.
*/
public static function processYamlFormMultiple(&$element, FormStateInterface $form_state, &$complete_form) {
$element['#tree'] = TRUE;
// Add validate callback that extracts the array of items.
$element['#element_validate'] = [
[
get_called_class(),
'validateYamlFormMultiple',
],
];
// Wrap this $element in a <div> that handle #states.
YamlFormElementHelper::fixStatesWrapper($element);
if ($element['#cardinality']) {
// If the cardinality is set limit number of items to this value.
$number_of_items = $element['#cardinality'];
}
else {
// Get unique key used to store the current number of items.
$number_of_items_storage_key = self::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;
$number_of_empty_items = (int) $element['#empty_items'];
}
else {
$number_of_default_values = count($element['#default_value']);
$number_of_empty_items = 1;
}
$form_state
->set($number_of_items_storage_key, $number_of_default_values + $number_of_empty_items);
}
$number_of_items = $form_state
->get($number_of_items_storage_key);
}
$table_id = implode('_', $element['#parents']) . '_table';
// DEBUG: Disable AJAX callback by commenting out the below callback and
// wrapper.
$ajax_settings = [
'callback' => [
get_called_class(),
'ajaxCallback',
],
'wrapper' => $table_id,
];
$element['#child_keys'] = Element::children($element['#element']);
// Build (single) element header.
$header = self::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'])) {
foreach ($element['#default_value'] as $default_value) {
$rows[$row_index] = self::buildElementRow($table_id, $row_index, $element, $default_value, $weight++, $ajax_settings);
$row_index++;
}
}
while ($row_index < $number_of_items) {
$rows[$row_index] = self::buildElementRow($table_id, $row_index, $element, NULL, $weight++, $ajax_settings);
$row_index++;
}
// Build table.
$element['items'] = [
'#prefix' => '<div id="' . $table_id . '" class="yamlform-multiple-table">',
'#suffix' => '</div>',
'#type' => 'table',
'#header' => $header,
'#tabledrag' => [
[
'action' => 'order',
'relationship' => 'sibling',
'group' => 'yamlform-multiple-sort-weight',
],
],
] + $rows;
// Build add items actions.
if (empty($element['#cardinality'])) {
$element['add'] = [
'#prefix' => '<div class="container-inline">',
'#suffix' => '</div>',
];
$element['add']['submit'] = [
'#type' => 'submit',
'#value' => t('Add'),
'#limit_validation_errors' => [],
'#submit' => [
[
get_called_class(),
'addItemsSubmit',
],
],
'#ajax' => $ajax_settings,
'#name' => $table_id . '_add',
];
$element['add']['more_items'] = [
'#type' => 'number',
'#min' => 1,
'#max' => 100,
'#default_value' => $element['#add_more'],
'#field_suffix' => t('more @labels', [
'@labels' => $element['#labels'],
]),
];
}
$element['#attached']['library'][] = 'yamlform/yamlform.element.multiple';
return $element;
}
/**
* Build a single element header.
*
* @param array $element
* The element.
*
* @return array
* A render array containing inputs for an element's header.
*/
public static function buildElementHeader(array $element) {
if (empty($element['#header'])) {
return [
[
'data' => '',
'colspan' => 4,
],
];
}
if (is_array($element['#header'])) {
return $element['#header'];
}
$header = [];
$header['_handle_'] = '';
if ($element['#child_keys']) {
foreach ($element['#child_keys'] as $child_key) {
$header[$child_key] = !empty($element['#element'][$child_key]['#title']) ? $element['#element'][$child_key]['#title'] : '';
}
}
else {
$header['item'] = isset($element['#element']['#title']) ? $element['#element']['#title'] : '';
}
$header['weight'] = t('Weight');
if (empty($element['#cardinality'])) {
$header['_operations_'] = '';
}
return $header;
}
/**
* 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.
*/
public static function buildElementRow($table_id, $row_index, array $element, $default_value, $weight, array $ajax_settings) {
if ($element['#child_keys']) {
foreach ($element['#child_keys'] as $child_key) {
if (isset($default_value[$child_key])) {
$element['#element'][$child_key]['#default_value'] = $default_value[$child_key];
}
}
}
else {
$element['#element']['#default_value'] = $default_value;
}
$row = [];
$row['_handle_'] = [];
if ($element['#child_keys'] && !empty($element['#header'])) {
foreach ($element['#child_keys'] as $child_key) {
$row[$child_key] = $element['#element'][$child_key];
}
}
else {
$row['_item_'] = $element['#element'];
}
$row['weight'] = [
'#type' => 'weight',
'#delta' => 1000,
'#title' => t('Item weight'),
'#title_display' => 'invisible',
'#attributes' => [
'class' => [
'yamlform-multiple-sort-weight',
],
],
'#default_value' => $weight,
];
// Allow users to add & remove rows if cardinality is not set.
if (empty($element['#cardinality'])) {
$row['_operations_'] = [];
$row['_operations_']['add'] = [
'#type' => 'image_button',
'#src' => drupal_get_path('module', 'yamlform') . '/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,
];
$row['_operations_']['remove'] = [
'#type' => 'image_button',
'#src' => drupal_get_path('module', 'yamlform') . '/images/icons/ex.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,
];
}
$row['#weight'] = $weight;
$row['#attributes']['class'][] = 'draggable';
return $row;
}
/****************************************************************************/
// Callbacks.
/****************************************************************************/
/**
* Form 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 form 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 = self::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.
$element['items']['#value'] = array_values($element['items']['#value']);
$form_state
->setValueForElement($element['items'], $element['items']['#value']);
NestedArray::setValue($form_state
->getUserInput(), $element['items']['#parents'], $element['items']['#value']);
// Rebuild the form.
$form_state
->setRebuild();
}
/**
* Form 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[] = [
'item' => '',
'text' => '',
];
}
}
// Add one item to the 'number of items'.
$number_of_items_storage_key = self::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);
// Rebuild the form.
$form_state
->setRebuild();
}
/**
* Form 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 = self::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 1.
if ($number_of_items != 1) {
$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);
// Rebuild the form.
$form_state
->setRebuild();
}
/**
* Form 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));
return $element['items'];
}
/**
* Validates form list element.
*/
public static function validateYamlFormMultiple(&$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\yamlform\Element\YamlFormEmailConfirm::validateYamlFormEmailConfirm
// @see \Drupal\yamlform\Element\YamlFormOtherBase::validateYamlFormOther
$values = NestedArray::getValue($form_state
->getValues(), $element['#parents']);
// Convert values to items.
$items = self::convertValuesToItems($element, $values['items']);
// Validate required items.
if (!empty($element['#required']) && empty($items)) {
if (isset($element['#required_error'])) {
$form_state
->setError($element, $element['#required_error']);
}
elseif (isset($element['#title'])) {
$form_state
->setError($element, t('@name field is required.', [
'@name' => $element['#title'],
]));
}
else {
$form_state
->setError($element);
}
return;
}
$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.
*
* @return string
* A unique key used to store the number of items for an element.
*/
public static function getStorageKey(array $element, $name) {
return 'yamlform_multiple__' . $element['#name'] . '__' . $name;
}
/**
* Convert an array containing of values (elements or _item_ and weight) to an array of items.
*
* @param array $element
* The element.
* @param array $values
* An array containing of item and weight.
*
* @return array
* An array of items.
*/
public static function convertValuesToItems(array &$element, array $values = []) {
// Sort the item values.
uasort($values, [
'Drupal\\Component\\Utility\\SortArray',
'sortByWeightElement',
]);
// Now build the associative array of items.
$items = [];
foreach ($values as $value) {
if (isset($value['_item_'])) {
if (!self::isEmpty($value['_item_'])) {
$items[] = $value['_item_'];
}
}
else {
unset($value['weight'], $value['_operations_']);
if (!self::isEmpty($value)) {
$items[] = $value;
}
}
}
return $items;
}
/**
* 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 !self::isEmpty($item);
});
}
else {
return FALSE;
}
}
}
Members
Name | Modifiers | Type | Description | Overrides |
---|---|---|---|---|
DependencySerializationTrait:: |
protected | property | An array of entity type IDs keyed by the property name of their storages. | |
DependencySerializationTrait:: |
protected | property | An array of service IDs keyed by property name used for serialization. | |
DependencySerializationTrait:: |
public | function | 1 | |
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. | 29 |
MessengerTrait:: |
public | function | Gets the messenger. | 29 |
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:: |
3 |
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. | 92 |
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. | 1 |
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. | |
YamlFormMultiple:: |
public static | function | Form submission handler for adding more items. | |
YamlFormMultiple:: |
public static | function | Form submission handler for adding an item. | |
YamlFormMultiple:: |
public static | function | Form submission AJAX callback the returns the list table. | |
YamlFormMultiple:: |
public static | function | Build a single element header. | |
YamlFormMultiple:: |
public static | function | Build a single element row. | |
YamlFormMultiple:: |
public static | function | Convert an array containing of values (elements or _item_ and weight) to an array of items. | |
YamlFormMultiple:: |
public | function |
Returns the element properties for this element. Overrides ElementInterface:: |
|
YamlFormMultiple:: |
public static | function | Get unique key used to store the number of items for an element. | |
YamlFormMultiple:: |
public static | function | Check if array is empty. | |
YamlFormMultiple:: |
public static | function | Process items and build multiple elements widget. | |
YamlFormMultiple:: |
public static | function | Form submission handler for removing an item. | |
YamlFormMultiple:: |
public static | function | Validates form list element. | |
YamlFormMultiple:: |
public static | function |
Determines how user input is mapped to an element's #value property. Overrides FormElement:: |