Back to all demos
DEMO

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 87 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()]
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()
    {
        $this->isEditing = true;
    }

    #[LiveAction]
    public function save(EntityManagerInterface $entityManager)
    {
        // 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 %}
        <div class="alert alert-success">{{ flashMessage }}</div>
    {% endif %}

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

                    <div class="form-floating">
                        <input
                            type="text"
                            data-model="food.name"
                            class="form-control form-control-lg{{ error ? ' is-invalid' }}"
                            autofocus
                            id="food_name"
                        />
                        <label for="food_name">Food name</label>
                    </div>

                    <button
                        data-action="live#action"
                        data-action-name="prevent|save"
                        class="btn btn-outline btn-outline-{{ error ? 'danger' : 'secondary' }}"
                    >Save</button>

                    {% if this.getError('food.name') %}
                        <div class="invalid-feedback">{{ this.getError('food.name').message }}</div>
                    {% endif %}
                </div>
                <div class="form-text">Clear the field to trigger validation!</div>
            </form>
        {% else %}
            <h2>{{ food.name }}</h2>
            <button
                data-action="live#action"
                data-action-name="activateEditing"
                class="btn btn-link"
                title="Click to edit!"
            >
                <small class="fa fa-pencil"></small>
            </button>
        {% endif %}
    </div>

    <hr>
    <p>
        The <strong>{{ food.name }}</strong> has {{ food.votes }} votes! Yum!
    </p>
</div>