sonata-project/SonataAdminBundle

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

BA-JBI opened this issue · 1 comments

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

<input type="hidden" name="{{ full_name }}" id="{{ id }}" value="{{ value|join(',') }}" />
the rendered field is just a hidden input type.

success: function(html) {
jQuery('#field_container_{{ id }}').replaceWith(html);
var newElement = jQuery('#{{ id }} [value="' + data.objectId + '"]');
if (newElement.is("input")) {
newElement.attr('checked', 'checked');
} else {
newElement.attr('selected', 'selected');
}
jQuery('#field_container_{{ id }}').trigger('sonata-admin-append-form-element');
}

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

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:

final class ChoiceTypeExtension extends AbstractTypeExtension

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);
        }
    }