Skip to content
Snippets Groups Projects
Commit e56f768f authored by Mikael Meulle's avatar Mikael Meulle Committed by Pierre Dureau
Browse files

Issue #3427538 by just_like_good_vibes: Form builder: Simpler & nestable sources selector (2/3)

parent 7e343fc3
Branches
Tags
1 merge request!104Issue #3427538 allow to nest components :)
......@@ -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;
}
......
......@@ -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;
}
/**
......
......@@ -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;
}
}
......@@ -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);
}
}
......@@ -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);
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment