symfony / ux

Symfony UX initiative: a JavaScript ecosystem for Symfony
https://ux.symfony.com/
MIT License
835 stars 307 forks source link

[LiveComponent] When selecting an "empty" choice, it get blanked. #960

Closed jcrombez closed 1 year ago

jcrombez commented 1 year ago

When using a live component, when the choice made is the "empty" choice and i'm using a placeholder as a choice name, it get blanked instead of staying on the placeholder text.

weaverryan: I believe the problem is that the underlying value is being set to null. And then in JavaScript, LiveComponents is basically doing select.value = null, which causes “nothing” to be selected.

screencast.webm

                ->add('paymentDelay', EntityType::class, [
                    'class' => PaymentDelay::class,
                    'label' => 'Délais de paiement',
                    'required' => false,
                    'placeholder' => "Date d'échéance libre",
                    'attr' => [
                        'data-action' => "change->live#action",
                        'data-action-name' => 'refresh()'
                    ]
                ])

image (1)

dsoriano commented 1 year ago

Same problem here. Tested on Chrome and Firefox

dsoriano commented 1 year ago

Ok, so I finally found why there is this bug. Actually, when morphdom synchronize option tags, it put a selected attribute on the good option tag and set the selectedIndex of the select tag to -1. As symfony-ux use a trick to speed up morphdom to not rerender not changed nodes, the placeholder option tag is not processed by morphdom and the selected attribute is not seted. And as the select tag has a selectedIndex to -1, that's why we have a blank select field. A bit tricky to explain sorry.

The most simple fix I found is to force morphdom to evaluate all option tags from a select, even if there is no change.

In ux/src/LiveComponent/assets/src/morphdom.ts :

if (fromEl.nodeName.toUpperCase() !== 'OPTION' && fromEl.isEqualNode(toEl)) {
    // the nodes are equal, but the "value" on some might differ
    // lets try to quickly compare a bit more deeply
    const normalizedFromEl = cloneHTMLElement(fromEl);
    normalizeAttributesForComparison(normalizedFromEl);

    const normalizedToEl = cloneHTMLElement(toEl);
    normalizeAttributesForComparison(normalizedToEl);

    if (normalizedFromEl.isEqualNode(normalizedToEl)) {
        // don't bother updating
        return false;
    }
}

I made a PR for the fix