Symfony Collections that works out of the box with Symfony UX
Before you start, make sure you have StimulusBundle configured in your app.
Install the bundle using Composer and Symfony Flex:
composer require arkounay/ux-collection
If you're using WebpackEncore, install your assets and restart Encore (not needed if you're using AssetMapper):
npm install --force
npm run watch
# or use yarn
yarn install --force
yarn watch
If you're using bootstrap 5, you should disable the sandalone CSS import in assets\controllers.json
:
"@arkounay/ux-collection": {
"collection": {
"enabled": true,
"fetch": "eager",
"autoimport": {
"@arkounay/ux-collection/src/style.css": true,
"@arkounay/ux-collection/src/style-when-not-using-bootstrap-5.css": false
}
},
"tabbed_collection": {
"enabled": true,
"fetch": "eager",
"autoimport": {
"@arkounay/ux-collection/src/tabbed-style.css": true
}
}
}
In a form, use UxCollectionType. It works like a classic CollectionType except it has more options : e.g:
use Arkounay\Bundle\UxCollectionBundle\Form\UxCollectionType;
// ...
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
// ...
->add('myCollection', UxCollectionType::class, [
'entry_type' => MyEntryType::class,
'allow_add' => true,
'allow_delete' => true,
'allow_drag_and_drop' => true,
'drag_and_drop_filter' => 'input,textarea,a,button,label',
'display_sort_buttons' => true,
'add_label' => 'Add an item',
'min' => 3,
'max' => 10,
])
;
}
event.preventDefault()
when triggered filter
(default false)allow_add
is set to true (default false)'.position'
)allow_add
set to true
and has less items than min
upon creation, empty items will be added and the remove button will remain hidden until more items are created. (default 1)prototype_name
of the child's collection. It needs to be different than the parent's collection (that defaults to __name__
)position_selector
in both parent and child collections, make sure they are differentUxCollection allows you to extend its default behavior using a custom Stimulus controller, ie custom_collection_controller.js
:
import { Controller } from '@hotwired/stimulus';
export default class extends Controller {
connect() {
this.element.addEventListener('ux-collection:connect', this._onConnect);
this.element.addEventListener('ux-collection:change', this._onChange);
this.element.addEventListener('ux-collection:add', this._onAdd);
this.element.addEventListener('ux-collection:remove', this._onRemove);
}
disconnect() {
this.element.removeEventListener('ux-collection:connect', this._onConnect);
this.element.removeEventListener('ux-collection:change', this._onChange);
this.element.removeEventListener('ux-collection:add', this._onAdd);
this.element.removeEventListener('ux-collection:remove', this._onRemove);
}
_onConnect() {
console.log('The custom collection was just created');
}
_onChange() {
console.log('The custom collection changed');
}
_onAdd(event) {
console.log('An element was added', event.detail);
}
_onRemove(event) {
console.log('An element was removed', event.detail);
}
}
Then in your form, add your controller as an HTML attribute:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
// ...
->add('collection', UxCollectionType::class, [
'attr' => ['data-controller' => 'custom-collection']
])
// ...
;
}
If you have a parent stimulus controller and want to monitor changes in the collection (such as updating a total number of items or adjusting prices in a cart), you can utilize the ux-collection:change dispatched event and directly invoke a parent's controller method. For example if you have a stimulus controller called parent
wrapping the collection with a onCollectionChange
method, it will be called if you add the proper action in the form:
$builder->add('collection', UxCollectionType::class, [
// ...
'attr' => ['data-action' => 'ux-collection:change->parent#onCollectionChange']
]);
If your collection contains File inputs, depending on how you use FileType (e.g if you use a collection of VichUploaderBundle), you might have issues when adding/removing/moving items related to how positionning work. Use either the position_selector
option to fix this, or disable sorting by setting allow_drag_and_drop
and display_sort_buttons
to false
: this way the form name will not change.
For easyadmin 3+ you need to manually specify the form theme by overriding configureCrud in your DashboardController to add the theme @ArkounayUxCollection/ux_collection_form_theme.html.twig
public function configureCrud(): Crud
{
return Crud::new()->addFormTheme('@ArkounayUxCollection/ux_collection_form_theme.html.twig');
}
You will need to configure your admin to use WebpackEncore so Symfony UX is taken into account, for example:
public function configureAssets(Assets $assets): Assets
{
return parent::configureAssets($assets)
->addWebpackEncoreEntry('app');
}
This bundle is already included in QAG and works out of the box
There is also UxHorizontalCollectionType for collections that need to move horizontally, and UxTabbedCollectionType that creates a tab-type collection (works only when bootstrap's used in your project for now, and you will probably need to override the base css a bit for this one - here's a QuickAdminGeneratorBundle integration example)