DEMOS / Live Component

Inline Editing

Inline editing? Simple. Use LiveComponents to track if you're in "edit" mode, let the user update any fields on your entity, and save through a LiveAction.

Banana 🍌


The Banana 🍌 has 27 votes! Yum!

// ... use statements hidden - click to show
use App\Entity\Food;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\UX\LiveComponent\Attribute\AsLiveComponent;
use Symfony\UX\LiveComponent\Attribute\LiveAction;
use Symfony\UX\LiveComponent\Attribute\LiveProp;
use Symfony\UX\LiveComponent\DefaultActionTrait;
use Symfony\UX\LiveComponent\ValidatableComponentTrait;

#[AsLiveComponent]
final class InlineEditFood extends AbstractController
{
    use DefaultActionTrait;
    use ValidatableComponentTrait;

    /** This allows us to have a data-model="food.name" */
    #[LiveProp(writable: ['name'])]
    /** When we validate, we want to also validate the Food object */
    #[Assert\Valid]
    public Food $food;

    /** Tracks whether the component is in "edit" mode or not */
    #[LiveProp]
    public bool $isEditing = false;

    /**
     * A temporary message to show to the user.
     *
     * This is purposely not a LiveProp: this is a "temporary" value that
     * will only show one time.
     */
    public ?string $flashMessage = null;

    #[LiveAction]
    public function activateEditing(): void
    {
        $this->isEditing = true;
    }

    #[LiveAction]
    public function save(EntityManagerInterface $entityManager): void
    {
        // if validation fails, this throws an exception & the component re-renders
        $this->validate();

        $this->isEditing = false;
        $this->flashMessage = 'Saved! Well, not actually because this is a demo (if you refresh, the value will go back).';

        // in a real app, we would save!
        // $entityManager->flush();
    }
}
<div {{ attributes }}>
    {% if flashMessage %}
        <twig:Alert variant="success" class="mb-4">{{ flashMessage }}</twig:Alert>
    {% endif %}

    <div class="inline-flex">
        {% if isEditing %}
            {# The form isn't used, but allows the user to hit enter to save. #}
            <form class="flex flex-col gap-4">
                <div class="mb-4">
                    {% set error = this.getError('food.name') %}

                    <twig:Form:Label for="food_name">Food name</twig:Form:Label>
                    <div class="flex gap-2">
                        <twig:Form:Input type="text" id="food_name" data-model="food.name" hasError="{{ error is not null }}" class="py-2" autofocus />
                        <twig:Button
                            variant="outline"
                            class="shrink-0 {{ error ? 'border-signal-red text-signal-red' }}"
                            data-action="live#action:prevent"
                            data-live-action-param="save"
                        >Save</twig:Button>
                    </div>

                    <twig:Form:Errors errors="{{ error ? error.message : null }}" />
                </div>
                <div class="text-sm text-muted">Clear the field to trigger validation!</div>
            </form>
        {% else %}
            <h2>{{ food.name }}</h2>
            <twig:Button
                variant="link"
                data-action="live#action"
                data-live-action-param="activateEditing"
                title="Click to edit!"
            >
                <twig:ux:icon name="pencil" />
            </twig:Button>
        {% endif %}
    </div>

    <hr>
    <p>
        The <strong>{{ food.name }}</strong> has {{ food.votes }} votes! Yum!
    </p>
</div>
Author weaverryan
Published 2023-02-21