Skip to content

Commit 569acef

Browse files
committed
feature #421 [LiveComponent] Tweak live collection rendering (1ed)
This PR was squashed before being merged into the 2.x branch. Discussion ---------- [LiveComponent] Tweak live collection rendering | Q | A | ------------- | --- | Bug fix? | yes | New feature? | yes? | Tickets | - | License | MIT Commits ------- e174a28 [LiveComponent] Tweak live collection rendering
2 parents 16d8629 + e174a28 commit 569acef

File tree

4 files changed

+141
-19
lines changed

4 files changed

+141
-19
lines changed

src/LiveComponent/src/Form/Type/LiveCollectionType.php

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,14 @@ public function buildView(FormView $view, FormInterface $form, array $options):
4343
{
4444
if ($form->getConfig()->hasAttribute('button_add_prototype')) {
4545
$prototype = $form->getConfig()->getAttribute('button_add_prototype');
46-
$view->vars['button_add_prototype'] = $prototype->setParent($form)->createView($view);
47-
array_splice($view->vars['button_add_prototype']->vars['block_prefixes'], 1, 0, 'live_collection_button_add');
46+
$view->vars['button_add'] = $prototype->setParent($form)->createView($view);
47+
48+
$attr = $view->vars['button_add']->vars['attr'];
49+
$attr['data-action'] ??= 'live#action';
50+
$attr['data-action-name'] ??= sprintf('addCollectionItem(name=%s)', $view->vars['full_name']);
51+
$view->vars['button_add']->vars['attr'] = $attr;
52+
53+
array_splice($view->vars['button_add']->vars['block_prefixes'], 1, 0, 'live_collection_button_add');
4854
}
4955
}
5056

@@ -75,8 +81,14 @@ public function finishView(FormView $view, FormInterface $form, array $options):
7581
}
7682

7783
foreach ($view as $k => $entryView) {
78-
$entryView->vars['button_delete_prototype'] = $prototypes[$k]->createView($entryView);
79-
array_splice($entryView->vars['button_delete_prototype']->vars['block_prefixes'], 1, 0, 'live_collection_button_delete');
84+
$entryView->vars['button_delete'] = $prototypes[$k]->createView($entryView);
85+
86+
$attr = $entryView->vars['button_delete']->vars['attr'];
87+
$attr['data-action'] ??= 'live#action';
88+
$attr['data-action-name'] ??= sprintf('removeCollectionItem(name=%s, index=%s)', $view->vars['full_name'], $k);
89+
$entryView->vars['button_delete']->vars['attr'] = $attr;
90+
91+
array_splice($entryView->vars['button_delete']->vars['block_prefixes'], 1, 0, 'live_collection_button_delete');
8092
}
8193
}
8294
}
@@ -92,6 +104,9 @@ public function configureOptions(OptionsResolver $resolver): void
92104
'button_add_options' => [],
93105
'button_delete_type' => ButtonType::class,
94106
'button_delete_options' => [],
107+
'allow_add' => true,
108+
'allow_delete' => true,
109+
'by_reference' => false,
95110
]);
96111
}
97112

src/LiveComponent/src/Resources/doc/index.rst

Lines changed: 114 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1265,7 +1265,7 @@ There is no need for a custom template just render the form as usual:
12651265
12661266
The ``add`` and ``delete`` buttons are rendered as separate ``ButtonType`` form
12671267
types and can be customized like a normal form type via the ``live_collection_button_add``
1268-
and ``live_collection_button_delete`` respectively:
1268+
and ``live_collection_button_delete`` block prefix respectively:
12691269

