diff --git a/modules/ui_patterns_field_formatters/src/Plugin/Field/FieldFormatter/ComponentAllFormatter.php b/modules/ui_patterns_field_formatters/src/Plugin/Field/FieldFormatter/ComponentAllFormatter.php index af8edc06f8b6f4eaaeefdaec486240db13a66032..fefcc6540dccc8986107c7eb22e918e1ace7dc76 100644 --- a/modules/ui_patterns_field_formatters/src/Plugin/Field/FieldFormatter/ComponentAllFormatter.php +++ b/modules/ui_patterns_field_formatters/src/Plugin/Field/FieldFormatter/ComponentAllFormatter.php @@ -38,7 +38,14 @@ class ComponentAllFormatter extends FormatterBase { * {@inheritdoc} */ public static function defaultSettings() { - return parent::defaultSettings() + self::getComponentSettingsFormDefault(); + return parent::defaultSettings() + self::getComponentFormDefault(); + } + + /** + * {@inheritdoc} + */ + public function getComponentSettings(): array { + return $this->getSettings(); } /** @@ -52,8 +59,7 @@ class ComponentAllFormatter extends FormatterBase { * {@inheritdoc} */ public function viewElements(FieldItemListInterface $items, $langcode) { - $build = $this->buildComponentSettingsData(); - return $build; + // @todo Implement viewElements() method. } } diff --git a/modules/ui_patterns_field_formatters/ui_patterns_field_formatters.module b/modules/ui_patterns_field_formatters/ui_patterns_field_formatters.module index 80c4271af5e6d0c72a0ee44d049f228c2c7534ea..617563a751a26ca7d021556ef4b60363463e3a0e 100644 --- a/modules/ui_patterns_field_formatters/ui_patterns_field_formatters.module +++ b/modules/ui_patterns_field_formatters/ui_patterns_field_formatters.module @@ -1,5 +1,10 @@ <?php +/** + * @file + * Adds UI Patterns component field formatter to existing fields. + */ + declare(strict_types = 1); /** diff --git a/modules/ui_patterns_layouts/ui_patterns_layouts.module b/modules/ui_patterns_layouts/ui_patterns_layouts.module index 959102ac15855e275d29e3a63527c57e310cd25a..0095c3e7559d7de2ecfeeaf2db5389b4da04a65b 100644 --- a/modules/ui_patterns_layouts/ui_patterns_layouts.module +++ b/modules/ui_patterns_layouts/ui_patterns_layouts.module @@ -1,5 +1,9 @@ <?php +/** + * @file + */ + declare(strict_types = 1); /** @@ -8,7 +12,6 @@ declare(strict_types = 1); */ use Drupal\Core\Layout\LayoutDefinition; -use Drupal\ui_patterns_layouts\Plugin\Layout\ComponentLayout; /** * Implements hook_layout_alter(). diff --git a/modules/ui_patterns_library/ui_patterns_library.module b/modules/ui_patterns_library/ui_patterns_library.module index d36533a0c3a5f8f3340d03dd8fa1732df6443f61..b7222e15de9f8358ee7b52bb7b11fca6bd13a270 100644 --- a/modules/ui_patterns_library/ui_patterns_library.module +++ b/modules/ui_patterns_library/ui_patterns_library.module @@ -1,5 +1,9 @@ <?php +/** + * @file + */ + declare(strict_types = 1); /** diff --git a/modules/ui_patterns_views/src/Plugin/views/row/ComponentRow.php b/modules/ui_patterns_views/src/Plugin/views/row/ComponentRow.php index 22657b468ecf570b24e72484de7d1b5a52040477..0f91299a7724e694b3d5a0934069d83df604e853 100644 --- a/modules/ui_patterns_views/src/Plugin/views/row/ComponentRow.php +++ b/modules/ui_patterns_views/src/Plugin/views/row/ComponentRow.php @@ -4,7 +4,7 @@ declare(strict_types = 1); namespace Drupal\ui_patterns_views\Plugin\views\row; -use Drupal\ui_patterns\Form\PatternDisplayFormTrait; +use Drupal\ui_patterns\Form\ComponentSettingsFormBuilderTrait; use Drupal\views\Plugin\views\row\Fields; /** @@ -22,6 +22,13 @@ use Drupal\views\Plugin\views\row\Fields; */ class ComponentRow extends Fields { - use PatternDisplayFormTrait; + use ComponentSettingsFormBuilderTrait; + + /** + * {@inheritdoc} + */ + public function getComponentSettings(): array { + return $this->options; + } } diff --git a/modules/ui_patterns_views/src/Plugin/views/style/ComponentStyle.php b/modules/ui_patterns_views/src/Plugin/views/style/ComponentStyle.php index ce4eacebd8acf6baf4e2f19a15d0842afd75151a..9cca5a0c121e4ff9c53c25a79559e4d2da666465 100755 --- a/modules/ui_patterns_views/src/Plugin/views/style/ComponentStyle.php +++ b/modules/ui_patterns_views/src/Plugin/views/style/ComponentStyle.php @@ -5,7 +5,8 @@ declare(strict_types = 1); namespace Drupal\ui_patterns_views\Plugin\views\style; use Drupal\Core\Form\FormStateInterface; -use Drupal\ui_patterns\Form\ComponentFormBuilderTrait; +use Drupal\Core\Url; +use Drupal\ui_patterns\Form\ComponentSettingsFormBuilderTrait; use Drupal\views\Plugin\views\style\StylePluginBase; /** @@ -23,40 +24,53 @@ use Drupal\views\Plugin\views\style\StylePluginBase; */ class ComponentStyle extends StylePluginBase { - use ComponentFormBuilderTrait; + use ComponentSettingsFormBuilderTrait; /** * {@inheritdoc} */ protected $usesRowPlugin = TRUE; + /** + * {@inheritdoc} + */ + protected $usesOptions = TRUE; + /** * {@inheritdoc} */ protected function defineOptions(): array { - return parent::defineOptions() + self::getComponentFormDefault(); + return parent::defineOptions() + ['ui_patterns' => ['default' => self::getComponentFormDefault()['ui_patterns']]]; } /** * {@inheritdoc} */ public function buildOptionsForm(&$form, FormStateInterface $form_state) { + $ui_patterns_form = $this->componentSettingsForm($form, $form_state); + $form['ui_patterns'] = $ui_patterns_form['ui_patterns']; parent::buildOptionsForm($form, $form_state); - $configuration = $this->options ?: $this->defineOptions(); - return $this->buildComponentsForm($form_state, $configuration, NULL, TRUE, TRUE); } /** * {@inheritdoc} */ - public function render(): array { - $build = parent::render(); - $ui_patterns = $this->options['ui_patterns']; + public function getComponentSettings(): array { + return $this->options; + } - // We are using groups ($usesRowPlugin) - foreach ($build as $delta => $group) { - } - return $build; + /** + * {@inheritdoc} + */ + protected function getAjaxUrl(FormStateInterface $form_state): ?Url { + return views_ui_build_form_url($form_state); + } + + /** + * {@inheritdoc} + */ + public function render(): array { + return []; } } diff --git a/modules/ui_patterns_views/ui_patterns_views.module b/modules/ui_patterns_views/ui_patterns_views.module new file mode 100644 index 0000000000000000000000000000000000000000..89692c1d21443e8b6b3abe1589afe30ef869e116 --- /dev/null +++ b/modules/ui_patterns_views/ui_patterns_views.module @@ -0,0 +1,25 @@ +<?php + +/** + * @file + * Adds views configuration changes after AJAX interactions. + */ + +use Drupal\Core\Form\FormStateInterface; + +/** + * Implements ui_patterns_form_configuration_changed(). + */ +function ui_patterns_views_ui_patterns_form_configuration_changed(FormStateInterface $form_state, array $configuration) { + /** @var \Drupal\views_ui\ViewUI $view */ + $view = $form_state->get('view'); + if ($view !== NULL) { + /** @var \Drupal\views\ViewExecutable $executable */ + $executable = $form_state->get('view')->getExecutable(); + $display_id = $form_state->get('display_id'); + $type = str_replace('_options', '', $form_state->get('type')); + $executable->displayHandlers->get($display_id)->setOption($type, ['type' => 'ui_patterns', 'options' => ['ui_patterns' => $configuration], 'grouping' => []]); + $view->cacheSet(); + $form_state->set('rerender', TRUE); + } +} diff --git a/src/Form/ComponentFormBuilderState.php b/src/Form/ComponentFormBuilderState.php new file mode 100644 index 0000000000000000000000000000000000000000..15ab12bec3a6019f55058b7c1b21567303d909c4 --- /dev/null +++ b/src/Form/ComponentFormBuilderState.php @@ -0,0 +1,190 @@ +<?php + +declare(strict_types = 1); + +namespace Drupal\ui_patterns\Form; + +use Drupal\Component\Utility\NestedArray; +use Drupal\Core\Form\FormStateInterface; + +/** + * Component Form Builder State + * + * Stores the component form builder states and AJAX handlers. + */ +final class ComponentFormBuilderState { + + /** + * Return the form api storage subkey. + */ + private static function getComponentStorageSubKey(FormStateInterface $form_state, $storage_sub_key = 'default'): string { + $trigger_element = $form_state->getTriggeringElement(); + return $trigger_element['#ui_patterns']['storage_sub_key'] ?? $storage_sub_key; + } + + /** + * Returns component storage. + */ + public static function getComponentStorage(FormStateInterface $form_state, $storage_sub_key = 'default'): array { + $storage = $form_state->getStorage(); + return $storage['ui_patterns'][self::getComponentStorageSubKey($form_state, $storage_sub_key)] ?? []; + } + + /** + * Sets the component storage to form_state storage. + */ + public static function setComponentStorage( + $storage, + FormStateInterface $form_state, + $storage_sub_key = 'default' + ): void { + $origin_storage = $form_state->getStorage(); + + $origin_storage['ui_patterns'][self::getComponentStorageSubKey($form_state, $storage_sub_key)] = $storage; + $form_state->setStorage($origin_storage); + } + + /** + * Sets the component state. + */ + public static function setComponentFormState( + $key, + $value, + FormStateInterface $form_state, + $storage_sub_key = 'default', + $invoke_hook = TRUE + ): void { + if ($key === 'configuration' && $invoke_hook) { + \Drupal::moduleHandler()->invokeAll('ui_patterns_form_configuration_changed', [$form_state, $value]); + } + $storage = self::getComponentStorage($form_state, $storage_sub_key); + $storage[$key] = $value; + self::setComponentStorage($storage, $form_state, $storage_sub_key); + } + + /** + * Get the current form state. + */ + public static function getComponentFormState( + $key, + FormStateInterface $form_state, + $storage_sub_key = 'default' + ): mixed { + $storage = self::getComponentStorage($form_state, $storage_sub_key); + return $storage[$key] ?? NULL; + } + + /** + * Ajax submit handler: Add slot item. + */ + public static function addSlotItemSubmitAjax( + array $form, + FormStateInterface $form_state + ) { + $button = $form_state->getTriggeringElement(); + $slot_id = $button['#slot_id']; + $source_id = $button['#source_id']; + $configuration = self::getComponentFormState( + 'configuration', + $form_state + ); + $configuration['component']['slots'][$slot_id]['sources'][] = ['id' => $source_id]; + self::setComponentFormState('configuration', $configuration, $form_state); + $form_state->setRebuild(); + } + + /** + * Ajax handler: Add slot item. + */ + public static function addSlotItemAjax(array $form, FormStateInterface $form_state) { + $parents = $form_state->getTriggeringElement()['#array_parents']; + $form = NestedArray::getValue($form, array_slice($parents, 0, -2)); + return $form['sources']; + } + + /** + * Ajax submit handler: Remove slot item. + */ + public static function removeSlotItemSubmitAjax( + array $form, + FormStateInterface $form_state + ) { + $button = $form_state->getTriggeringElement(); + $delta = $button['#delta']; + $slot_id = $button['#slot_id']; + $configuration = self::getComponentFormState( + 'configuration', + $form_state + ); + unset($configuration['component']['slots'][$slot_id]['sources'][$delta]); + self::setComponentFormState('configuration', $configuration, $form_state); + $form_state->setRebuild(); + } + + /** + * Ajax handler: Remove slot item. + */ + final public static function removeSlotItemAjax( + array $form, + FormStateInterface $form_state + ) { + $parents = $form_state->getTriggeringElement()['#array_parents']; + return NestedArray::getValue($form, array_slice($parents, 0, -4)); + } + + /** + * Ajax submit handler: Change props type submit handler. + */ + public static function changePropSubmitAjax( + array $form, + FormStateInterface $form_state + ) { + $configuration = ComponentFormBuilderState::getComponentFormState('configuration', $form_state); + $button = $form_state->getTriggeringElement(); + $prop_id = $button['#prop_id']; + $source_id = $button['#source_id']; + $configuration['component']['props'][$prop_id]['source_id'] = $source_id; + self::setComponentFormState('configuration', $configuration, $form_state); + $form_state->setRebuild(); + } + + /** + * Ajax handler: Change prop type. + */ + public static function changePropItemAjax( + array $form, + FormStateInterface $form_state + ) { + $parents = $form_state->getTriggeringElement()['#array_parents']; + return NestedArray::getValue($form, array_slice($parents, 0, -3)); + } + + /** + * Ajax callback for component selector change. + */ + public static function changeSelectorFormChangeAjax( + array $form, + FormStateInterface $form_state + ) { + + $parents = $form_state->getTriggeringElement()['#array_parents']; + $component_id = $form_state->getValue($form_state->getTriggeringElement()['#parents']); + $configuration = ComponentFormBuilderState::getComponentFormState('configuration', $form_state); + $configuration['component_id'] = $component_id; + ComponentFormBuilderState::setComponentFormState('configuration', $configuration, $form_state); + $sub_form = NestedArray::getValue($form, array_slice($parents, 0, -1)); + return $sub_form['component']; + } + + /** + * Ajax callback for variant selector change. + */ + public static function buildComponentVariantSelectorFormChangeAjax( + array $form, + FormStateInterface $form_state + ) { + $parents = $form_state->getTriggeringElement()['#array_parents']; + return NestedArray::getValue($form, array_slice($parents, 0, -1)); + } + +} diff --git a/src/Form/ComponentFormBuilderTrait.php b/src/Form/ComponentFormBuilderTrait.php index dc9c29e807317e81b720277fa6650420254c27d7..d2666efa195af43e965ff92a6fa3fecf095a9a3c 100644 --- a/src/Form/ComponentFormBuilderTrait.php +++ b/src/Form/ComponentFormBuilderTrait.php @@ -5,10 +5,10 @@ declare(strict_types = 1); namespace Drupal\ui_patterns\Form; use Drupal\Component\Utility\Html; -use Drupal\Component\Utility\NestedArray; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Form\SubformState; use Drupal\Core\Render\Element; +use Drupal\Core\Url; use Drupal\sdc\Plugin\Component; use Drupal\ui_patterns\SourcePluginManager; @@ -30,9 +30,12 @@ trait ComponentFormBuilderTrait { return [ "ui_patterns" => [ "component_id" => NULL, - "variant_id" => NULL, - "slots" => [], - "props" => [], + + 'component' => [ + "variant_id" => NULL, + "slots" => [], + "props" => [], + ], ], ]; } @@ -50,6 +53,23 @@ trait ComponentFormBuilderTrait { return []; } + /** + * Returns the ajax url. + * + * Some integrations plugins like the views plugin provide there + * ajax urls for ajax form interaction. To support + * those plugins overwrite this function. + * + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The form state. + * + * @return \Drupal\Core\Url|null + * The ajax url. + */ + protected function getAjaxUrl(FormStateInterface $form_state): ?Url { + return NULL; + } + /** * Helper function to return the component. */ @@ -60,58 +80,23 @@ trait ComponentFormBuilderTrait { } /** - * Return the form api storage subkey. + * Expand each ajax element with ajax urls. * - * Overwrite this method and provide a specifc subkey - * if your form handles multiple component form builders. - */ - protected function getComponentStorageSubKey(): string { - return 'default'; - } - - /** - * Returns component storage. - */ - private function getComponentStorage(FormStateInterface $form_state): array { - $storage = $form_state->getStorage(); - return $storage['ui_patterns'][$this->getComponentStorageSubKey()] ?? []; - } - - /** - * Sets the component storage to form_state storage. - */ - private function seComponentStorage( - $storage, - FormStateInterface $form_state - ): void { - $origin_storage = $form_state->getStorage(); - $origin_storage['ui_patterns'][$this->getComponentStorageSubKey( - )] = $storage; - $form_state->setStorage($origin_storage); - } - - /** - * Sets the component state. - */ - private function setComponentFormState( - $key, - $value, - FormStateInterface $form_state - ): void { - $storage = $this->getComponentStorage($form_state); - $storage[$key] = $value; - $this->seComponentStorage($storage, $form_state); - } - - /** - * Get the current form state. + * @param array $element + * The ajax element. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The form state. + * + * @return array + * The extended ajax form. */ - private function getComponentFormState( - $key, - FormStateInterface $form_state - ): mixed { - $storage = $this->getComponentStorage($form_state); - return $storage[$key] ?? NULL; + private function expandAjax(array $element, FormStateInterface $form_state): array { + $url = $this->getAjaxUrl($form_state); + if (isset($element['#ajax']) && $url) { + $element['#ajax']['url'] = $url; + } + $element['#ui_patterns']['storage_sub_key'] = $this->getComponentStorageSubKey(); + return $element; } /** @@ -123,7 +108,7 @@ trait ComponentFormBuilderTrait { * @return array * Button render array. */ - private function expandComponentButton(array $button_base) { + private function expandComponentButton(array $button_base, FormStateInterface $form_state) { // Do not expand elements that do not have submit handler. if (empty($button_base['#submit'])) { return $button_base; @@ -149,7 +134,7 @@ trait ComponentFormBuilderTrait { ]; } - return $button; + return $this->expandAjax($button, $form_state); } /** @@ -194,44 +179,51 @@ trait ComponentFormBuilderTrait { * * The form contains. * - * @param Drupal\sdc\Plugin\Component $component - * The component plugin. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The form state. + * @param array $configuration + * The stored configuration. + * @param string|null $initial_component_id + * The initial_component_id. If provided the component is changeable. + * @param boolean $render_slots + * TRUE if slots are editable. + * @param boolean $render_props + * TRUE if props are editable. */ protected function buildComponentsForm( FormStateInterface $form_state, - $configuration, - $initial_component_id = NULL, - $render_slots = TRUE, - $render_props = TRUE + array $configuration, + string $initial_component_id = NULL, + bool $render_slots = TRUE, + bool $render_props = TRUE ): array { $form_state = $form_state instanceof SubformState ? $form_state->getCompleteFormState() : $form_state; + $configuration = $configuration['ui_patterns'] ?? []; // TBD Make this id more flexible. $wrapper_id = 'ui-patterns-component'; - $storage = $this->getComponentStorage($form_state); + $storage = ComponentFormBuilderState::getComponentStorage($form_state, $this->getComponentStorageSubKey()); $storage['render_slots'] = $render_slots; $storage['render_props'] = $render_props; if (!isset($storage['configuration'])) { $storage['configuration'] = $configuration; } - $component_id = $configuration['component_id'] ?? $initial_component_id; + $component_id = $storage['configuration']['component_id'] ?? $configuration['component_id'] ?? $initial_component_id; $trigger_element = $form_state->getTriggeringElement(); + // Handling select change. if ($form_state->isRebuilding()) { - if (isset($trigger_element['#ui_patterns']['id']) && $trigger_element['#ui_patterns']['id'] === 'component_selector') { + if (isset($trigger_element['#ui_patterns']['id']) && $trigger_element['#ui_patterns']['id'] === 'component_id') { $component_id = $form_state->getValue($trigger_element['#parents']); $storage['configuration']['component_id'] = $component_id; } - else { - $component_id = $storage['configuration']['component_id'] ?? $initial_component_id; - } } - $this->seComponentStorage($storage, $form_state); + ComponentFormBuilderState::setComponentStorage($storage, $form_state, $this->getComponentStorageSubKey()); $form = []; if ($initial_component_id === NULL) { - $form["component_selector"] = $this->buildComponentSelectorForm( + $form["component_id"] = $this->expandAjax($this->buildComponentSelectorForm( $wrapper_id, $component_id - ); + ), $form_state); } $form["component"] = $this->buildComponentForm( $wrapper_id, @@ -252,17 +244,18 @@ trait ComponentFormBuilderTrait { $component_id, FormStateInterface $form_state ): array { - $configuration = $this->getComponentFormState('configuration', $form_state); + $configuration = ComponentFormBuilderState::getComponentFormState('configuration', $form_state, $this->getComponentStorageSubKey()); $variant_id = $configuration['variant_id'] ?? NULL; $trigger_element = $form_state->getTriggeringElement(); if ($form_state->isRebuilding( - ) && isset($trigger_element['#ui_patterns']['id']) && $trigger_element['#ui_patterns']['id'] === 'variant_selector') { + ) && isset($trigger_element['#ui_patterns']['id']) && $trigger_element['#ui_patterns']['id'] === 'variant_id') { $variant_id = $form_state->getValue($trigger_element['#parents']); $configuration['variant_id'] = $variant_id; - $this->setComponentFormState( + ComponentFormBuilderState::setComponentFormState( 'configuration', $configuration, - $form_state + $form_state, + $this->getComponentStorageSubKey() ); } $form = [ @@ -272,16 +265,15 @@ trait ComponentFormBuilderTrait { ]; $variant_wrapper_id = 'ui-patterns-component-variant'; if ($component_id !== NULL) { - $form['variant_selector'] = $this->buildComponentVariantSelectorForm( + $form['variant_id'] = $this->buildComponentVariantSelectorForm( $variant_wrapper_id, $form_state, $component_id, $variant_id ); - $form['variant'] = $this->buildComponentVariantForm( + $form += $this->buildComponentVariantForm( $variant_wrapper_id, $component_id, - $variant_id, $form_state ); } @@ -298,7 +290,6 @@ trait ComponentFormBuilderTrait { private function buildComponentVariantForm( $wrapper_id, string $component_id, - $variant_id, FormStateInterface $form_state ): array { $component = $this->getComponent($component_id); @@ -307,29 +298,29 @@ trait ComponentFormBuilderTrait { '#prefix' => '<div id="' . $wrapper_id . '">', '#suffix' => '</div>', ]; - $render_slots = $this->getComponentFormState('render_slots', $form_state); - $render_props = $this->getComponentFormState('render_props', $form_state); - $configuration = $this->getComponentFormState('configuration', $form_state); + $render_slots = ComponentFormBuilderState::getComponentFormState('render_slots', $form_state, $this->getComponentStorageSubKey()); + $render_props = ComponentFormBuilderState::getComponentFormState('render_props', $form_state, $this->getComponentStorageSubKey()); + $configuration = ComponentFormBuilderState::getComponentFormState('configuration', $form_state, $this->getComponentStorageSubKey()); if (!isset($configuration['props'])) { $configuration['props'] = []; } if (!isset($configuration['slots'])) { - $configuration['props'] = []; + $configuration['slots'] = []; } if ($render_slots) { $form['slots'] = $this->buildSlotsForm( $form_state, $component, - $configuration['slots'] + $configuration['component']['slots'] ); } if ($render_props) { $form['props'] = $this->buildPropsForm( $form_state, $component, - $configuration['props'] + $configuration['component']['props'] ); } return $form; @@ -355,16 +346,26 @@ trait ComponentFormBuilderTrait { "#type" => "select", "#title" => t("Component"), "#options" => $options, - '#ui_patterns' => ['id' => 'component_selector'], + '#ui_patterns' => ['id' => 'component_id'], '#default_value' => $selected_component_id, '#ajax' => [ - 'callback' => [$this, 'buildComponentSelectorFormChangeAjax'], + 'callback' => [ComponentFormBuilderState::class, 'changeSelectorFormChangeAjax'], 'wrapper' => $wrapper_id, 'effect' => 'fade', ], ]; } + /** + * Returns the component form storage subkey. + * + * Plugins should overwrite this method + * if the same form builder form is part of the + */ + protected function getComponentStorageSubKey(): string { + return 'default'; + } + /** * Build the variant select widget. * @@ -372,10 +373,10 @@ trait ComponentFormBuilderTrait { * The variant select. */ private function buildComponentVariantSelectorForm( - $wrapper_id, + string $wrapper_id, FormStateInterface $form_state, - $component_id, - $default_variant_id + string $component_id, + string $default_variant_id ): array { $component = $this->getComponent($component_id); $definition = $component->getPluginDefinition(); @@ -386,18 +387,22 @@ trait ComponentFormBuilderTrait { foreach ($definition["variants"] as $variant_id => $variant) { $options[$variant_id] = $variant["title"]; } - return [ + $id = strtr(Html::getId('variant_id'), '-', '_'); + + return $this->expandAjax([ "#type" => "select", + '#name' => $id, + '#id' => $id, "#title" => t("Variant"), "#options" => $options, - '#ui_patterns' => ['id' => 'variant_selector'], + '#ui_patterns' => ['id' => 'variant_id', 'storage_sub_key' => $this->getComponentStorageSubKey()], '#default_value' => $default_variant_id, '#ajax' => [ - 'callback' => [$this, 'buildComponentVariantSelectorFormChangeAjax'], + 'callback' => [ComponentFormBuilderState::class, 'buildComponentVariantSelectorFormChangeAjax'], 'wrapper' => $wrapper_id, 'effect' => 'fade', ], - ]; + ], $form_state); } /** @@ -410,39 +415,21 @@ trait ComponentFormBuilderTrait { FormStateInterface $form_state ) { $form = $form['ui_patterns']; - $configuration = $this->getComponentFormState('configuration', $form_state); + $configuration = ComponentFormBuilderState::getComponentFormState('configuration', $form_state, $this->getComponentStorageSubKey()); $configuration['props'] = []; - $props_form = $form['component']['variant']['props'] ?? []; + $props_form = $form['component']['props'] ?? []; $this->submitPropsForm($props_form, $form_state, $configuration['props']); $configuration['slots'] = []; - $slots_form = $form['component']['variant']['slots'] ?? []; - $this->submitSlotsForm($slots_form, $form_state, $configuration['slots']); - $this->setComponentFormState('configuration', $configuration, $form_state); - $this->configuration['ui_patterns'] = $configuration; - } - /** - * Ajax callback for component selector change. - */ - final public function buildComponentSelectorFormChangeAjax( - array $form, - FormStateInterface $form_state - ) { - $parents = $form_state->getTriggeringElement()['#array_parents']; - $sub_form = NestedArray::getValue($form, array_slice($parents, 0, -1)); - return $sub_form['component']; - } - - /** - * Ajax callback for variant selector change. - */ - final public function buildComponentVariantSelectorFormChangeAjax( - array $form, - FormStateInterface $form_state - ) { - $parents = $form_state->getTriggeringElement()['#array_parents']; - $form = NestedArray::getValue($form, array_slice($parents, 0, -1)); - return $form['variant']; + $this->submitPropsForm( + $form['component']['props'], + $form_state, + $configuration['props'] + ); + $slots = isset($form['component']['slots']) ?: []; + $this->submitSlotsForm($slots, $form_state, $configuration['slots']); + ComponentFormBuilderState::setComponentFormState('configuration', $configuration, $form_state, $this->getComponentStorageSubKey()); + $this->configuration['ui_patterns'] = $configuration; } /** @@ -517,12 +504,15 @@ trait ComponentFormBuilderTrait { '#type' => 'submit', '#name' => strtr($slot_id, '-', '_') . $delta . '_remove', '#value' => $this->t('Delete'), - '#submit' => [[$this, 'removeSlotItemSubmitAjax']], + '#submit' => [ComponentFormBuilderState::class . '::removeSlotItemSubmitAjax'], '#access' => TRUE, '#delta' => $delta, + '#ui_patterns' => [ + 'storage_sub_key' => $this->getComponentStorageSubKey(), + ], '#slot_id' => $slot_id, '#ajax' => [ - 'callback' => [$this, 'removeSlotItemAjax'], + 'callback' => [ComponentFormBuilderState::class, 'removeSlotItemAjax'], 'wrapper' => $wrapper_id, 'effect' => 'fade', ], @@ -532,7 +522,8 @@ trait ComponentFormBuilderTrait { '#ui_patterns_header' => TRUE, 'dropdown_actions' => [ $this->expandComponentButton( - $delete_action + $delete_action, + $form_state ), ], ]; @@ -543,33 +534,23 @@ trait ComponentFormBuilderTrait { foreach ($options as $source_id => $source_label) { $action_buttons[$source_id] = $this->expandComponentButton([ '#type' => 'submit', - '#name' => strtr( - $slot_id, - '-', - '_' - ) . $source_id . '_add_more', - '#value' => $this->t( - 'Add %source', - ['%source' => $source_label] - ), + '#name' => strtr($slot_id, '-', '_') . $source_id . '_add_more', + '#value' => $this->t('Add %source', ['%source' => $source_label]), '#submit' => [ - [ - $this, - 'addSlotItemSubmitAjax', - ], + ComponentFormBuilderState::class . '::addSlotItemSubmitAjax', ], '#access' => TRUE, '#slot_id' => $slot_id, '#source_id' => $source_id, '#ajax' => [ 'callback' => [ - $this, + ComponentFormBuilderState::class, 'addSlotItemAjax', ], 'wrapper' => $wrapper_id, 'effect' => 'fade', ], - ]); + ], $form_state); } $form[$slot_id]['add_more_button'] = $this->buildComponentDropbutton( $action_buttons @@ -581,93 +562,6 @@ trait ComponentFormBuilderTrait { return $form; } - /** - * Ajax submit handler: Add slot item. - */ - final public function addSlotItemSubmitAjax( - array $form, - FormStateInterface $form_state - ) { - $button = $form_state->getTriggeringElement(); - $slot_id = $button['#slot_id']; - $source_id = $button['#source_id']; - $configuration = $this->getComponentFormState( - 'configuration', - $form_state - ); - $configuration['slots'][$slot_id]['sources'][] = ['id' => $source_id]; - $this->setComponentFormState('configuration', $configuration, $form_state); - $form_state->setRebuild(); - } - - /** - * Ajax handler: Add slot item. - */ - public function addSlotItemAjax( - array $form, - FormStateInterface $form_state - ) { - $parents = $form_state->getTriggeringElement()['#array_parents']; - $form = NestedArray::getValue($form, array_slice($parents, 0, -2)); - return $form['sources']; - } - - /** - * Ajax submit handler: Remove slot item. - */ - final public function removeSlotItemSubmitAjax( - array $form, - FormStateInterface $form_state - ) { - $button = $form_state->getTriggeringElement(); - $delta = $button['#delta']; - $slot_id = $button['#slot_id']; - $configuration = $this->getComponentFormState( - 'configuration', - $form_state - ); - unset($configuration['slots'][$slot_id]['sources'][$delta]); - $this->setComponentFormState('configuration', $configuration, $form_state); - $form_state->setRebuild(); - } - - /** - * Ajax handler: Remove slot item. - */ - final public function removeSlotItemAjax( - array $form, - FormStateInterface $form_state - ) { - $parents = $form_state->getTriggeringElement()['#array_parents']; - return NestedArray::getValue($form, array_slice($parents, 0, -4)); - } - - /** - * Submit slots form. - */ - private function submitSlotsForm( - array $form, - FormStateInterface $form_state, - &$configuration - ) { - $source_slots = $form_state->get('ui_patterns_slot_sources'); - if (!isset($source_slots)) { - return; - } - foreach ($source_slots as $slot_id => $source_ids) { - foreach ($source_ids as $source_index => $source) { - $source->submitConfigurationForm( - $form[$slot_id]['sources'][$source_index], - $form_state - ); - $configuration[$slot_id]['sources'][$source_index] = [ - 'value' => $source->getConfiguration()['form_value'], - 'id' => $source->getPluginId(), - ]; - } - } - } - /** * Returns the source plugin manager. */ @@ -687,6 +581,7 @@ trait ComponentFormBuilderTrait { &$configuration ): array { $contexts = $this->getComponentSourceContexts(); + $form = []; $build_sources = []; $source_plugin_manager = $this->sourcePluginManager(); @@ -731,26 +626,20 @@ trait ComponentFormBuilderTrait { $button = [ '#type' => 'submit', '#name' => $prop_id . '_' . strtr($source_id, '-', '_') . '_change', - '#value' => $source->label(), + '#value' => $source_id, + '#submit' => [ComponentFormBuilderState::class . '::changePropSubmitAjax'], '#source_id' => $source_id, '#prop_id' => $prop_id, - '#submit' => [ - [ - $this, - 'changePropSubmitAjax', - ], - ], - '#access' => TRUE, '#ajax' => [ 'callback' => [ - $this, + ComponentFormBuilderState::class, 'changePropItemAjax', ], 'wrapper' => $wrapper_id, 'effect' => 'fade', ], ]; - $dropdown_actions[$source_id] = $this->expandComponentButton($button); + $dropdown_actions[$source_id] = $this->expandComponentButton($button, $form_state); } } $form[$prop_id]['source_id'] = [ @@ -772,36 +661,6 @@ trait ComponentFormBuilderTrait { return $form; } - /** - * Ajax submit handler: Change props type submit handler. - */ - final public function changePropSubmitAjax( - array $form, - FormStateInterface $form_state - ) { - $button = $form_state->getTriggeringElement(); - $prop_id = $button['#prop_id']; - $source_id = $button['#source_id']; - $configuration = $this->getComponentFormState( - 'configuration', - $form_state - ); - $configuration['props'][$prop_id]['source_id'] = $source_id; - $this->setComponentFormState('configuration', $configuration, $form_state); - $form_state->setRebuild(); - } - - /** - * Ajax handler: Change prop type. - */ - final public function changePropItemAjax( - array $form, - FormStateInterface $form_state - ) { - $parents = $form_state->getTriggeringElement()['#array_parents']; - return NestedArray::getValue($form, array_slice($parents, 0, -3)); - } - /** * Submit props forms. */ @@ -811,7 +670,7 @@ trait ComponentFormBuilderTrait { &$configuration ) { $sources = $form_state->get('ui_patterns_prop_sources'); - $state_configuration = $this->getComponentFormState('configuration', $form_state); + $state_configuration = ComponentFormBuilderState::getComponentFormState('configuration', $form_state, $this->getComponentStorageSubKey()); foreach ($sources as $prop_id => $source) { $source->submitConfigurationForm( $form[$prop_id], @@ -825,6 +684,32 @@ trait ComponentFormBuilderTrait { } } + /** + * Submit slots form. + */ + private function submitSlotsForm( + $form, + FormStateInterface $form_state, + &$configuration + ):void { + $source_slots = $form_state->get('ui_patterns_slot_sources'); + if (!isset($source_slots)) { + return; + } + foreach ($source_slots as $slot_id => $source_ids) { + foreach ($source_ids as $source_index => $source) { + $source->submitConfigurationForm( + $form[$slot_id]['sources'][$source_index], + $form_state + ); + $configuration[$slot_id]['sources'][$source_index] = [ + 'value' => $source->getConfiguration()['form_value'], + 'id' => $source->getPluginId(), + ]; + } + } + } + /** * Build component data provided to the SDC element. */ @@ -841,8 +726,7 @@ trait ComponentFormBuilderTrait { // @todo injection $builder = \Drupal::service('ui_patterns.component_element_builder'); $contexts = $this->getComponentSourceContexts(); - $build = $builder->build($component, $configuration, $contexts); - return $build; + return $builder->build($component, $configuration, $contexts); } } diff --git a/src/Form/ComponentSettingsFormBuilderTrait.php b/src/Form/ComponentSettingsFormBuilderTrait.php index dc703907f308083bbf8f27eab88c6f0c99e01e8c..4e52fd9bf645bf6d6a4d0679b88bf3334654b401 100644 --- a/src/Form/ComponentSettingsFormBuilderTrait.php +++ b/src/Form/ComponentSettingsFormBuilderTrait.php @@ -1,6 +1,6 @@ <?php -declare(strict_types = 1); +declare(strict_types=1); namespace Drupal\ui_patterns\Form; @@ -14,52 +14,37 @@ trait ComponentSettingsFormBuilderTrait { use ComponentFormBuilderTrait; /** + * Adapter function for plugin settings/options. * - */ - public static function getComponentSettingsFormDefault() { - return [ - "ui_patterns" => NULL, - ]; - } - - /** + * Overwrite to return settings/options of the + * current plugin. * + * @return mixed + * The plugin settings/options. */ - private function getMappedSettingsToConfig() { - $settings = $this->getSettings()['ui_patterns'] ?? []; - // Mapping the settings array saved by settingsForm to the required configuration form. - $configuration = [ - 'ui_patterns' => [ - 'component_id' => $settings['component_selector'] ?? NULL, - 'variant_id' => $settings['component']['variant']['variant_selector'] ?? NULL, - 'props' => $settings['component']['variant']['props'] ?? [], - 'slots' => $settings['component']['variant']['slots'] ?? [], - ], - ]; - return $configuration; - } + abstract protected function getComponentSettings(): array; /** * {@inheritdoc} */ - protected function componentSettingsForm(array $form, FormStateInterface $form_state): array { - - $configuration = $this->getMappedSettingsToConfig(); + protected function componentSettingsForm( + array $form, + FormStateInterface $form_state + ): array { $trigger_element = $form_state->getTriggeringElement(); + $configuration = $this->getComponentSettings()['ui_patterns'] ?? []; if ($form_state->isRebuilding() === FALSE - || (isset($trigger_element['#op']) && $trigger_element['#op'] === 'update') + || (isset($trigger_element['#op']) && $trigger_element['#op'] === 'update') ) { - $this->setComponentFormState('configuration', $configuration['ui_patterns'], $form_state); + ComponentFormBuilderState::setComponentFormState( + 'configuration', + $configuration, + $form_state, + $this->getComponentStorageSubKey(), + FALSE + ); } return $this->buildComponentsForm($form_state, $configuration); } - /** - * - */ - final protected function buildComponentSettingsData() { - $configuration = $this->getMappedSettingsToConfig(); - return $this->buildComponentData($configuration); - } - } diff --git a/src/Plugin/UiPatterns/Source/TextfieldWidget.php b/src/Plugin/UiPatterns/Source/TextfieldWidget.php index cb88e65f4ade723248fab2467b823370f2a75b83..684a1c5ef1acfe6d04409f61af0468b900c47db7 100644 --- a/src/Plugin/UiPatterns/Source/TextfieldWidget.php +++ b/src/Plugin/UiPatterns/Source/TextfieldWidget.php @@ -15,7 +15,8 @@ use Drupal\ui_patterns\SourcePluginBase; * description = @Translation("One-line text field."), * prop_types = { * "string", - * "machine_name" + * "machine_name", + * "slot" * } * ) */ diff --git a/ui_patterns.api.php b/ui_patterns.api.php new file mode 100644 index 0000000000000000000000000000000000000000..97ac96a21d85a9353fdbdcc0b878739360754594 --- /dev/null +++ b/ui_patterns.api.php @@ -0,0 +1,25 @@ +<?php + +/** + * @file + */ + +use Drupal\Core\Form\FormStateInterface; + +/** + * .UI Patterns form configuration changed. + * + * Informs contrib module if ui patterns form has changed. + * + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The form state. + * @param array $configuration + * The form configuration. + * + * @void + * + * @ingroup ui_patterns + */ +function hook_ui_patterns_form_configuration_changed(FormStateInterface $form_state, array $configuration) { + +} diff --git a/ui_patterns.module b/ui_patterns.module index db57b19754c3f1e9d382bb08ba32a58788ebd179..ff150c1c20d2d86c150e57d6594fa5a386441e23 100644 --- a/ui_patterns.module +++ b/ui_patterns.module @@ -1,20 +1,24 @@ <?php +/** + * @file + * Adds UI Patterns themes and UI Elements. + */ + declare(strict_types = 1); /** * Implements hook_theme(). */ function ui_patterns_theme() { - return array( + return [ 'ui_patterns_actions' => [ 'render element' => 'element', 'template' => 'ui-patterns-actions', ], - ); + ]; } - /** * Prepares variables for ui_patterns_actions component. * @@ -40,24 +44,23 @@ function template_preprocess_ui_patterns_actions(&$variables) { } } - /** * Implements hook_preprocess_HOOK() for field_multiple_value_form(). */ function ui_patterns_preprocess_field_multiple_value_form(&$variables) { - // @TODO: Move header buttons to own column. + // @todo Move header buttons to own column. /* if (!empty($variables['table']['#header']) && isset($variables['table']['#rows'][0])) { - // Find paragraph_actions and move to header. - // @see template_preprocess_field_multiple_value_form() - if (is_array($variables['table']['#rows'][0]['data'][1]) && !empty($variables['table']['#rows'][0]['data'][1]['data']['_remove']['#ui_patterns_header'])) { - $variables['table']['#header'][0]['data'] = [ - 'title' => $variables['table']['#header'][0]['data'], - 'button' => $variables['table']['#rows'][0]['data'][1]['_remove']['data'], - ]; - unset($variables['table']['#rows'][0]); - } - } - */ + // Find paragraph_actions and move to header. + // @see template_preprocess_field_multiple_value_form() + if (is_array($variables['table']['#rows'][0]['data'][1]) && !empty($variables['table']['#rows'][0]['data'][1]['data']['_remove']['#ui_patterns_header'])) { + $variables['table']['#header'][0]['data'] = [ + 'title' => $variables['table']['#header'][0]['data'], + 'button' => $variables['table']['#rows'][0]['data'][1]['_remove']['data'], + ]; + unset($variables['table']['#rows'][0]); + } + } + */ }