Skip to content

Commit 3988e7f

Browse files
committed
FEAT: [Admin] 100 add order functionality for content elements
refactor: use LiveComponent instead of JS for moving logic. [behat]: adjust behat test to new behavior
1 parent 1f6b1f6 commit 3988e7f

7 files changed

Lines changed: 245 additions & 10 deletions

File tree

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
@managing_pages
2+
Feature: Sorting content elements on a page
3+
In order to manage the order of content on a page
4+
As an Administrator
5+
I want to be able to reorder content elements
6+
7+
Background:
8+
Given I am logged in as an administrator
9+
And the store operates on a single channel in "United States"
10+
11+
@ui @javascript
12+
Scenario: Moving a content element down
13+
When I go to the create page page
14+
And I fill the code with "sort-test-page"
15+
And I fill the name with "Sort Test Page"
16+
And I fill the slug with "sort-test-page"
17+
And I add a heading content element with type "h1" and "My Title" content
18+
And I add a textarea content element with "My body text" content
19+
When I move the 1st content element down
20+
Then the 1st content element should be a "Textarea" element
21+
And the 2nd content element should be a "Heading" element
22+
23+
@ui @javascript
24+
Scenario: Moving a content element up
25+
When I go to the create page page
26+
And I fill the code with "sort-test-page"
27+
And I fill the name with "Sort Test Page"
28+
And I fill the slug with "sort-test-page"
29+
And I add a heading content element with type "h1" and "My Title" content
30+
And I add a textarea content element with "My body text" content
31+
When I move the 2nd content element up
32+
Then the 1st content element should be a "Textarea" element
33+
And the 2nd content element should be a "Heading" element
34+
35+
@ui @javascript
36+
Scenario: The first content element cannot be moved up
37+
When I go to the create page page
38+
And I fill the code with "sort-test-page"
39+
And I fill the name with "Sort Test Page"
40+
And I fill the slug with "sort-test-page"
41+
And I add a heading content element with type "h1" and "My Title" content
42+
And I add a textarea content element with "My body text" content
43+
Then the move up button of the 1st content element should be disabled
44+
45+
@ui @javascript
46+
Scenario: The last content element cannot be moved down
47+
When I go to the create page page
48+
And I fill the code with "sort-test-page"
49+
And I fill the name with "Sort Test Page"
50+
And I fill the slug with "sort-test-page"
51+
And I add a heading content element with type "h1" and "My Title" content
52+
And I add a textarea content element with "My body text" content
53+
Then the move down button of the 2nd content element should be disabled

src/Twig/Component/Trait/ContentElementsCollectionFormComponentTrait.php

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,18 @@
1313

1414
namespace Sylius\CmsPlugin\Twig\Component\Trait;
1515

16+
use Sylius\Bundle\UiBundle\Twig\Component\LiveCollectionTrait;
1617
use Sylius\CmsPlugin\Entity\TemplateInterface;
1718
use Sylius\CmsPlugin\Form\Type\Translation\ContentConfigurationTranslationsType;
1819
use Sylius\CmsPlugin\Repository\TemplateRepositoryInterface;
20+
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
1921
use Symfony\UX\LiveComponent\Attribute\LiveAction;
2022
use Symfony\UX\LiveComponent\Attribute\LiveArg;
2123
use Symfony\UX\LiveComponent\ComponentWithFormTrait;
2224

2325
/**
2426
* @mixin ComponentWithFormTrait
27+
* @mixin LiveCollectionTrait
2528
*
2629
* @see ContentConfigurationTranslationsType
2730
*/
@@ -33,6 +36,46 @@ trait ContentElementsCollectionFormComponentTrait
3336
/** @var array<int|string, TemplateInterface> */
3437
protected array $templatesCache = [];
3538

