symfony / ux

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

[LiveComponent] How to set the value of an input field within a modal-live-component when the modal is opened? #1625

Open momocode-de opened 6 months ago

momocode-de commented 6 months ago

I tried the following demo for a bootstrap modal: https://ux.symfony.com/demos/live-component/product-form

It works quite well, but I have a question. I have a list of items on my page and each item has a button that should open the modal. In the modal there should be a form that contains a hidden input with the ID of the item. There are also two text fields. If I render the modal in every item, this is not a problem. But that wouldn't be so nice if the modal is rendered in every item just because it has to contain the ID of the item in the hidden input. So my idea would be to include the modal in the template once under the list. Is there a smart way to set the value of the input field when the button is clicked, i.e. when the modal is opened?

smnandre commented 6 months ago

I did it with an event, listened by the ModalComponent which itselfs change its model and trigger an event leading to the re-render and the opening of the modal there:

There is a controller which use event delegation to handle the "clicks" but if you have a compenent for every item it's event better

const customEvent = new CustomEvent('Icon:Clicked', { detail: { icon: iconCard.title }, bubbles: true });
window.dispatchEvent(customEvent);

Then the modal controller listen to this and

connect() {
    window.addEventListener('Icon:Clicked', this.onIconClick.bind(this));
}

// ...

onIconClick(event) {
    const input = this.element.querySelector('input');
    input.value = event.detail.icon;
    input.dispatchEvent(new Event('change', {bubbles: true}));
    this.show();
}

Now the ModalController, beeing a LiveController, has got the "id" and can render a form with the correct entity

The event part could be improved but you got the idea

momocode-de commented 6 months ago

@smnandre Thank you for your approach! I have now done it with an extra controller for this type of modal. I have added this to the template of the general bootstrap modal component:

<div {{ attributes.defaults(stimulus_controller('bootstrap-modal')) }} ...

And where the modal is added to the template, I now add an extra controller:

{% component BootstrapModal with {id: 'my-item-modal', 'data-controller': 'my-item-modal'} %}

This is the "my-item-modal-controller" for this specific type of modals:

import { Controller } from '@hotwired/stimulus';
import { getComponent } from '@symfony/ux-live-component';

/* stimulusFetch: 'lazy' */
export default class extends Controller {
    async initialize() {
        // The inner component of the modal
        this.formComponent = await getComponent(this.element.querySelector('[data-live-name-value="MyItemForm"]'));
    }

    connect() {
        this.element.addEventListener('show.bs.modal', (event) => {
            this.formComponent.set('item', event.relatedTarget.dataset.itemId);
        });
    }
}

I then added the data attribute for the ID to the buttons that open the modal: data-item-id="{{ item.id }}"

Please let me know if there are any better approaches.

smnandre commented 6 months ago

It seems very clean & reusable 👍

carsonbot commented 1 week ago

Thank you for this issue. There has not been a lot of activity here for a while. Has this been resolved?