How to add JS loaded textarea with LiveView form?

Hopefully this helps out anyone in the future. Based on the tip found here. For my use case, what i did was:

  1. Set up a form in a live component.
def render(assigns) do
  ~L"""
  <div class="form_container">
    <%= f = form for @changeset, 
       #,
       phx_target: @myself,
       phx_change: "validate_form" ,
       id: "richtext_form"
    %>
      <div 
        phx-hook="RichText"
        phx-update="ignore"
        phx-target="<%= @myself %>"
        id="richtext_container"
      >
        <%= textarea f, 
          :the_content, 
          value: @clientside_richtext_input 
        %>
      </div>
    </form>
  </div>
  """
end 

Reason being is i still want to validate the form input on the server side. But the pushing the params after a validation function in the returning {:noreply, socket |> ...} kept on failing and the data being put into to the rich text wasn’t stored anywhere. Which led to:

  1. Create two handle events. One for the form validation, the other to handle the clientside hook and pass the data into an assign that will be set as the value for the textarea.
def handle_event(
  "validate_form", 
  %{"richtext_form" => params}, 
  socket) do 
  {:noreply, socket |> validate_form(params)}
end

def handle_event(
  "handle_clientside_richtext", 
  %{"richtext_data" => richtext_data},
  socket) do 
  {:noreply, 
    socket 
    |> assign_form(:clientside_richtext_input, richtext_data)
    |> push_event("richtext_event", %{richtext_data: richtext_data})}
end

This is so that the richtext data can be stored by cycling back and forth between the client and backend for when the live component is updated. Now for the same thing inside the JS code.

  1. In a hook object do
const RichText = {
  mounted(){
    init(this.el.querySelector("#richtext_form_the_content"))
  },
  updated(){
    const textArea = init(this.el.querySelector("#richtext_form_the_content"));
    textArea.codemirror.on("change", ( ) => {
      this.pushEventTo(
        this.el, 
        "handle_clientside_richtext",
        {richtext_data: textArea.value()}
      );
      this.handleEvent(
        "richtext_event",
        (richtext_data) => textArea.value(richtext_data)
      )
    })
  }
}

The logic is in the updated function since mounted rich text area gets erased right away.

  1. And finally to have easyMDE be usable do
import easyMDE from "easyMDE";

const init = (element) => new easyMDE({
  element: element,
  forceSync: true
})

The forceSync: true is for the data to be set as the textarea value on the clientside and play well with the backend.

It took a few days of looking back and forth at different examples, reading the forum, rereading the docs, looking at source code, and feeling dumb. This is the simplest solution that i can think of once it all clicked.

*edit typo fix: phx-update: "TextEditor" to phx-hook: "RichText"

1 Like