39+
#[LiveAction]
40+
public function moveCollectionItem(
41+
PropertyAccessorInterface $propertyAccessor,
42+
#[LiveArg]
43+
string $name,
44+
#[LiveArg]
45+
int $index,
46+
#[LiveArg]
47+
string $direction,
48+
): void {
49+
if (null === $this->formName) {
50+
return;
51+
}
52+
53+
$propertyPath = $this->fieldNameToPropertyPath($name, $this->formName);
54+
$data = $propertyAccessor->getValue($this->formValues, $propertyPath);
55+
56+
if (!\is_array($data)) {
57+
return;
58+
}
59+
60+
$keys = array_keys($data);
61+
$currentPos = array_search($index, $keys, true);
62+
63+
if (false === $currentPos) {
64+
return;
65+
}
66+
67+
$swapPos = 'up' === $direction ? $currentPos - 1 : $currentPos + 1;
68+
69+
if ($swapPos < 0 || $swapPos >= \count($keys)) {
70+
return;
71+
}
72+
73+
$swapKey = $keys[$swapPos];
74+
[$data[$index], $data[$swapKey]] = [$data[$swapKey], $data[$index]];
75+
76+
$propertyAccessor->setValue($this->formValues, $propertyPath, $data);
77+
}
78+
3679
#[LiveAction]
3780
public function applyContentTemplate(#[LiveArg] string $localeCode): void
3881
{

templates/admin/shared/component_elements/form_theme.html.twig

Lines changed: 42 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,51 @@
55
{%- endblock live_collection_widget -%}
66

77
{%- block live_collection_entry_row -%}
8-
<div id="{{ id }}" {{ block('attributes') }} {{ sylius_test_html_attribute('entry-row') }}>
8+
{% set entryKeys = form.parent.children|keys %}
9+
{% set isFirst = form.vars.name == entryKeys|first %}
10+
{% set isLast = form.vars.name == entryKeys|last %}
11+
12+
<div class="sortable-item" id="{{ id }}" {{ block('attributes') }} {{ sylius_test_html_attribute('entry-row') }}>
913
{{- form_errors(form) -}}
1014

11-
<div class="alert text-body">
12-
<div class="text-end">
13-
{{- form_row(button_delete, sylius_test_form_attribute('delete-action')|sylius_merge_recursive({'attr': {'class': 'btn-close'}})) -}}
15+
<div class="alert text-body d-flex align-items-center gap-4">
16+
<div class="flex-grow-1">
17+
{{- form_row(form.type) -}}
18+
<div>
19+
{{- form_row(form.configuration, {'label': false}) -}}
20+
</div>
1421
</div>
15-
16-
{{- form_row(form.type) -}}
17-
18-
<div>
19-
{{- form_row(form.configuration, {'label': false}) -}}
22+
<div class="d-flex flex-column align-items-center gap-2">
23+
<button
24+
type="button"
25+
class="btn p-1 btn-outline-primary d-flex align-items-center justify-content-center"
26+
data-action="live#action"
27+
data-live-action-param="moveCollectionItem"
28+
data-live-name-param="{{ form.parent.vars.full_name }}"
29+
data-live-index-param="{{ form.vars.name }}"
30+
data-live-direction-param="up"
31+
{{ isFirst ? 'disabled' }}
32+
>
33+
{{ ux_icon('tabler:chevron-up', {style: 'display: block; margin: auto'}) }}
34+
</button>
35+
{{- form_row(button_delete, sylius_test_form_attribute('delete-action')|sylius_merge_recursive({
36+
'label': ux_icon('tabler:trash', {style: 'display: block; margin: auto'}),
37+
'label_html': true,
38+
'attr': {'class': 'btn p-1 btn-outline-danger'},
39+
'row_attr': {'class': 'mb-0'}
40+
})) -}}
41+
<button
42+
type="button"
43+
class="btn p-1 btn-outline-primary d-flex align-items-center justify-content-center lh-1"
44+
data-action="live#action"
45+
data-live-action-param="moveCollectionItem"
46+
data-live-name-param="{{ form.parent.vars.full_name }}"
47+
data-live-index-param="{{ form.vars.name }}"
48+
data-live-direction-param="down"
49+
{{ isLast ? 'disabled' }}
50+
>
51+
{{ ux_icon('tabler:chevron-down', {style: 'display: block; margin: auto'}) }}
52+
</button>
2053
</div>
2154
</div>
2255
</div>

templates/admin/shared/editor/trix.html.twig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,6 @@
66
<input type="hidden" id="{{ id }}" name="{{ full_name }}" value="{{ value }}" />
77

88
<trix-toolbar id="{{ toolbar_id }}"></trix-toolbar>
9-
<trix-editor input="{{ id }}" toolbar="{{ toolbar_id }}"></trix-editor>
9+
<trix-editor input="{{ id }}" toolbar="{{ toolbar_id }}" data-live-ignore></trix-editor>
1010
</div>
1111
{% endblock %}

tests/Behat/Context/Ui/Admin/ContentCollectionContext.php

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,4 +205,56 @@ public function iShouldNotSeeContentElementInTheContentElementsSection(string $c
205205
{
206206
Assert::false($this->contentElementsCollectionElement->hasContentElement($contentElement));
207207
}
208+
209+
/**
210+
* @When I move the :ordinal content element up
211+
*/
212+
public function iMoveTheContentElementUp(string $ordinal): void
213+
{
214+
$this->contentElementsCollectionElement->moveContentElementUp($this->parseOrdinal($ordinal));
215+
}
216+
217+
/**
218+
* @When I move the :ordinal content element down
219+
*/
220+
public function iMoveTheContentElementDown(string $ordinal): void
221+
{
222+
$this->contentElementsCollectionElement->moveContentElementDown($this->parseOrdinal($ordinal));
223+
}
224+
225+
/**
226+
* @Then the :ordinal content element should be a :type element
227+
*/
228+
public function theContentElementAtPositionShouldBeOfType(string $ordinal, string $type): void
229+
{
230+
Assert::same(
231+
$this->contentElementsCollectionElement->getContentElementTypeAtPosition($this->parseOrdinal($ordinal)),
232+
$type,
233+
);
234+
}
235+
236+
/**
237+
* @Then the move up button of the :ordinal content element should be disabled
238+
*/
239+
public function theMoveUpButtonOfTheContentElementShouldBeDisabled(string $ordinal): void
240+
{
241+
Assert::true(
242+
$this->contentElementsCollectionElement->isContentElementMoveUpButtonDisabled($this->parseOrdinal($ordinal)),
243+
);
244+
}
245+
246+
/**
247+
* @Then the move down button of the :ordinal content element should be disabled
248+
*/
249+
public function theMoveDownButtonOfTheContentElementShouldBeDisabled(string $ordinal): void
250+
{
251+
Assert::true(
252+
$this->contentElementsCollectionElement->isContentElementMoveDownButtonDisabled($this->parseOrdinal($ordinal)),
253+
);
254+
}
255+
256+
private function parseOrdinal(string $ordinal): int
257+
{
258+
return (int) preg_replace('/\D/', '', $ordinal);
259+
}
208260
}

tests/Behat/Element/Admin/ContentElementsCollectionElement.php

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,50 @@ public function removeContentElement(string $type): void
157157
$this->waitForFormUpdate();
158158
}
159159

160+
public function moveContentElementUp(int $position): void
161+
{
162+
$button = $this->getSortButton($position, 'up');
163+
$button->click();
164+
}
165+
166+
public function moveContentElementDown(int $position): void
167+
{
168+
$button = $this->getSortButton($position, 'down');
169+
$button->click();
170+
}
171+
172+
public function getContentElementTypeAtPosition(int $position): string
173+
{
174+
$elements = $this->getContentElements();
175+
Assert::keyExists($elements, $position - 1, sprintf('No content element at position %d.', $position));
176+
177+
$selectedOption = $elements[$position - 1]->find('css', 'option[selected]');
178+
Assert::notNull($selectedOption, sprintf('No selected type option found at position %d.', $position));
179+
180+
return $selectedOption->getText();
181+
}
182+
183+
public function isContentElementMoveUpButtonDisabled(int $position): bool
184+
{
185+
return $this->getSortButton($position, 'up')->hasAttribute('disabled');
186+
}
187+
188+
public function isContentElementMoveDownButtonDisabled(int $position): bool
189+
{
190+
return $this->getSortButton($position, 'down')->hasAttribute('disabled');
191+
}
192+
193+
private function getSortButton(int $position, string $direction): NodeElement
194+
{
195+
$elements = $this->getContentElements();
196+
Assert::keyExists($elements, $position - 1, sprintf('No content element at position %d.', $position));
197+
198+
$button = $elements[$position - 1]->find('css', sprintf('[data-live-direction-param="%s"]', $direction));
199+
Assert::notNull($button, sprintf('Sort %s button not found at position %d.', $direction, $position));
200+
201+
return $button;
202+
}
203+
160204
protected function getDefinedElements(): array
161205
{
162206
return array_merge(parent::getDefinedElements(), [

tests/Behat/Element/Admin/ContentElementsCollectionElementInterface.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,4 +33,14 @@ public function updateContentElementOfType(string $type, array|string $content):
3333
public function addContentElementOfTypeWithContent(string $type, array|string $content): void;
3434

3535
public function removeContentElement(string $type): void;
36+
37+
public function moveContentElementUp(int $position): void;
38+
39+
public function moveContentElementDown(int $position): void;
40+
41+
public function getContentElementTypeAtPosition(int $position): string;
42+
43+
public function isContentElementMoveUpButtonDisabled(int $position): bool;
44+
45+
public function isContentElementMoveDownButtonDisabled(int $position): bool;
3646
}

0 commit comments

Comments
 (0)