sonata-project / SonataAdminBundle

The missing Symfony Admin Generator
https://docs.sonata-project.org/projects/SonataAdminBundle
MIT License
2.11k stars 1.26k forks source link

Model Type: New created entity does not get selected on sortable collections #8201

Open BA-JBI opened 2 months ago

BA-JBI commented 2 months ago

Environment

Sonata packages

show

``` $ composer show --latest 'sonata-project/*' Direct dependencies required in composer.json: sonata-project/translation-bundle dev-symfony7 cbd1ec0 dev-symfony7 cbd1ec0 SonataTranslationBundle sonata-project/user-bundle 5.x-dev 2d82bcb 5.x-dev 2d82bcb Symfony SonataUserBundle Transitive dependencies not required in composer.json: sonata-project/admin-bundle 4.31.0 4.31.0 The missing Symfony Admin Generator sonata-project/block-bundle 5.1.0 5.1.0 Symfony SonataBlockBundle sonata-project/doctrine-extensions 2.4.0 2.4.0 Doctrine2 behavioral extensions sonata-project/doctrine-orm-admin-bundle 4.17.1 4.17.1 Integrate Doctrine ORM into the SonataAdminBundle sonata-project/exporter 3.3.0 3.3.0 Lightweight Exporter library sonata-project/form-extensions 2.4.0 2.4.0 Symfony form extensions sonata-project/intl-bundle 3.2.0 3.2.0 Symfony SonataIntlBundle sonata-project/media-bundle 4.13.0 4.13.0 Symfony SonataMediaBundle sonata-project/twig-extensions 2.4.0 2.4.0 Sonata twig extensions ```

PHP version

show

``` $ php -v PHP 8.3.10 (cli) (built: Aug 2 2024 16:00:00) (NTS) ```

Subject

$form->add('references', ModelType::class, [
    'multiple' => true,
    'sortable' => true, // <-- This produces the issue
];

image

When adding new Entity via "New" button, entity is created correctly but new created entity does not get selected automatically.

Problem

Because of https://github.com/sonata-project/SonataAdminBundle/blob/73492afdcdf6cb5314685b2661f87dc906b1f2b6/src/Resources/views/Form/form_admin_fields.html.twig#L557 the rendered field is just a hidden input type.

https://github.com/sonata-project/SonataAdminBundle/blob/73492afdcdf6cb5314685b2661f87dc906b1f2b6/src/Resources/views/CRUD/Association/edit_many_script.html.twig#L332-L342 The edit_many_script does not handle the returned id value correctly.

Proposed solution

jQuery('#field_container_{{ id }}').replaceWith(html);
var newElement = jQuery('#{{ id }} [value="' + data.objectId + '"]');

if (newElement.length) {
    if (newElement.is("input")) {
        newElement.attr('checked', 'checked');
    } else {
        newElement.attr('selected', 'selected');
    }
} else {
    var selections = jQuery('#{{ id }}').val().split(',');
    selections.push(data.objectId);
    jQuery('#{{ id }}').val(selections.filter((val) => val.length > 0).join(','));
}

jQuery('#field_container_{{ id }}').trigger('sonata-admin-append-form-element');

Already tested it works

BA-JBI commented 2 months ago

Detected one further related issue while loading the new choice list by calling https://example.com/admin/core/get-form-field-element (RetrieveFormFieldElementAction).

The problem is the FormEvents::PRE_SUBMIT listener in Symfony ChoiceType

https://github.com/symfony/symfony/blob/bd244cc88c9106da311055bf437ee9ced5285254/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php#L101-L125

The data in the dispatched event is a array with exactly one item and this contains a comma separated list of items. When there is more than one item selected, then the listener clears the selection, because the list is interpreted as unknown item.

In Order to resolve that i suppose the following solution:

https://github.com/sonata-project/SonataAdminBundle/blob/73492afdcdf6cb5314685b2661f87dc906b1f2b6/src/Form/Extension/ChoiceTypeExtension.php#L28 Add the following method:

    public function buildForm(FormBuilderInterface $builder, array $options): void
    {
        if ($options['multiple'] && (true === ($options['sortable'] ?? false))) {
            // Make sure that scalar, submitted values are converted to arrays
            // which can be submitted to the checkboxes/radio buttons
            $builder->addEventListener(FormEvents::PRE_SUBMIT, static function (FormEvent $event) use ($options) {
                /** @var PreSubmitEvent $event */
                $form = $event->getForm();
                $data = $event->getData();

                if (!is_array($data) || count($data) !== 1) {
                    return;
                }

                if (str_contains($data[0], ',')) {
                    $event->setData(explode(',', $data[0]));
                }
            },1);
        }
    }