12701270
.. code-block:: twig
12711271
@@ -1282,6 +1282,118 @@ and ``live_collection_button_delete`` respectively:
12821282
{{ block('button_widget') }}
12831283
{% endblock live_collection_button_add_widget %}
12841284
1285+
If you only want to customize some attributes maybe simpler to use the options in the form type:
1286+
1287+
// ...
1288+
->add('comments', LiveCollectionType::class, [
1289+
'entry_type' => CommentFormType::class,
1290+
'allow_add' => true,
1291+
'allow_delete' => true,
1292+
'by_reference' => false,
1293+
'label' => false,
1294+
'button_delete_options' => [
1295+
'label' => 'X',
1296+
'attr' => [
1297+
'class' => 'btn btn-outline-danger',
1298+
],
1299+
]
1300+
])
1301+
// ...
1302+
1303+
If you want more control over how each row is rendered you can override the blocks
1304+
related to the ``LiveCollectionType``. This works the same way as for `the traditional
1305+
collection type`_, but you should use ``live_collection_*`` and ``live_collection_entry_*``
1306+
as prefixes instead.
1307+
1308+
For example, let's continue our previous example and customize the rendering of the blog post comments form.
1309+
By default the add comment button is placed after the comments, let's move it before them.
1310+
1311+
.. code-block:: twig
1312+
1313+
{%- block live_collection_widget -%}
1314+
{%- if button_add is defined and not button_add.rendered -%}
1315+
{{ form_row(button_add) }}
1316+
{%- endif -%}
1317+
{{ block('form_widget') }}
1318+
{%- endblock -%}
1319+
1320+
Now add a div around each row:
1321+
1322+
.. code-block:: twig
1323+
1324+
{%- block live_collection_entry_row -%}
1325+
<div>
1326+
{{ block('form_row') }}
1327+
{%- if button_delete is defined and not button_delete.rendered -%}
1328+
{{ form_row(button_delete) }}
1329+
{%- endif -%}
1330+
</div>
1331+
{%- endblock -%}
1332+
1333+
.. note::
1334+
1335+
Under the hood, ``LiveCollectionType`` adds ``button_add`` and
1336+
``button_delete`` fields to the form in a special way. These fields
1337+
are not added as regular form fields, so they are not part of the form
1338+
tree, but only the form view. The ``button_add`` is added to the
1339+
collection view variables and a ``button_delete`` is added to each
1340+
item view variables.
1341+
1342+
As an another example, now let's create a general bootstrap 5 theme for the live
1343+
collection type, rendering every item in a table row:
1344+
1345+
.. code-block:: twig
1346+
1347+
{%- block live_collection_widget -%}
1348+
<table class="table table-borderless form-no-mb">
1349+
<thead>
1350+
<tr>
1351+
{% for child in form|last %}
1352+
<td>{{ form_label(child) }}</td>
1353+
{% endfor %}
1354+
<td></td>
1355+
</tr>
1356+
</thead>
1357+
<tbody>
1358+
{{ block('form_widget') }}
1359+
</tbody>
1360+
</table>
1361+
{%- if skip_add_button|default(false) is same as false and button_add is defined and not button_add.rendered -%}
1362+
{{ form_widget(button_add, { label: '+ Add Item', class: 'btn btn-outline-primary' }) }}
1363+
{%- endif -%}
1364+
{%- endblock -%}
1365+
1366+
{%- block live_collection_entry_row -%}
1367+
<tr>
1368+
{% for child in form %}
1369+
<td>{{- form_row(child, { label: false }) -}}</td>
1370+
{% endfor %}
1371+
<td>
1372+
{{- form_row(button_delete, { label: 'X', attr: { class: 'btn btn-outline-danger' } }) -}}
1373+
</td>
1374+
</tr>
1375+
{%- endblock -%}
1376+
1377+
To render the add button later in the template, you can skip rendering it initially with ``skip_add_button``,
1378+
then render it manually after:
1379+
1380+
.. code-block:: twig
1381+
1382+
<table class="table table-borderless form-no-mb">
1383+
<thead>
1384+
<tr>
1385+
<td>Item</td>
1386+
<td>Priority</td>
1387+
<td></td>
1388+
</tr>
1389+
</thead>
1390+
<tbody>
1391+
{{ form_row(form.todoItems, { skip_add_button: true }) }}
1392+
</tbody>
1393+
</table>
1394+
1395+
{{ form_widget(form.todoItems.vars.button_add, { label: '+ Add Item', class: 'btn btn-outline-primary' }) }}
1396+
12851397
Modifying Nested Object Properties with the "exposed" Option
12861398
------------------------------------------------------------
12871399

