class MultiValue in SAML Authentication 4.x
Same name and namespace in other branches
- 8.3 src/Element/MultiValue.php \Drupal\samlauth\Element\MultiValue
Provides a multi-value form element.
Shamelessly copied from multivalue_form_element, on who we'll depend in 4.x. (At the time of adding this, they seemed to have issues with D9 compatibility.)
Properties:
- #cardinality: the cardinality of this element. Can be a positive number or MultiValue::CARDINALITY_UNLIMITED to set it as unlimited. The default value is unlimited.
- #add_empty: Applicable only for unlimited cardinality: 0 to add no extra empty sets values below existing values. FALSE to add no extra empty values, except display one when there are no existing values. By default, one empty value is added.
- #add_more_label: the label to use for the "add more" button. The default value is "Add another item".
Use this element as a wrapper for other form elements. They will be repeated based on the cardinality specified, organised under a "delta", similar to field widgets. Deltas are sortable. Example of an element that allows to specify unlimited job title strings:
$form['job_titles'] = [
'#type' => 'multivalue',
'#title' => $this
->t('Job titles'),
'title' => [
'#type' => 'textfield',
'#title' => $this
->t('Job title'),
'#title_display' => 'invisible',
],
];
Example of an element with multiple form elements inside. Each "delta" will contain all the children of the main element. This example allows to specify up to three pairs of name/e-mail values:
$form['contacts'] = [
'#type' => 'multivalue',
'#title' => $this->t('Contacts'),
'#cardinality' => 3,
'name' => [
'#type' => 'textfield',
'#title' => $this->t('Name'),
],
'mail' => [
'#type' => 'email',
'#title' => $this->t('E-mail'),
],
];
@endCode
Default values can be set to the multi-value form element. Never set them in
child elements as they will be overridden.
Pass the default values keyed by their delta:
@code
$form['contacts'] = [
'#type' => 'multivalue',
'#default_value' => [
0 => ['name' => 'Bob', 'mail' => 'bob@example.com'],
1 => ['name' => 'Ted', 'mail' => 'ted@example.com'],
],
...
];
@endCode
If only one child element is present, said child element name can be omitted
from the default value array:
@code
$form['job_titles'] = [
'#type' => 'multivalue',
'#title' => $this->t('Job titles'),
'title' => [
...
],
'#default_value' => [
'Foo',
'Bar',
],
];
Note that the values in the form state will always have the full array structure, including the child element name.
The element can be marked as required. The required will apply *only* to the first delta. This behaviour is consistent with entity fields. How child elements are marked as required depends on their own #required property. Given the multi-value element is marked as required:
- if no children is marked as required, all the children of the first delta will be set as required.
- if any children is marked as required, then the required status specified for the children will be retained for the first delta.
For all the deltas after the first, or when the main element is not marked as required, the #required property of the child elements will be set to FALSE.
Example of specifying only some elements are required:
$form['contacts'] = [
'#type' => 'multivalue',
'#title' => $this->t('Contacts'),
'#required' => TRUE,
'name' => [
'#type' => 'textfield',
'#title' => $this->t('Name'),
'#required' => TRUE,
],
'mail' => [
'#type' => 'email',
'#title' => $this->t('E-mail'),
],
];
@endCode
If you want to have some children required in all the deltas, use #states
to mark the wanted elements as required if one of the other children is
filled.
<h3>Plugin annotation</h3>
@code
@FormElement("samlmultivalue")
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\samlauth\Element\MultiValue
- 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 MultiValue
1 #type use of MultiValue
- SamlauthConfigureForm::buildForm in src/
Form/ SamlauthConfigureForm.php - Form constructor.
File
- src/
Element/ MultiValue.php, line 135
Namespace
Drupal\samlauth\ElementView source
class MultiValue extends FormElement {
/**
* Value indicating that an instance of this element accepts unlimited values.
*/
const CARDINALITY_UNLIMITED = -1;
/**
* {@inheritdoc}
*/
public function getInfo() {
$class = get_class($this);
return [
'#input' => TRUE,
'#theme' => 'field_multiple_value_form',
'#cardinality_multiple' => TRUE,
'#description' => NULL,
'#cardinality' => self::CARDINALITY_UNLIMITED,
'#add_more_label' => $this
->t('Add another item'),
'#process' => [
[
$class,
'processMultiValue',
],
[
$class,
'processAjaxForm',
],
],
'#element_validate' => [
[
$class,
'validateMultiValue',
],
],
];
}
/**
* Processes a multi-value form element.
*
* @param array $element
* The element being processed.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current form state.
* @param array $complete_form
* The complete form.
*
* @return array
* The processed element.
*/
public static function processMultiValue(array &$element, FormStateInterface $form_state, array &$complete_form) : array {
$element_name = end($element['#array_parents']);
$parents = $element['#parents'];
$cardinality = $element['#cardinality'];
$element['#tree'] = TRUE;
$element['#field_name'] = $element_name;
$element_state = static::getElementState($parents, $element_name, $form_state);
if ($element_state === NULL) {
// The default value should always have numeric keys. The initial count
// is based on the default value... except if #add_empty says to add an
// extra item only for 0 values.
if (!$element['#default_value'] && isset($element['#add_empty']) && $element['#add_empty'] === FALSE) {
$element_state = [
'items_count' => 1,
];
}
else {
$element_state = [
'items_count' => count($element['#default_value'] ?? []),
];
}
static::setElementState($parents, $element_name, $form_state, $element_state);
}
// Determine the number of elements to display.
if ($cardinality !== self::CARDINALITY_UNLIMITED) {
$nr_elements = $cardinality;
}
elseif (!empty($element['#disabled']) || isset($element['#add_empty']) && !$element['#add_empty']) {
$nr_elements = $element_state['items_count'];
}
else {
$nr_elements = $element_state['items_count'] + 1;
}
// Extract the elements that will have to be repeated for each delta.
$children = [];
foreach (Element::children($element) as $child) {
$children[$child] = $element[$child];
unset($element[$child]);
}
$value = is_array($element['#value']) ? $element['#value'] : [];
// Re-key the elements so that deltas are consecutive.
$value = array_values($value);
for ($i = 0; $i < $nr_elements; $i++) {
$element[$i] = $children;
if (isset($value[$i])) {
static::setDefaultValue($element[$i], $value[$i]);
}
static::setRequiredProperty($element[$i], $i, $element['#required']);
$element[$i]['_weight'] = [
'#type' => 'weight',
'#title' => t('Weight for row @number', [
'@number' => $i + 1,
]),
'#title_display' => 'invisible',
'#default_value' => $i,
'#weight' => 100,
];
}
if ($cardinality === self::CARDINALITY_UNLIMITED && !$form_state
->isProgrammed()) {
$id_prefix = implode('-', $parents);
$wrapper_id = Html::getUniqueId($id_prefix . '-add-more-wrapper');
$element['#prefix'] = '<div id="' . $wrapper_id . '">';
$element['#suffix'] = '</div>';
$element['add_more'] = [
'#type' => 'submit',
'#name' => strtr($id_prefix, '-', '_') . '_add_more',
'#value' => $element['#add_more_label'],
'#attributes' => [
'class' => [
'multivalue-add-more-submit',
],
],
'#limit_validation_errors' => [
$element['#array_parents'],
],
'#submit' => [
[
static::class,
'addMoreSubmit',
],
],
'#ajax' => [
'callback' => [
static::class,
'addMoreAjax',
],
'wrapper' => $wrapper_id,
'effect' => 'fade',
],
];
}
return $element;
}
/**
* Validates a multi-value form element.
*
* Used to clean and sort the submitted values in the form state.
*
* @param array $element
* The element being processed.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current form state.
* @param array $complete_form
* The complete form.
*/
public static function validateMultiValue(array &$element, FormStateInterface $form_state, array &$complete_form) : void {
$input_exists = FALSE;
$values = NestedArray::getValue($form_state
->getValues(), $element['#parents'], $input_exists);
if (!$input_exists) {
return;
}
// Remove the 'value' of the 'add more' button.
unset($values['add_more']);
// Sort the values based on the weight.
usort($values, function ($a, $b) {
return SortArray::sortByKeyInt($a, $b, '_weight');
});
foreach ($values as $delta => &$delta_values) {
// Remove the weight element value from the submitted data.
unset($delta_values['_weight']);
// Determine if all the elements of this delta are empty.
$is_empty_delta = array_reduce($delta_values, function (bool $carry, $value) : bool {
if (is_array($value)) {
return $carry && empty(array_filter($value));
}
else {
return $carry && ($value === NULL || $value === '');
}
}, TRUE);
// If all the elements are empty, drop this delta.
if ($is_empty_delta) {
unset($values[$delta]);
}
}
// Re-key the elements so that deltas are consecutive.
$values = array_values($values);
// Set the value back to the form state.
$form_state
->setValueForElement($element, $values);
}
/**
* {@inheritdoc}
*/
public static function valueCallback(&$element, $input, FormStateInterface $form_state) {
if ($input !== FALSE) {
return $input;
}
$value = [];
$element += [
'#default_value' => [],
];
$children_keys = Element::children($element, FALSE);
$first_child = reset($children_keys);
$children_count = count($children_keys);
foreach ($element['#default_value'] as $delta => $default_value) {
// Enforce numeric deltas.
if (!is_numeric($delta)) {
continue;
}
// Allow to omit the child element name when one single child exists and
// the values are simple literals. This allows to pass
// [0 => 'value 1', 1 => 'value 2'] instead of
// [0 => ['element_name' => 'value 1', 1 => ['element_name' => ...]].
if ($children_count === 1 && !is_array($default_value)) {
$value[$delta] = [
$first_child => $default_value,
];
}
else {
$value[$delta] = $default_value;
}
}
return $value;
}
/**
* Handles the "Add another item" button AJAX request.
*
* @param array $form
* The build form.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The form state.
*
* @see \Drupal\Core\Field\WidgetBase::addMoreSubmit()
*/
public static function addMoreSubmit(array $form, FormStateInterface $form_state) : void {
$button = $form_state
->getTriggeringElement();
// Go one level up in the form, to the widgets container.
$element = NestedArray::getValue($form, array_slice($button['#array_parents'], 0, -1));
$element_name = $element['#field_name'];
$parents = $element['#parents'];
// Increment the items count.
$element_state = static::getElementState($parents, $element_name, $form_state);
$element_state['items_count']++;
static::setElementState($parents, $element_name, $form_state, $element_state);
$form_state
->setRebuild();
}
/**
* Ajax callback for the "Add another item" button.
*
* @param array $form
* The build form.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The form state.
*
* @return array|null
* The element.
*
* @see \Drupal\Core\Field\WidgetBase::addMoreAjax()
*/
public static function addMoreAjax(array $form, FormStateInterface $form_state) : ?array {
$button = $form_state
->getTriggeringElement();
// Go one level up in the form, to the widgets container.
$element = NestedArray::getValue($form, array_slice($button['#array_parents'], 0, -1));
// Ensure the widget allows adding additional items.
if ($element['#cardinality'] != FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED) {
return NULL;
}
// Adding until #1091852 gets solved.
if (!empty($element['#description_suffix'])) {
$element['#description'] .= ' ' . $element['#description_suffix'];
}
return $element;
}
/**
* Sets the default value for the child elements.
*
* @param array $elements
* The elements array.
* @param array $value
* An array of values, keyed by the children element name.
*/
public static function setDefaultValue(array &$elements, array $value) : void {
// @todo Handle nested elements.
foreach (Element::children($elements, FALSE) as $child) {
if (isset($value[$child])) {
$elements[$child]['#default_value'] = $value[$child];
}
}
}
/**
* Sets the required property for the delta being processed.
*
* @param array $elements
* The array containing the child elements.
* @param int $delta
* The delta currently being processed.
* @param bool $required
* If the main element is required or not.
*/
protected static function setRequiredProperty(array &$elements, int $delta, bool $required) : void {
if ($delta === 0 && $required) {
// If any of the children is set as required, the first delta is already
// set correctly.
foreach ($elements as $element) {
if (isset($element['#required']) && $element['#required'] === TRUE) {
return;
}
}
// Set all children as required otherwise.
foreach ($elements as &$element) {
$element['#required'] = TRUE;
}
return;
}
// For every other delta or when the main element is marked as not required,
// none of the children should be required neither.
foreach ($elements as &$element) {
$element['#required'] = FALSE;
}
}
/**
* Retrieves processing information about the element from $form_state.
*
* This method is static so that it can be used in static Form API callbacks.
*
* @param array $parents
* The array of #parents where the element lives in the form.
* @param string $element_name
* The field name.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The form state.
*
* @return array
* An array with the following key/value pairs:
* - items_count: The number of sub-elements to display for the element.
*
* @see \Drupal\Core\Field\WidgetBase::getWidgetState()
*/
public static function getElementState(array $parents, string $element_name, FormStateInterface $form_state) : ?array {
return NestedArray::getValue($form_state
->getStorage(), static::getElementStateParents($parents, $element_name));
}
/**
* Stores processing information about the element in $form_state.
*
* This method is static so that it can be used in static Form API #callbacks.
*
* @param array $parents
* The array of #parents where the element lives in the form.
* @param string $element_name
* The element name.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The form state.
* @param array $field_state
* The array of data to store. See getElementState() for the structure and
* content of the array.
*
* @see \Drupal\Core\Field\WidgetBase::setWidgetState()
*/
public static function setElementState(array $parents, string $element_name, FormStateInterface $form_state, array $field_state) : void {
NestedArray::setValue($form_state
->getStorage(), static::getElementStateParents($parents, $element_name), $field_state);
}
/**
* Returns the location of processing information within $form_state.
*
* @param array $parents
* The array of #parents where the element lives in the form.
* @param string $element_name
* The element name.
*
* @return array
* The location of processing information within $form_state.
*
* @see \Drupal\Core\Field\WidgetBase::getWidgetStateParents()
*/
protected static function getElementStateParents(array $parents, string $element_name) : array {
// phpcs:disable
// Element processing data is placed at
// $form_state->get(['multivalue_form_element_storage', '#parents', ...$parents..., '#elements', $element_name]),
// to avoid clashes between field names and $parents parts.
// phpcs:enable
return array_merge([
'multivalue_form_element_storage',
'#parents',
], $parents, [
'#elements',
$element_name,
]);
}
}
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. | |
MultiValue:: |
public static | function | Ajax callback for the "Add another item" button. | |
MultiValue:: |
public static | function | Handles the "Add another item" button AJAX request. | |
MultiValue:: |
constant | Value indicating that an instance of this element accepts unlimited values. | ||
MultiValue:: |
public static | function | Retrieves processing information about the element from $form_state. | |
MultiValue:: |
protected static | function | Returns the location of processing information within $form_state. | |
MultiValue:: |
public | function |
Returns the element properties for this element. Overrides ElementInterface:: |
|
MultiValue:: |
public static | function | Processes a multi-value form element. | |
MultiValue:: |
public static | function | Sets the default value for the child elements. | |
MultiValue:: |
public static | function | Stores processing information about the element in $form_state. | |
MultiValue:: |
protected static | function | Sets the required property for the delta being processed. | |
MultiValue:: |
public static | function | Validates a multi-value form element. | |
MultiValue:: |
public static | function |
Determines how user input is mapped to an element's #value property. Overrides FormElement:: |
|
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. |