Issue #2940201 by xjm, amateescu, benjifisher, tedbow, tim.plunkett, Berdir: hook_field_widget_form_alter() can no longer affect the whole widget for multi-value fields

merge-requests/1654/head
Nathaniel Catchpole 2018-02-06 15:32:24 +00:00
parent 17a47b8e93
commit c924020c45
4 changed files with 174 additions and 10 deletions

View File

@ -241,6 +241,19 @@ abstract class WidgetBase extends PluginSettingsBase implements WidgetInterface
],
];
}
// Allow modules to alter the field multi-value widget form element.
// This hook can also be used for single-value fields.
$context = [
'form' => $form,
'widget' => $this,
'items' => $items,
'default' => $this->isDefaultValueWidget($form_state),
];
\Drupal::moduleHandler()->alter([
'field_widget_multivalue_form',
'field_widget_multivalue_' . $this->getPluginId() . '_form',
], $elements, $form_state, $context);
}
return $elements;

View File

@ -164,6 +164,11 @@ function hook_field_widget_info_alter(array &$info) {
/**
* Alter forms for field widgets provided by other modules.
*
* This hook can only modify individual elements within a field widget and
* cannot alter the top level (parent element) for multi-value fields. In most
* cases, you should use hook_field_widget_multivalue_form_alter() instead and
* loop over the elements.
*
* @param $element
* The field widget form element as constructed by
* \Drupal\Core\Field\WidgetBaseInterface::form().
@ -183,6 +188,7 @@ function hook_field_widget_info_alter(array &$info) {
* @see \Drupal\Core\Field\WidgetBaseInterface::form()
* @see \Drupal\Core\Field\WidgetBase::formSingleElement()
* @see hook_field_widget_WIDGET_TYPE_form_alter()
* @see hook_field_widget_multivalue_form_alter()
*/
function hook_field_widget_form_alter(&$element, \Drupal\Core\Form\FormStateInterface $form_state, $context) {
// Add a css class to widget form elements for all fields of type mytype.
@ -200,6 +206,11 @@ function hook_field_widget_form_alter(&$element, \Drupal\Core\Form\FormStateInte
* specific widget form, rather than using hook_field_widget_form_alter() and
* checking the widget type.
*
* This hook can only modify individual elements within a field widget and
* cannot alter the top level (parent element) for multi-value fields. In most
* cases, you should use hook_field_widget_multivalue_WIDGET_TYPE_form_alter()
* instead and loop over the elements.
*
* @param $element
* The field widget form element as constructed by
* \Drupal\Core\Field\WidgetBaseInterface::form().
@ -212,6 +223,7 @@ function hook_field_widget_form_alter(&$element, \Drupal\Core\Form\FormStateInte
* @see \Drupal\Core\Field\WidgetBaseInterface::form()
* @see \Drupal\Core\Field\WidgetBase::formSingleElement()
* @see hook_field_widget_form_alter()
* @see hook_field_widget_multivalue_WIDGET_TYPE_form_alter()
*/
function hook_field_widget_WIDGET_TYPE_form_alter(&$element, \Drupal\Core\Form\FormStateInterface $form_state, $context) {
// Code here will only act on widgets of type WIDGET_TYPE. For example,
@ -220,6 +232,74 @@ function hook_field_widget_WIDGET_TYPE_form_alter(&$element, \Drupal\Core\Form\F
$element['#autocomplete_route_name'] = 'mymodule.autocomplete_route';
}
/**
* Alter forms for multi-value field widgets provided by other modules.
*
* To alter the individual elements within the widget, loop over
* \Drupal\Core\Render\Element::children($elements).
*
* @param array $elements
* The field widget form elements as constructed by
* \Drupal\Core\Field\WidgetBase::formMultipleElements().
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
* @param array $context
* An associative array containing the following key-value pairs:
* - form: The form structure to which widgets are being attached. This may be
* a full form structure, or a sub-element of a larger form.
* - widget: The widget plugin instance.
* - items: The field values, as a
* \Drupal\Core\Field\FieldItemListInterface object.
* - default: A boolean indicating whether the form is being shown as a dummy
* form to set default values.
*
* @see \Drupal\Core\Field\WidgetBaseInterface::form()
* @see \Drupal\Core\Field\WidgetBase::formMultipleElements()
* @see hook_field_widget_multivalue_WIDGET_TYPE_form_alter()
*/
function hook_field_widget_multivalue_form_alter(array &$elements, \Drupal\Core\Form\FormStateInterface $form_state, array $context) {
// Add a css class to widget form elements for all fields of type mytype.
$field_definition = $context['items']->getFieldDefinition();
if ($field_definition->getType() == 'mytype') {
// Be sure not to overwrite existing attributes.
$elements['#attributes']['class'][] = 'myclass';
}
}
/**
* Alter multi-value widget forms for a widget provided by another module.
*
* Modules can implement hook_field_widget_multivalue_WIDGET_TYPE_form_alter() to
* modify a specific widget form, rather than using
* hook_field_widget_form_alter() and checking the widget type.
*
* To alter the individual elements within the widget, loop over
* \Drupal\Core\Render\Element::children($elements).
*
* @param array $elements
* The field widget form elements as constructed by
* \Drupal\Core\Field\WidgetBase::formMultipleElements().
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
* @param array $context
* An associative array. See hook_field_widget_multivalue_form_alter() for
* the structure and content of the array.
*
* @see \Drupal\Core\Field\WidgetBaseInterface::form()
* @see \Drupal\Core\Field\WidgetBase::formMultipleElements()
* @see hook_field_widget_multivalue_form_alter()
*/
function hook_field_widget_multivalue_WIDGET_TYPE_form_alter(array &$elements, \Drupal\Core\Form\FormStateInterface $form_state, array $context) {
// Code here will only act on widgets of type WIDGET_TYPE. For example,
// hook_field_widget_multivalue_mymodule_autocomplete_form_alter() will only
// act on widgets of type 'mymodule_autocomplete'.
// Change the autcomplete route for each autocomplete element within the
// multivalue widget.
foreach (Element::children($elements) as $delta => $element) {
$elements[$delta]['#autocomplete_route_name'] = 'mymodule.autocomplete_route';
}
}
/**
* @} End of "defgroup field_widget".
*/

View File

@ -113,6 +113,9 @@ class FormTest extends FieldTestBase {
// Check that hook_field_widget_form_alter() does not believe this is the
// default value form.
$this->assertNoText('From hook_field_widget_form_alter(): Default form is true.', 'Not default value form in hook_field_widget_form_alter().');
// Check that hook_field_widget_form_alter() does not believe this is the
// default value form.
$this->assertNoText('From hook_field_widget_multivalue_form_alter(): Default form is true.', 'Not default value form in hook_field_widget_form_alter().');
// Submit with invalid value (field-level validation).
$edit = [
@ -634,4 +637,44 @@ class FormTest extends FieldTestBase {
$this->assertEscaped("<script>alert('a configurable field');</script>");
}
/**
* Tests hook_field_widget_multivalue_form_alter().
*/
public function testFieldFormMultipleWidgetAlter() {
$this->widgetAlterTest('hook_field_widget_multivalue_form_alter');
}
/**
* Tests hook_field_widget_multivalue_WIDGET_TYPE_form_alter().
*/
public function testFieldFormMultipleWidgetTypeAlter() {
$this->widgetAlterTest('hook_field_widget_multivalue_WIDGET_TYPE_form_alter');
}
/**
* Tests widget alter hooks for a given hook name.
*/
protected function widgetAlterTest($hook) {
// Set a flag in state so that the hook implementations will run.
\Drupal::state()->set("field_test.$hook", TRUE);
// Create a field with fixed cardinality, configure the form to use a
// "multiple" widget.
$field_storage = $this->fieldStorageMultiple;
$field_name = $field_storage['field_name'];
$this->field['field_name'] = $field_name;
FieldStorageConfig::create($field_storage)->save();
FieldConfig::create($this->field)->save();
entity_get_form_display($this->field['entity_type'], $this->field['bundle'], 'default')
->setComponent($field_name, [
'type' => 'test_field_widget_multiple',
])
->save();
$this->drupalGet('entity_test/add');
$this->assertUniqueText("From $hook(): prefix on field_test_text parent element.");
$this->assertText("From $hook(): description on field_test_text child element.");
$this->drupalGet('entity_test/add');
}
}

View File

@ -15,6 +15,7 @@
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Render\Element;
use Drupal\field\FieldStorageConfigInterface;
require_once __DIR__ . '/field_test.entity.inc';
@ -100,16 +101,6 @@ function field_test_entity_display_build_alter(&$output, $context) {
* Implements hook_field_widget_form_alter().
*/
function field_test_field_widget_form_alter(&$element, FormStateInterface $form_state, $context) {
$field_definition = $context['items']->getFieldDefinition();
switch ($field_definition->getName()) {
case 'alter_test_text':
drupal_set_message('Field size: ' . $context['widget']->getSetting('size'));
break;
case 'alter_test_options':
drupal_set_message('Widget type: ' . $context['widget']->getPluginId());
break;
}
// Set a message if this is for the form displayed to set default value for
// the field.
if ($context['default']) {
@ -117,6 +108,43 @@ function field_test_field_widget_form_alter(&$element, FormStateInterface $form_
}
}
/**
* Implements hook_field_widget_multivalue_form_alter().
*/
function field_test_field_widget_multivalue_form_alter(array &$elements, FormStateInterface $form_state, array $context) {
_field_test_alter_widget("hook_field_widget_multivalue_form_alter", $elements, $form_state, $context);
}
/**
* Implements hook_field_widget_multivalue_form_alter().
*/
function field_test_field_widget_multivalue_text_textfield_form_alter(array &$elements, FormStateInterface $form_state, array $context) {
_field_test_alter_widget("hook_field_widget_multivalue_WIDGET_TYPE_form_alter", $elements, $form_state, $context);
}
/**
* Sets up alterations for widget alter tests.
*
* @see \Drupal\field\Tests\FormTest::widgetAlterTest()
*/
function _field_test_alter_widget($hook, array &$elements, FormStateInterface $form_state, array $context) {
// Set a message if this is for the form displayed to set default value for
// the field.
if ($context['default']) {
drupal_set_message("From $hook(): Default form is true.");
}
if (\Drupal::state()->get("field_test.$hook")) {
$name = $context['items']->getFieldDefinition()->getName();
if ($name === 'field_test_text') {
$elements['#prefix'] = "From $hook(): prefix on $name parent element.";
foreach (Element::children($elements) as $delta => $element) {
$elements[$delta]['#description'] = "From $hook(): description on $name child element.";
}
}
}
}
/**
* Implements hook_query_TAG_alter() for tag 'efq_table_prefixing_test'.
*