@@ -1768,3 +1880,4 @@ bound to Symfony's BC policy for the moment.
17681880
.. _`Symfony UX configured in your app`: https://symfony.com/doc/current/frontend/ux.html
17691881
.. _`attributes variable`: https://symfony.com/bundles/ux-twig-component/current/index.html#component-attributes
17701882
.. _`CollectionType`: https://symfony.com/doc/current/form/form_collections.html
1883+
.. _`the traditional collection type`: https://symfony.com/doc/current/form/form_themes.html#fragment-naming-for-collections
Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,13 @@
11
{%- block live_collection_widget -%}
22
{{ block('form_widget') }}
3-
{%- if button_add_prototype is defined and not button_add_prototype.rendered -%}
4-
{{ form_row(button_add_prototype, { attr: button_add_prototype.vars.attr|merge({
5-
'data-action': 'live#action',
6-
'data-action-name': 'addCollectionItem(name=' ~ form.vars.full_name ~ ')'
7-
}) }) }}
3+
{%- if skip_add_button|default(false) is same as(false) and button_add is defined and not button_add.rendered -%}
4+
{{ form_row(button_add) }}
85
{%- endif -%}
96
{%- endblock live_collection_widget -%}
107

118
{%- block live_collection_entry_row -%}
129
{{ block('form_row') }}
13-
{%- if button_delete_prototype is defined and not button_delete_prototype.rendered -%}
14-
{{ form_row(button_delete_prototype, { attr: button_delete_prototype.vars.attr|merge({
15-
'data-action': 'live#action',
16-
'data-action-name': 'removeCollectionItem(name=' ~ form.parent.vars.full_name ~ ', index=' ~ form.vars.name ~ ')'
17-
}) }) }}
10+
{%- if button_delete is defined and not button_delete.rendered -%}
11+
{{ form_row(button_delete) }}
1812
{%- endif -%}
1913
{%- endblock live_collection_entry_row -%}

src/LiveComponent/tests/Unit/Form/Type/LiveCollectionTypeTest.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ public function testAddButtonPrototypeDefaultBlockPrefixes()
3737
];
3838

3939
$this->assertCount(0, $collectionView);
40-
$this->assertSame($expectedBlockPrefixes, $collectionView->vars['button_add_prototype']->vars['block_prefixes']);
40+
$this->assertSame($expectedBlockPrefixes, $collectionView->vars['button_add']->vars['block_prefixes']);
4141
}
4242

4343
public function testAddButtonPrototypeBlockPrefixesWithCustomBlockPrefix()
@@ -57,7 +57,7 @@ public function testAddButtonPrototypeBlockPrefixesWithCustomBlockPrefix()
5757
];
5858

5959
$this->assertCount(0, $collectionView);
60-
$this->assertSame($expectedBlockPrefixes, $collectionView->vars['button_add_prototype']->vars['block_prefixes']);
60+
$this->assertSame($expectedBlockPrefixes, $collectionView->vars['button_add']->vars['block_prefixes']);
6161
}
6262

6363
public function testDeleteButtonPrototypeDefaultBlockPrefixes()
@@ -78,7 +78,7 @@ public function testDeleteButtonPrototypeDefaultBlockPrefixes()
7878
];
7979

8080
$this->assertCount(1, $collectionView);
81-
$this->assertSame($expectedBlockPrefixes, $collectionView['tags']->vars['button_delete_prototype']->vars['block_prefixes']);
81+
$this->assertSame($expectedBlockPrefixes, $collectionView['tags']->vars['button_delete']->vars['block_prefixes']);
8282
}
8383

8484
public function testDeleteButtonPrototypeBlockPrefixesWithCustomBlockPrefix()
@@ -101,6 +101,6 @@ public function testDeleteButtonPrototypeBlockPrefixesWithCustomBlockPrefix()
101101
];
102102

103103
$this->assertCount(1, $collectionView);
104-
$this->assertSame($expectedBlockPrefixes, $collectionView['tags']->vars['button_delete_prototype']->vars['block_prefixes']);
104+
$this->assertSame($expectedBlockPrefixes, $collectionView['tags']->vars['button_delete']->vars['block_prefixes']);
105105
}
106106
}

0 commit comments

Comments
 (0)