Tutorial
In place edit with LiveView
A very common practice in web applications these days is to allow for inline editing of content. This is especially true when the value is a text field and contains like a name or title. You can see this pattern if you use something like Trello or Linear.
Instead of using a form input, you can use the HTML attribute contenteditable
. That can make any element that has text editable. The upside is that you dont break your normal styling and the page will look like the content that you edit, which can reduce the visual clutter of traditional form elements.
The downside is that inputs has a data validations built in so if you need data validation, you probably will be better of with plain forms.
In this tutorial, I will go though how to use contenteditable
in Phoenix LiveView to achieve the pretty inline editing.
Creating a reusable LiveView LiveComponent
Our journey begins with the construction of a reusable in-place edit component, designed as a Phoenix LiveComponent. This section will detail the component's architecture, emphasizing its flexibility for use in various application contexts. We aim to construct a versatile component that transforms standard elements into editable fields upon user interaction, thereby enhancing the overall user experience.
defmodule TutorialWeb.InPlaceEditComponent do
use Phoenix.LiveComponent
use Phoenix.Component
attr :id, :string, required: true
attr :content, :string, required: true
attr :on_save, :any, default: nil
attr :rest, :global
def editable(assigns) do
~H"""
<.live_component module={__MODULE__} id={@id} content={@content} on_save={@on_save} rest={@rest} />
"""
end
def render(assigns) do
~H"""
<div
id={@id}
phx-hook="InPlaceEdit"
phx-keydown="cancel"
phx-key="escape"
phx-target={@myself}
contenteditable="true"
{@rest}
>
<%= @content %>
</div>
"""
end
def update(assigns, socket) do
{
:ok,
socket
|> assign(assigns)
}
end
def handle_event("update", %{"content" => content}, socket) do
case socket.assigns.on_save do
{%{} = record, attr} -> send(self(), {record, %{attr => content}})
_ -> nil
end
{:noreply, socket}
end
def handle_event("cancel", _, socket) do
{:noreply, push_event(socket, "cancel", %{content: socket.assigns.content})}
end
end
Implementing JavaScript Hooks for LiveView Interactions
There are some thing that the LiveView can do by itself. Or at least as fair as I know now. It does not track the changes of the content that you are editing. That means, when saving it, we need to check what the element contains, and push that to the LiveView. For this purpose, I am setting up a LiveView javascript hook so I can easily communicate between.
phx-hook="InLineEdit"
The hook is pretty simple. First of all, It will listen to blur-events. Blur means that I am done with the element and click away. That will then push the content to the LiveComponent. Note that I used this.pushEventTo
so I can send it back to the LiveComponent instead of the LiveView.
// Add this amongst the other LiveView Hooks
Hooks.InPlaceEdit = {
mounted() {
this.el.addEventListener('blur', event => {
this.pushEventTo(this.el, 'update', {content: this.el.innerText})
})
this.handleEvent('cancel', data => {
this.el.innerText = data.content
this.el.blur()
})
}
}
Start using the LiveComponent in your application
With our in-place edit component and JavaScript hooks prepared, we're set to integrate them into a Phoenix application. This section provides a detailed walkthrough for embedding the LiveComponent into a LiveView, such as a user profile or company details page. We'll guide you through transforming static content into dynamic, editable fields, effectively bringing your application to vibrant life.
# Add this LiveView callback
def handle_info({%Companies.Company{} = company, attrs}, socket) do
case Companies.update_company(company, attrs) do
{:ok, company} ->
{:noreply, assign(socket, company: company)}
{:error, _} ->
{:noreply, socket}
end
end
<!-- Replace -->
<:item title="Name"><%= @company.address %></:item>
<!-- With -->
<:item title="Name">
<.editable id="company-name" content={@company.name} on_save={{@company, :name}} />
</:item>
Ensuring Smooth Operation: Testing and Debugging
The successful integration of the in-place edit feature sets the stage for rigorous testing and debugging. This section sheds light on effective strategies to test your LiveView components, ensuring they operate flawlessly. We'll also delve into debugging techniques to identify and resolve any issues, ensuring a polished and reliable in-place editing feature.
Conclusion
This tutorial has navigated the intricacies of creating a dynamic, in-place edit feature using Phoenix LiveView. By embracing the power of LiveComponents and JavaScript hooks, we've unlocked new possibilities for enhancing user experiences in Phoenix applications. As you continue to explore and innovate with Phoenix LiveView, consider the vast potential of in-place editing and other real-time features to elevate your applications to new heights of interactivity and engagement.