I am trying to have the server update the database whenever the div on the client is changed. I think the code I have tried does not work because phx-change only works on forms.
I have read through Phoenix.LiveView.JS, and was thinking of using key events events while monitoring whether or not the content of the div has changed. However, that seems overly complex, and I may be overlooking some capabilities of Phoenix and LiveView.
Where else should look to figure out how to accomplish this?
def handle_event("update", _, socket) do
# update database with new content
{:noreply, socket}
end
def render(assigns) do
~H"""
<div contenteditable="true" phx-change="update">
word
</td>
"""
end
If you are doing rich text editing it is better to consider solutions such as CKEditor. If you only need plain text you can dress your text area up with CSS. Proper editing and two way synchronisation of contenteditable is very difficult. Hope this helps.
CKEditor looks like it would work if I was creating a professional product. But I am doing this as a personal project to learn how to implement something like that.
Taking your requirements at face value, you could create a JS Hook where on mounted you hook up Mutation Observer to your dom and send events to the server as needed.
Careful! Input elements with name="id" in live view forms can cause some very annoying bugs, because that causes the .id attribute of the form element to hold the DOM element of the input with that name rather than id attribute value of the form. See for instance this thread. The solution is to use a name other than id for your inputāin this case, perhaps it could be name="list-id".
I built an inline form for updating one field today and I wanted to toggle between displaying the field and editing the field by simply clicking on it. So, Iāve built this little thing which toggles between the form and a <p>-element when the user clicks on it:
alias Phoenix.LiveView.JS
def render(assigns) do
~H"""
<p
id={"name-#{@element.id}"}
# The group element means that the edit icon is shown
# when a user hovers over the group.
class="group hover:cursor-pointer flex items-center space-x-2"
phx-click={toggle_name_divs(@element.id)}
>
<span><%= @element.name %></span>
<.icon name="hero-pencil-solid" class="h-4 w-4 hidden group-hover:block" />
</p>
<form
id={"form-#{@element.id}"}
phx-submit="update_name"
phx-target={@myself}
class="hidden items-center space-x-2"
>
<input
type="text"
name="name"
value={@element.name}
autocomplete="off"
/>
<.button phx-click={toggle_name_divs(@element.id)}>
<.icon name="hero-check" />
</.button>
</form>
"""
end
def toggle_name_divs(element_id) do
JS.toggle(to: "#name-#{element_id}", display: "flex")
|> JS.toggle(to: "#form-#{element_id}", display: "flex")
end
def handle_event("update_name", %{"name" => name}, socket) do
# Update and assign your element here
end
So, if a user hovers over the name display, an pencil icon shows. If the user clicks on the group, it disappears and the form appears. If the user saves the form, the form disappears and the name display appears again, but it also sends an āupdate_nameā-event to the server. Hope it helps!
My initial implementation looked quite a bit like this but the JS.toggleing would execute before the server had a chance to push the updated values back over the socket and that created a bit of a FOUC for me.
The value would ārevertā to what the <p> element held before the update until the server would paste over the new value 1/10th of a second later. At that point I started thinking about optimistic client updates and edge cases etc. - but then I took a step back to reassess.
In the end I moved the toggling bit to the server so the actions are always queued correctly - first the value is updated in the backend and then the UI is updated to reflect the new value