From f988ec3934ecc99598f264c2c27ebb7455237f8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Harri=20H=C3=A4iv=C3=A4l=C3=A4?= Date: Thu, 10 Apr 2025 10:30:53 +0300 Subject: [PATCH 1/4] WIP: sorting for live component --- .../src/Form/Type/LiveCollectionType.php | 60 +++++++++++++++++++ src/LiveComponent/src/LiveCollectionTrait.php | 40 +++++++++++++ .../templates/form_theme.html.twig | 6 ++ 3 files changed, 106 insertions(+) diff --git a/src/LiveComponent/src/Form/Type/LiveCollectionType.php b/src/LiveComponent/src/Form/Type/LiveCollectionType.php index 2fc3aea95f3..c1a54742fdf 100644 --- a/src/LiveComponent/src/Form/Type/LiveCollectionType.php +++ b/src/LiveComponent/src/Form/Type/LiveCollectionType.php @@ -35,6 +35,14 @@ public function buildForm(FormBuilderInterface $builder, array $options): void $prototype = $builder->create('delete', $options['button_delete_type'], $options['button_delete_options']); $builder->setAttribute('button_delete_prototype', $prototype->getForm()); } + + if ($options['allow_sort']) { + $moveUpPrototype = $builder->create('move_up', $options['button_move_up_type'], $options['button_move_up_options']); + $builder->setAttribute('button_move_up_prototype', $moveUpPrototype->getForm()); + + $moveDownPrototype = $builder->create('move_down', $options['button_move_down_type'], $options['button_move_down_options']); + $builder->setAttribute('button_move_down_prototype', $moveDownPrototype->getForm()); + } } public function buildView(FormView $view, FormInterface $form, array $options): void @@ -92,6 +100,53 @@ public function finishView(FormView $view, FormInterface $form, array $options): array_splice($entryView->vars['button_delete']->vars['block_prefixes'], 1, 0, 'live_collection_button_delete'); } } + + // Add move up and move down buttons + if ($form->getConfig()->hasAttribute('button_move_up_prototype')) { + $prototype = $form->getConfig()->getAttribute('button_move_up_prototype'); + + $prototypes = []; + foreach ($form as $k => $entry) { + $prototypes[$k] = clone $prototype; + $prototypes[$k]->setParent($entry); + } + + foreach ($view as $k => $entryView) { + $entryView->vars['button_move_up'] = $prototypes[$k]->createView($entryView); + + $attr = $entryView->vars['button_move_up']->vars['attr']; + $attr['data-action'] ??= 'live#action'; + $attr['data-live-action-param'] ??= 'moveCollectionItemUp'; + $attr['data-live-name-param'] ??= $view->vars['full_name']; + $attr['data-live-index-param'] ??= $k; + $entryView->vars['button_move_up']->vars['attr'] = $attr; + + array_splice($entryView->vars['button_move_up']->vars['block_prefixes'], 1, 0, 'live_collection_button_move_up'); + } + } + + if ($form->getConfig()->hasAttribute('button_move_down_prototype')) { + $prototype = $form->getConfig()->getAttribute('button_move_down_prototype'); + + $prototypes = []; + foreach ($form as $k => $entry) { + $prototypes[$k] = clone $prototype; + $prototypes[$k]->setParent($entry); + } + + foreach ($view as $k => $entryView) { + $entryView->vars['button_move_down'] = $prototypes[$k]->createView($entryView); + + $attr = $entryView->vars['button_move_down']->vars['attr']; + $attr['data-action'] ??= 'live#action'; + $attr['data-live-action-param'] ??= 'moveCollectionItemDown'; + $attr['data-live-name-param'] ??= $view->vars['full_name']; + $attr['data-live-index-param'] ??= $k; + $entryView->vars['button_move_down']->vars['attr'] = $attr; + + array_splice($entryView->vars['button_move_down']->vars['block_prefixes'], 1, 0, 'live_collection_button_move_down'); + } + } } public function configureOptions(OptionsResolver $resolver): void @@ -105,8 +160,13 @@ public function configureOptions(OptionsResolver $resolver): void 'button_add_options' => [], 'button_delete_type' => ButtonType::class, 'button_delete_options' => [], + 'button_move_up_type' => ButtonType::class, + 'button_move_up_options' => [], + 'button_move_down_type' => ButtonType::class, + 'button_move_down_options' => [], 'allow_add' => true, 'allow_delete' => true, + 'allow_sort' => true, 'by_reference' => false, ]); } diff --git a/src/LiveComponent/src/LiveCollectionTrait.php b/src/LiveComponent/src/LiveCollectionTrait.php index 70f11f23b8b..6bc0e167a7f 100644 --- a/src/LiveComponent/src/LiveCollectionTrait.php +++ b/src/LiveComponent/src/LiveCollectionTrait.php @@ -46,6 +46,46 @@ public function removeCollectionItem(PropertyAccessorInterface $propertyAccessor $propertyAccessor->setValue($this->formValues, $propertyPath, $data); } + #[LiveAction] + public function moveCollectionItemUp(PropertyAccessorInterface $propertyAccessor, #[LiveArg] string $name, #[LiveArg] int $index): void + { + if ($index <= 0) { + return; + } + + $propertyPath = $this->fieldNameToPropertyPath($name, $this->formName); + $data = $propertyAccessor->getValue($this->formValues, $propertyPath); + + if (!\is_array($data) || !isset($data[$index]) || !isset($data[$index - 1])) { + return; + } + + // Swap the current item with the one above it + $temp = $data[$index - 1]; + $data[$index - 1] = $data[$index]; + $data[$index] = $temp; + + $propertyAccessor->setValue($this->formValues, $propertyPath, $data); + } + + #[LiveAction] + public function moveCollectionItemDown(PropertyAccessorInterface $propertyAccessor, #[LiveArg] string $name, #[LiveArg] int $index): void + { + $propertyPath = $this->fieldNameToPropertyPath($name, $this->formName); + $data = $propertyAccessor->getValue($this->formValues, $propertyPath); + + if (!\is_array($data) || !isset($data[$index]) || !isset($data[$index + 1])) { + return; + } + + // Swap the current item with the one below it + $temp = $data[$index + 1]; + $data[$index + 1] = $data[$index]; + $data[$index] = $temp; + + $propertyAccessor->setValue($this->formValues, $propertyPath, $data); + } + private function fieldNameToPropertyPath(string $collectionFieldName, string $rootFormName): string { $propertyPath = $collectionFieldName; diff --git a/src/LiveComponent/templates/form_theme.html.twig b/src/LiveComponent/templates/form_theme.html.twig index 1042510f2f9..4349883469a 100644 --- a/src/LiveComponent/templates/form_theme.html.twig +++ b/src/LiveComponent/templates/form_theme.html.twig @@ -10,4 +10,10 @@ {%- if button_delete is defined and not button_delete.rendered -%} {{ form_row(button_delete) }} {%- endif -%} + {%- if button_move_up is defined and not button_move_up.rendered -%} + {{ form_row(button_move_up) }} + {%- endif -%} + {%- if button_move_down is defined and not button_move_down.rendered -%} + {{ form_row(button_move_down) }} + {%- endif -%} {%- endblock live_collection_entry_row -%} From 7e5557dbcf40729c3eed365b13bd32a9862688cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Harri=20H=C3=A4iv=C3=A4l=C3=A4?= Date: Thu, 10 Apr 2025 10:39:16 +0300 Subject: [PATCH 2/4] Coding standard --- .../src/Form/Type/LiveCollectionType.php | 8 ++++---- src/LiveComponent/src/LiveCollectionTrait.php | 14 +++++++------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/LiveComponent/src/Form/Type/LiveCollectionType.php b/src/LiveComponent/src/Form/Type/LiveCollectionType.php index c1a54742fdf..7c34d7e6e38 100644 --- a/src/LiveComponent/src/Form/Type/LiveCollectionType.php +++ b/src/LiveComponent/src/Form/Type/LiveCollectionType.php @@ -35,11 +35,11 @@ public function buildForm(FormBuilderInterface $builder, array $options): void $prototype = $builder->create('delete', $options['button_delete_type'], $options['button_delete_options']); $builder->setAttribute('button_delete_prototype', $prototype->getForm()); } - + if ($options['allow_sort']) { $moveUpPrototype = $builder->create('move_up', $options['button_move_up_type'], $options['button_move_up_options']); $builder->setAttribute('button_move_up_prototype', $moveUpPrototype->getForm()); - + $moveDownPrototype = $builder->create('move_down', $options['button_move_down_type'], $options['button_move_down_options']); $builder->setAttribute('button_move_down_prototype', $moveDownPrototype->getForm()); } @@ -100,7 +100,7 @@ public function finishView(FormView $view, FormInterface $form, array $options): array_splice($entryView->vars['button_delete']->vars['block_prefixes'], 1, 0, 'live_collection_button_delete'); } } - + // Add move up and move down buttons if ($form->getConfig()->hasAttribute('button_move_up_prototype')) { $prototype = $form->getConfig()->getAttribute('button_move_up_prototype'); @@ -124,7 +124,7 @@ public function finishView(FormView $view, FormInterface $form, array $options): array_splice($entryView->vars['button_move_up']->vars['block_prefixes'], 1, 0, 'live_collection_button_move_up'); } } - + if ($form->getConfig()->hasAttribute('button_move_down_prototype')) { $prototype = $form->getConfig()->getAttribute('button_move_down_prototype'); diff --git a/src/LiveComponent/src/LiveCollectionTrait.php b/src/LiveComponent/src/LiveCollectionTrait.php index 6bc0e167a7f..354e0b0ec98 100644 --- a/src/LiveComponent/src/LiveCollectionTrait.php +++ b/src/LiveComponent/src/LiveCollectionTrait.php @@ -52,19 +52,19 @@ public function moveCollectionItemUp(PropertyAccessorInterface $propertyAccessor if ($index <= 0) { return; } - + $propertyPath = $this->fieldNameToPropertyPath($name, $this->formName); $data = $propertyAccessor->getValue($this->formValues, $propertyPath); - + if (!\is_array($data) || !isset($data[$index]) || !isset($data[$index - 1])) { return; } - + // Swap the current item with the one above it $temp = $data[$index - 1]; $data[$index - 1] = $data[$index]; $data[$index] = $temp; - + $propertyAccessor->setValue($this->formValues, $propertyPath, $data); } @@ -73,16 +73,16 @@ public function moveCollectionItemDown(PropertyAccessorInterface $propertyAccess { $propertyPath = $this->fieldNameToPropertyPath($name, $this->formName); $data = $propertyAccessor->getValue($this->formValues, $propertyPath); - + if (!\is_array($data) || !isset($data[$index]) || !isset($data[$index + 1])) { return; } - + // Swap the current item with the one below it $temp = $data[$index + 1]; $data[$index + 1] = $data[$index]; $data[$index] = $temp; - + $propertyAccessor->setValue($this->formValues, $propertyPath, $data); } From 0b5bdfce4787636498aa9ab5f64ef109917adf89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Harri=20H=C3=A4iv=C3=A4l=C3=A4?= Date: Fri, 25 Apr 2025 00:51:20 +0300 Subject: [PATCH 3/4] reorder buttons, add sorting property --- .../src/Form/Type/LiveCollectionType.php | 11 ++--- src/LiveComponent/src/LiveCollectionTrait.php | 40 +++++++++++++++---- .../templates/form_theme.html.twig | 8 ++-- 3 files changed, 42 insertions(+), 17 deletions(-) diff --git a/src/LiveComponent/src/Form/Type/LiveCollectionType.php b/src/LiveComponent/src/Form/Type/LiveCollectionType.php index 7c34d7e6e38..8cc5d219e6c 100644 --- a/src/LiveComponent/src/Form/Type/LiveCollectionType.php +++ b/src/LiveComponent/src/Form/Type/LiveCollectionType.php @@ -35,11 +35,11 @@ public function buildForm(FormBuilderInterface $builder, array $options): void $prototype = $builder->create('delete', $options['button_delete_type'], $options['button_delete_options']); $builder->setAttribute('button_delete_prototype', $prototype->getForm()); } - + if ($options['allow_sort']) { $moveUpPrototype = $builder->create('move_up', $options['button_move_up_type'], $options['button_move_up_options']); $builder->setAttribute('button_move_up_prototype', $moveUpPrototype->getForm()); - + $moveDownPrototype = $builder->create('move_down', $options['button_move_down_type'], $options['button_move_down_options']); $builder->setAttribute('button_move_down_prototype', $moveDownPrototype->getForm()); } @@ -100,7 +100,7 @@ public function finishView(FormView $view, FormInterface $form, array $options): array_splice($entryView->vars['button_delete']->vars['block_prefixes'], 1, 0, 'live_collection_button_delete'); } } - + // Add move up and move down buttons if ($form->getConfig()->hasAttribute('button_move_up_prototype')) { $prototype = $form->getConfig()->getAttribute('button_move_up_prototype'); @@ -124,7 +124,7 @@ public function finishView(FormView $view, FormInterface $form, array $options): array_splice($entryView->vars['button_move_up']->vars['block_prefixes'], 1, 0, 'live_collection_button_move_up'); } } - + if ($form->getConfig()->hasAttribute('button_move_down_prototype')) { $prototype = $form->getConfig()->getAttribute('button_move_down_prototype'); @@ -166,7 +166,8 @@ public function configureOptions(OptionsResolver $resolver): void 'button_move_down_options' => [], 'allow_add' => true, 'allow_delete' => true, - 'allow_sort' => true, + 'allow_sort' => false, + 'order_property_path' => null, 'by_reference' => false, ]); } diff --git a/src/LiveComponent/src/LiveCollectionTrait.php b/src/LiveComponent/src/LiveCollectionTrait.php index 354e0b0ec98..631d5f27e36 100644 --- a/src/LiveComponent/src/LiveCollectionTrait.php +++ b/src/LiveComponent/src/LiveCollectionTrait.php @@ -60,10 +60,22 @@ public function moveCollectionItemUp(PropertyAccessorInterface $propertyAccessor return; } - // Swap the current item with the one above it - $temp = $data[$index - 1]; - $data[$index - 1] = $data[$index]; - $data[$index] = $temp; + $formConfig = $this->getForm()->get($name)->getConfig(); + $orderPropertyPath = $formConfig->getOption('order_property_path'); + + if ($orderPropertyPath !== null) { + // Swap positions using the specified order property + $prevItemOrder = $propertyAccessor->getValue($data[$index - 1], $orderPropertyPath); + $currentItemOrder = $propertyAccessor->getValue($data[$index], $orderPropertyPath); + + $propertyAccessor->setValue($data[$index], $orderPropertyPath, $prevItemOrder); + $propertyAccessor->setValue($data[$index - 1], $orderPropertyPath, $currentItemOrder); + } else { + // Legacy behavior - simple array swap (with warning about limitations) + $temp = $data[$index - 1]; + $data[$index - 1] = $data[$index]; + $data[$index] = $temp; + } $propertyAccessor->setValue($this->formValues, $propertyPath, $data); } @@ -78,10 +90,22 @@ public function moveCollectionItemDown(PropertyAccessorInterface $propertyAccess return; } - // Swap the current item with the one below it - $temp = $data[$index + 1]; - $data[$index + 1] = $data[$index]; - $data[$index] = $temp; + $formConfig = $this->getForm()->get($name)->getConfig(); + $orderPropertyPath = $formConfig->getOption('order_property_path'); + + if ($orderPropertyPath !== null) { + // Swap positions using the specified order property + $nextItemOrder = $propertyAccessor->getValue($data[$index + 1], $orderPropertyPath); + $currentItemOrder = $propertyAccessor->getValue($data[$index], $orderPropertyPath); + + $propertyAccessor->setValue($data[$index], $orderPropertyPath, $nextItemOrder); + $propertyAccessor->setValue($data[$index + 1], $orderPropertyPath, $currentItemOrder); + } else { + // Legacy behavior - simple array swap (with warning about limitations) + $temp = $data[$index + 1]; + $data[$index + 1] = $data[$index]; + $data[$index] = $temp; + } $propertyAccessor->setValue($this->formValues, $propertyPath, $data); } diff --git a/src/LiveComponent/templates/form_theme.html.twig b/src/LiveComponent/templates/form_theme.html.twig index 4349883469a..cc6222a094e 100644 --- a/src/LiveComponent/templates/form_theme.html.twig +++ b/src/LiveComponent/templates/form_theme.html.twig @@ -6,14 +6,14 @@ {%- endblock live_collection_widget -%} {%- block live_collection_entry_row -%} - {{ block('form_row') }} - {%- if button_delete is defined and not button_delete.rendered -%} - {{ form_row(button_delete) }} - {%- endif -%} {%- if button_move_up is defined and not button_move_up.rendered -%} {{ form_row(button_move_up) }} {%- endif -%} {%- if button_move_down is defined and not button_move_down.rendered -%} {{ form_row(button_move_down) }} {%- endif -%} + {%- if button_delete is defined and not button_delete.rendered -%} + {{ form_row(button_delete) }} + {%- endif -%} + {{ block('form_row') }} {%- endblock live_collection_entry_row -%} From e8f2c4a3dd29e3e439383d7c367f26a924022143 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Harri=20H=C3=A4iv=C3=A4l=C3=A4?= Date: Fri, 25 Apr 2025 00:53:29 +0300 Subject: [PATCH 4/4] coding standard --- src/LiveComponent/src/Form/Type/LiveCollectionType.php | 8 ++++---- src/LiveComponent/src/LiveCollectionTrait.php | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/LiveComponent/src/Form/Type/LiveCollectionType.php b/src/LiveComponent/src/Form/Type/LiveCollectionType.php index 8cc5d219e6c..a7d214b51ad 100644 --- a/src/LiveComponent/src/Form/Type/LiveCollectionType.php +++ b/src/LiveComponent/src/Form/Type/LiveCollectionType.php @@ -35,11 +35,11 @@ public function buildForm(FormBuilderInterface $builder, array $options): void $prototype = $builder->create('delete', $options['button_delete_type'], $options['button_delete_options']); $builder->setAttribute('button_delete_prototype', $prototype->getForm()); } - + if ($options['allow_sort']) { $moveUpPrototype = $builder->create('move_up', $options['button_move_up_type'], $options['button_move_up_options']); $builder->setAttribute('button_move_up_prototype', $moveUpPrototype->getForm()); - + $moveDownPrototype = $builder->create('move_down', $options['button_move_down_type'], $options['button_move_down_options']); $builder->setAttribute('button_move_down_prototype', $moveDownPrototype->getForm()); } @@ -100,7 +100,7 @@ public function finishView(FormView $view, FormInterface $form, array $options): array_splice($entryView->vars['button_delete']->vars['block_prefixes'], 1, 0, 'live_collection_button_delete'); } } - + // Add move up and move down buttons if ($form->getConfig()->hasAttribute('button_move_up_prototype')) { $prototype = $form->getConfig()->getAttribute('button_move_up_prototype'); @@ -124,7 +124,7 @@ public function finishView(FormView $view, FormInterface $form, array $options): array_splice($entryView->vars['button_move_up']->vars['block_prefixes'], 1, 0, 'live_collection_button_move_up'); } } - + if ($form->getConfig()->hasAttribute('button_move_down_prototype')) { $prototype = $form->getConfig()->getAttribute('button_move_down_prototype'); diff --git a/src/LiveComponent/src/LiveCollectionTrait.php b/src/LiveComponent/src/LiveCollectionTrait.php index 631d5f27e36..ac2fa595a40 100644 --- a/src/LiveComponent/src/LiveCollectionTrait.php +++ b/src/LiveComponent/src/LiveCollectionTrait.php @@ -63,11 +63,11 @@ public function moveCollectionItemUp(PropertyAccessorInterface $propertyAccessor $formConfig = $this->getForm()->get($name)->getConfig(); $orderPropertyPath = $formConfig->getOption('order_property_path'); - if ($orderPropertyPath !== null) { + if (null !== $orderPropertyPath) { // Swap positions using the specified order property $prevItemOrder = $propertyAccessor->getValue($data[$index - 1], $orderPropertyPath); $currentItemOrder = $propertyAccessor->getValue($data[$index], $orderPropertyPath); - + $propertyAccessor->setValue($data[$index], $orderPropertyPath, $prevItemOrder); $propertyAccessor->setValue($data[$index - 1], $orderPropertyPath, $currentItemOrder); } else { @@ -93,11 +93,11 @@ public function moveCollectionItemDown(PropertyAccessorInterface $propertyAccess $formConfig = $this->getForm()->get($name)->getConfig(); $orderPropertyPath = $formConfig->getOption('order_property_path'); - if ($orderPropertyPath !== null) { + if (null !== $orderPropertyPath) { // Swap positions using the specified order property $nextItemOrder = $propertyAccessor->getValue($data[$index + 1], $orderPropertyPath); $currentItemOrder = $propertyAccessor->getValue($data[$index], $orderPropertyPath); - + $propertyAccessor->setValue($data[$index], $orderPropertyPath, $nextItemOrder); $propertyAccessor->setValue($data[$index + 1], $orderPropertyPath, $currentItemOrder); } else {