diff --git a/modules/ui_patterns_field_formatters/src/Plugin/UiPatterns/Source/FieldFormatterFormBase.php b/modules/ui_patterns_field_formatters/src/Plugin/UiPatterns/Source/FieldFormatterFormBase.php index dac4e8952cb1882b3ae2292db22e6cc8d17f7e60..f67efc486b3aa047425e9d3e5e6550f36a33c242 100644 --- a/modules/ui_patterns_field_formatters/src/Plugin/UiPatterns/Source/FieldFormatterFormBase.php +++ b/modules/ui_patterns_field_formatters/src/Plugin/UiPatterns/Source/FieldFormatterFormBase.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace Drupal\ui_patterns_field_formatters\Plugin\UiPatterns\Source; +use Drupal\Component\Utility\Html; use Drupal\Core\Field\BaseFieldDefinition; use Drupal\Core\Field\FieldDefinitionInterface; use Drupal\Core\Field\FieldStorageDefinitionInterface; @@ -146,7 +147,7 @@ abstract class FieldFormatterFormBase extends FieldFormatterSourceBase { 'type', ]; $formatter_type = $this->getSettingsFromConfiguration($type_path); - + $uniqueID = Html::getUniqueId('field-formatter-settings-ajax'); // Get the formatter settings from configuration. $settings = $this->getSettingsFieldFormatter($form_state, $field_storage, $formatter_options, $formatter_type); $form['type'] = [ @@ -158,7 +159,7 @@ abstract class FieldFormatterFormBase extends FieldFormatterSourceBase { // display. '#ajax' => [ 'callback' => [__CLASS__, 'onFormatterTypeChange'], - 'wrapper' => 'field-formatter-settings-ajax', + 'wrapper' => $uniqueID, 'method' => 'replace', ], ]; @@ -181,7 +182,7 @@ abstract class FieldFormatterFormBase extends FieldFormatterSourceBase { } $form['settings'] = $settings_form; - $form['settings']['#prefix'] = '<div id="field-formatter-settings-ajax">' . ($form['settings']['#prefix'] ?? ''); + $form['settings']['#prefix'] = '<div id="' . $uniqueID . '">' . ($form['settings']['#prefix'] ?? ''); $form['settings']['#suffix'] = ($form['settings']['#suffix'] ?? '') . '</div>'; return TRUE; } diff --git a/src/Element/ComponentForm.php b/src/Element/ComponentForm.php index 6388bb9bb224f73cbc16440efa813af1d46bff1a..63f38655f6b6659fa68607cdcf50913320105a0c 100644 --- a/src/Element/ComponentForm.php +++ b/src/Element/ComponentForm.php @@ -75,9 +75,9 @@ class ComponentForm extends ComponentFormBase { if ($input) { $value = [ 'component_id' => $input['component_id'] ?? NULL, - 'variant_id' => $input['component_form']['variant_id'] ?? NULL, - 'props' => $input['component_form']['props'] ?? [], - 'slots' => $input['component_form']['slots'] ?? [], + 'variant_id' => $input['variant_id'] ?? NULL, + 'props' => $input['props'] ?? [], + 'slots' => $input['slots'] ?? [], ]; $element['#default_value'] = $value; return $value; @@ -99,20 +99,23 @@ class ComponentForm extends ComponentFormBase { public static function buildForm(array &$element, FormStateInterface $form_state) { $trigger_element = $form_state->getTriggeringElement(); if ($form_state->isRebuilding() && isset($trigger_element['#ui_patterns'])) { - $value = $form_state->getValue(self::getComponentFormStateParents($trigger_element['#parents'])); - self::valueCallback($element, $value, $form_state); + $parents_for_trigger = self::getComponentFormStateParents($trigger_element['#parents']); + if ($parents_for_trigger == $element['#parents']) { + $value = $form_state->getValue($parents_for_trigger); + self::valueCallback($element, $value, $form_state); + } } $initial_component_id = $element['#component_id'] ?? NULL; $component_id = $initial_component_id ?? $element['#default_value']['component_id'] ?? NULL; - $wrapper_id = 'ui-patterns-component'; + $wrapper_id = static::getElementId($element, 'ui-patterns-component'); if ($initial_component_id === NULL) { $element["component_id"] = self::expandAjax(self::buildComponentSelectorForm( $wrapper_id, $component_id )); } - $element["component_form"] = self::buildComponentForm( + self::buildComponentForm( $element, $wrapper_id, $component_id @@ -128,25 +131,22 @@ class ComponentForm extends ComponentFormBase { * The component form. */ private static function buildComponentForm( - array $element, + array &$element, string $wrapper_id, ?string $component_id, ): array { - $form = [ - '#type' => 'container', - '#prefix' => '<div id="' . $wrapper_id . '">', - '#suffix' => '</div>', - ]; + $element['#prefix'] = '<div id="' . $wrapper_id . '">'; + $element['#suffix'] = '</div>'; if (!$component_id) { - return $form; + return $element; } - $form['variant_id'] = self::buildComponentVariantSelectorForm( + $element['variant_id'] = self::buildComponentVariantSelectorForm( $element, $element['#default_value']['variant_id'] ?? NULL, ); - $form['slots'] = self::buildSlotsForm($element, $component_id); - $form['props'] = self::buildPropsForm($element, $component_id); - return $form; + $element['slots'] = self::buildSlotsForm($element, $component_id); + $element['props'] = self::buildPropsForm($element, $component_id); + return $element; } /** @@ -174,7 +174,7 @@ class ComponentForm extends ComponentFormBase { "#options" => $options, '#default_value' => $selected_component_id, '#ajax' => [ - 'callback' => [self::class, 'changeSelectorFormChangeAjax'], + 'callback' => [static::class, 'changeSelectorFormChangeAjax'], 'wrapper' => $wrapper_id, 'effect' => 'fade', ], @@ -195,7 +195,7 @@ class ComponentForm extends ComponentFormBase { array $element, string|NULL $default_variant_id, ): array { - $component = self::getComponent($element); + $component = static::getComponent($element); $definition = $component->getPluginDefinition(); if (!isset($definition["variants"])) { return []; @@ -256,7 +256,7 @@ class ComponentForm extends ComponentFormBase { $parents = $form_state->getTriggeringElement()['#array_parents']; $sub_form = NestedArray::getValue($form, array_slice($parents, 0, -1)); $form_state->setRebuild(); - return $sub_form['component_form']; + return $sub_form; } /** diff --git a/src/Element/ComponentFormBase.php b/src/Element/ComponentFormBase.php index 4fcbc693eaee529a5bf686f0a7918ef5a33ac796..052d66243db402092123659f2443f6743fe39de1 100644 --- a/src/Element/ComponentFormBase.php +++ b/src/Element/ComponentFormBase.php @@ -2,6 +2,7 @@ namespace Drupal\ui_patterns\Element; +use Drupal\Component\Utility\Html; use Drupal\Core\Plugin\Component; use Drupal\Core\Render\Element\FormElementBase; @@ -10,6 +11,18 @@ use Drupal\Core\Render\Element\FormElementBase; */ abstract class ComponentFormBase extends FormElementBase { + /** + * Get a unique element id based on the parents and a parameter. + */ + protected static function getElementId(array $element, string $base_id): string { + $parents = (array_key_exists("#array_parents", $element) && is_array($element["#array_parents"])) ? + $element["#array_parents"] : []; + $returned = (count($parents) > 0) ? + Html::getId(implode("_", $parents) . "_" . $base_id) + : Html::getId($base_id); + return $returned; + } + /** * Expand each ajax element with ajax urls. * @@ -47,8 +60,14 @@ abstract class ComponentFormBase extends FormElementBase { * The parents to locate the form builder. */ protected static function getComponentFormStateParents(array $parents): array { - $needle = array_search('component_form', $parents); - return array_slice($parents, 0, $needle); + $reverse_parents = array_reverse($parents); + $needle = array_search('ui_patterns', $reverse_parents); + if (FALSE === $needle) { + return []; + } + $sliced = count($parents) - $needle; + $returned = array_slice($parents, 0, $sliced); + return $returned; } } diff --git a/src/Element/ComponentPropsForm.php b/src/Element/ComponentPropsForm.php index f8661f46eed8bf9d334869ba701e0db1d6b7a330..01c949f419e40b15baf48e886777f11a4d75dc49 100644 --- a/src/Element/ComponentPropsForm.php +++ b/src/Element/ComponentPropsForm.php @@ -56,7 +56,7 @@ class ComponentPropsForm extends ComponentFormBase { * Build props forms. */ public static function buildForm(array &$element, FormStateInterface $form_state): array { - $component = self::getComponent($element); + $component = static::getComponent($element); $contexts = $element['#source_contexts'] ?? []; $props = $component->metadata->schema['properties']; if (empty($props)) { @@ -64,7 +64,7 @@ class ComponentPropsForm extends ComponentFormBase { } $configuration = $element['#default_value']['props'] ?? []; foreach ($props as $prop_id => $prop) { - $element[$prop_id] = self::buildPropForm($form_state, $prop_id, $prop, $configuration[$prop_id] ?? [], $contexts); + $element[$prop_id] = static::buildPropForm($element, $form_state, $prop_id, $prop, $configuration[$prop_id] ?? [], $contexts); } if (count(Element::children($element)) === 0) { hide($element); @@ -113,18 +113,18 @@ class ComponentPropsForm extends ComponentFormBase { /** * Build single prop form. */ - protected static function buildPropForm(FormStateInterface $form_state, string $prop_id, array $definition, array $configuration, array $source_contexts): array { - $sources = self::getSources($prop_id, $definition, $configuration, $source_contexts); - $selected_source = self::getSelectedSource($configuration, $sources); + protected static function buildPropForm(array $element, FormStateInterface $form_state, string $prop_id, array $definition, array $configuration, array $source_contexts): array { + $sources = static::getSources($prop_id, $definition, $configuration, $source_contexts); + $selected_source = static::getSelectedSource($configuration, $sources); if (!$selected_source) { - $selected_source = self::getDefaultSource($prop_id, $definition, $configuration, $source_contexts); + $selected_source = static::getDefaultSource($prop_id, $definition, $configuration, $source_contexts); } if (!$selected_source) { return []; } - $wrapper_id = 'ui-patterns-prop-item-' . $prop_id; - $source_selector = self::buildSourceSelector($sources, $selected_source, $wrapper_id); - $source_form = self::getSourcePluginForm($form_state, $selected_source, $wrapper_id); + $wrapper_id = static::getElementId($element, 'ui-patterns-prop-item-' . $prop_id); + $source_selector = static::buildSourceSelector($sources, $selected_source, $wrapper_id); + $source_form = static::getSourcePluginForm($form_state, $selected_source, $wrapper_id); $prop_type = $definition['ui_patterns']['type_definition']; $build = [ '#type' => 'fieldset', @@ -184,7 +184,7 @@ class ComponentPropsForm extends ComponentFormBase { '#prop_definition' => $selected_source->getPropDefinition(), '#ajax' => [ 'callback' => [ - self::class, + static::class, 'switchSourceForm', ], 'wrapper' => $wrapper_id, @@ -209,7 +209,7 @@ class ComponentPropsForm extends ComponentFormBase { $source_plugin_manager = \Drupal::service("plugin.manager.ui_patterns_source"); $plugin_configuration = SourcePluginManager::buildPluginConfiguration($prop_id, $prop_definition, []); $source = $source_plugin_manager->createInstance($source_id, $plugin_configuration); - return self::getSourcePluginForm($form_state, $source, $wrapper_id); + return static::getSourcePluginForm($form_state, $source, $wrapper_id); } } diff --git a/src/Element/ComponentSlotsForm.php b/src/Element/ComponentSlotsForm.php index 7b22d5e17abc2cbc0c5c8174a631e242def019cc..8103257511446d3de73e51a53234b95c4c3adc6b 100644 --- a/src/Element/ComponentSlotsForm.php +++ b/src/Element/ComponentSlotsForm.php @@ -74,7 +74,7 @@ class ComponentSlotsForm extends ComponentFormBase { foreach ($valid_source_plugins as $valid_source_plugin) { $options[$valid_source_plugin->getPluginId()] = $valid_source_plugin->label(); } - $component = self::getComponent($element); + $component = static::getComponent($element); if (!isset($component->metadata->slots) || count( $component->metadata->slots ) === 0) { @@ -83,7 +83,7 @@ class ComponentSlotsForm extends ComponentFormBase { } $configuration = $element['#default_value']['slots'] ?? []; foreach ($component->metadata->slots as $slot_id => $slot) { - $element[$slot_id] = self::buildSlotForm($form_state, $slot_id, $slot, $configuration[$slot_id] ?? [], $options, $source_contexts); + $element[$slot_id] = static::buildSlotForm($element, $form_state, $slot_id, $slot, $configuration[$slot_id] ?? [], $options, $source_contexts); } return $element; } @@ -91,20 +91,20 @@ class ComponentSlotsForm extends ComponentFormBase { /** * Build single slot form. */ - protected static function buildSlotForm(FormStateInterface $form_state, string $slot_id, array $definition, array $configuration, array $options, array $source_contexts): array { - $element = []; - $wrapper_id = 'ui-patterns-slot-' . $slot_id; - $element['#slot_id'] = $slot_id; - $element['sources'] = self::buildSourcesForm($form_state, $slot_id, $definition, $configuration, $wrapper_id, $source_contexts); - $element['add_more_button'] = self::buildSourceSelector($slot_id, $wrapper_id, $options); - return $element; + protected static function buildSlotForm(array $element, FormStateInterface $form_state, string $slot_id, array $definition, array $configuration, array $options, array $source_contexts): array { + $form = []; + $wrapper_id = static::getElementId($element, 'ui-patterns-slot-' . $slot_id); + $form['#slot_id'] = $slot_id; + $form['sources'] = static::buildSourcesForm($element, $form_state, $slot_id, $definition, $configuration, $wrapper_id, $source_contexts); + $form['add_more_button'] = static::buildSourceSelector($element, $slot_id, $wrapper_id, $options); + return $form; } /** * Build single slot's sources form. */ - protected static function buildSourcesForm(FormStateInterface $form_state, string $slot_id, array $definition, array $configuration, string $wrapper_id, array $source_contexts): array { - $element = [ + protected static function buildSourcesForm(array $element, FormStateInterface $form_state, string $slot_id, array $definition, array $configuration, string $wrapper_id, array $source_contexts): array { + $form = [ '#theme' => 'field_multiple_value_form', '#title' => $definition['title'], '#cardinality_multiple' => TRUE, @@ -113,58 +113,58 @@ class ComponentSlotsForm extends ComponentFormBase { ]; // Add fake #field_name to avoid errors from // template_preprocess_field_multiple_value_form. - $element['#field_name'] = "foo"; + $form['#field_name'] = "foo"; if (!isset($configuration['sources'])) { - return $element; + return $form; } foreach ($configuration['sources'] as $delta => $source_configuration) { if (!isset($source_configuration['source_id'])) { continue; } - $element[$delta] = self::buildSourceForm($form_state, $slot_id, $definition, $source_configuration, $delta, $wrapper_id, $source_contexts); + $form[$delta] = static::buildSourceForm($element, $form_state, $slot_id, $definition, $source_configuration, $delta, $wrapper_id, $source_contexts); } - return $element; + return $form; } /** * Build single source form. */ - protected static function buildSourceForm(FormStateInterface $form_state, string $slot_id, array $definition, array $configuration, int $delta, string $wrapper_id, array $source_contexts): array { - $element = []; + protected static function buildSourceForm(array $element, FormStateInterface $form_state, string $slot_id, array $definition, array $configuration, int $delta, string $wrapper_id, array $source_contexts): array { + $form = []; $sources_manager = \Drupal::service("plugin.manager.ui_patterns_source"); $source = $sources_manager->createInstance( $configuration['source_id'], SourcePluginManager::buildPluginConfiguration($slot_id, $definition, $configuration, $source_contexts) ); - $element['source'] = $source->settingsForm([], $form_state); - $element['source_id'] = [ + $form['source'] = $source->settingsForm([], $form_state); + $form['source_id'] = [ '#type' => 'hidden', '#value' => $source->getPluginId(), ]; - $element['_weight'] = [ + $form['_weight'] = [ '#type' => 'weight', '#title' => t( 'Weight for row @number', ['@number' => $delta + 1] ), '#title_display' => 'invisible', - '#delta' => count($element), + '#delta' => count($form), '#default_value' => $configuration['_weight'] ?? $delta, '#weight' => 100, ]; - $element['_remove'] = self::buildRemoveSourceButton($slot_id, $wrapper_id, $delta); - return $element; + $form['_remove'] = static::buildRemoveSourceButton($element, $slot_id, $wrapper_id, $delta); + return $form; } /** * Build widget to remove source. */ - protected static function buildRemoveSourceButton(string $slot_id, string $wrapper_id, int $delta): array { - $delete_action = [ + protected static function buildRemoveSourceButton(array $element, string $slot_id, string $wrapper_id, int $delta): array { + $remove_action = [ '#type' => 'submit', '#name' => strtr($slot_id, '-', '_') . $delta . '_remove', - '#value' => t('Delete'), - '#submit' => [self::class . '::removeSlotItemSubmitAjax'], + '#value' => t('Remove'), + '#submit' => [static::class . '::removeSource'], '#access' => TRUE, '#delta' => $delta, '#ui_patterns' => [ @@ -172,7 +172,7 @@ class ComponentSlotsForm extends ComponentFormBase { ], '#slot_id' => $slot_id, '#ajax' => [ - 'callback' => [self::class, 'removeSlotItemAjax'], + 'callback' => [static::class, 'refreshAfterSourceRemoval'], 'wrapper' => $wrapper_id, 'effect' => 'fade', ], @@ -181,7 +181,7 @@ class ComponentSlotsForm extends ComponentFormBase { '#type' => 'ui_patterns_actions', '#ui_patterns_header' => TRUE, 'dropdown_actions' => [ - self::expandComponentButton($delete_action), + static::expandComponentButton($element, $remove_action), ], ]; } @@ -189,15 +189,15 @@ class ComponentSlotsForm extends ComponentFormBase { /** * Build source selector. */ - protected static function buildSourceSelector(string $slot_id, string $wrapper_id, array $options): array { + protected static function buildSourceSelector(array $element, string $slot_id, string $wrapper_id, array $options): array { $action_buttons = []; foreach ($options as $source_id => $source_label) { - $action_buttons[$source_id] = self::expandComponentButton([ + $action_buttons[$source_id] = static::expandComponentButton($element, [ '#type' => 'submit', '#name' => strtr($slot_id, '-', '_') . $source_id . '_add_more', '#value' => t('Add %source', ['%source' => $source_label]), '#submit' => [ - self::class . '::addSlotItemSubmitAjax', + static::class . '::addSource', ], '#access' => TRUE, '#ui_patterns' => TRUE, @@ -205,60 +205,78 @@ class ComponentSlotsForm extends ComponentFormBase { '#source_id' => $source_id, '#ajax' => [ 'callback' => [ - self::class, - 'addSlotItemAjax', + static::class, + 'refreshAfterSourceAddition', ], + 'wrapper' => $wrapper_id, 'effect' => 'fade', ], ]); } - return self::buildComponentDropbutton($action_buttons); + return static::buildComponentDropbutton($action_buttons); } /** - * Ajax submit handler: Add slot item. + * Ajax submit handler: Add source. */ - public static function addSlotItemSubmitAjax(array $form, FormStateInterface $form_state) { + public static function addSource(array $form, FormStateInterface $form_state) { $trigger_element = $form_state->getTriggeringElement(); $slot_id = $trigger_element['#slot_id']; $source_id = $trigger_element['#source_id']; - $component_form_parents = self::getComponentFormStateParents($trigger_element['#parents']); + $component_form_parents = static::getComponentFormStateParents($trigger_element['#parents']); $configuration = $form_state->getValue($component_form_parents); - $configuration['component_form']['slots'][$slot_id]['sources'][]['source_id'] = $source_id; - $form_state->setValue($component_form_parents, $configuration); + $configuration['slots'][$slot_id]['sources'][] = [ + 'source_id' => $source_id, + 'source' => [], + ]; + $component_form_parents[] = "slots"; + $form_state->setValue($component_form_parents, $configuration["slots"]); $form_state->setRebuild(); } /** - * Ajax handler: Add slot item. + * Ajax handler: Refresh sources form. */ - public static function addSlotItemAjax(array $form, FormStateInterface $form_state) { + public static function refreshAfterSourceAddition(array $form, FormStateInterface $form_state) { $parents = $form_state->getTriggeringElement()['#array_parents']; - $form = NestedArray::getValue($form, array_slice($parents, 0, -2)); + $form_layers = count([ + "(source)", + "add_more_button", + ]); + $form = NestedArray::getValue($form, array_slice($parents, 0, -$form_layers)); + if (!array_key_exists("sources", $form) || !is_array($form["sources"])) { + return []; + } return $form['sources']; } /** - * Ajax submit handler: Remove slot item. + * Ajax submit handler: Remove source. */ - public static function removeSlotItemSubmitAjax(array $form, FormStateInterface $form_state): void { + public static function removeSource(array $form, FormStateInterface $form_state): void { $trigger_element = $form_state->getTriggeringElement(); $delta = $trigger_element['#delta']; $slot_id = $trigger_element['#slot_id']; - $component_form_parents = self::getComponentFormStateParents($trigger_element['#parents']); + $component_form_parents = static::getComponentFormStateParents($trigger_element['#parents']); $configuration = $form_state->getValue($component_form_parents); - unset($configuration['component_form']['slots'][$slot_id]['sources'][$delta]); + unset($configuration['slots'][$slot_id]['sources'][$delta]); $form_state->setValue($component_form_parents, $configuration); $form_state->setRebuild(); } /** - * Ajax handler: Remove slot item. + * Ajax handler: Refresh sources form. */ - final public static function removeSlotItemAjax(array $form, FormStateInterface $form_state) { + final public static function refreshAfterSourceRemoval(array $form, FormStateInterface $form_state) { $parents = $form_state->getTriggeringElement()['#array_parents']; - return NestedArray::getValue($form, array_slice($parents, 0, -4)); + $form_layers = count([ + "sources", + "(delta)", + "_remove", + "dropdown_actions", + ]); + return NestedArray::getValue($form, array_slice($parents, 0, -$form_layers)); } /** @@ -301,13 +319,15 @@ class ComponentSlotsForm extends ComponentFormBase { /** * Expand button base array into a paragraph widget action button. * + * @param array $element + * Element. * @param array $button_base * Button base render array. * * @return array * Button render array. */ - protected static function expandComponentButton(array $button_base): array { + protected static function expandComponentButton(array $element, array $button_base): array { // Do not expand elements that do not have submit handler. if (empty($button_base['#submit'])) { return $button_base; @@ -321,7 +341,7 @@ class ComponentSlotsForm extends ComponentFormBase { // Html::getId will give us '-' char in name but we want '_' for now so // we use strtr to search&replace '-' to '_'. $button['#name'] = strtr(Html::getId($button_base['#name']), '-', '_'); - $button['#id'] = Html::getUniqueId($button['#name']); + $button['#id'] = static::getElementId($element, $button['#name']); if (isset($button['#ajax'])) { $button['#ajax'] += [ @@ -333,7 +353,7 @@ class ComponentSlotsForm extends ComponentFormBase { ]; } - return self::expandAjax($button); + return static::expandAjax($button); } }