quinten-kamphuis

quinten-kamphuis

Hi,

I’m running into an issue with a form inside of my rich editor live component. For the editor I have a button that opens a modal with a link insertion form. The problem is that the editor itself is also rendered inside a form of course. This causes issues and is invalid html right?

First I tried not having a form in the modal but saving the fields values on change in the assigns of the editor component. But pressing enter would still submit the parent form. I experimented with javascript to prevent the enter event but this felt too hacky.

Then I tried moving or “portalling” the modal component to the so that it’s form is no longer part of the form the editor is part of. But I could not get this to work, live view could no longer perform any updates and I believe it lost track of the modal since it was no longer in the same place. I also tried teleport with Alpine.js but I had similar issues here.

Hoping anybody knows a proper solution to handling nested form structures. Or proper live view portalling that would solve this.

Thanks!

Showing Posts 1 to 8

gushonorato

gushonorato

This is not a Phoenix/LiveView issue; the HTML5 standard prohibits nesting <form> tags. My first choice to address this would be to find another rich text editor that doesn’t generate <form> tags inside modals. If that’s not an option, could you please provide more details about the rich text editor you’re using and the structure of the parent (LiveView) form?

quinten-kamphuis

quinten-kamphuis OP

Hi, Thank you for helping out!

I created the rich editor myself, the problem is not necessarily about the editor, I should’ve been clearer about that. The issue is that I have a modal with a form inside a live component and that live component lives in another form. I can’t really lift the modal portion out. That’s why I tried moving or portalling or teleporting the modal to the body element but this caused issues with live view updates.

I could be wrong, but I feel like this is a fundamental framework issue. Other frameworks and even Alpine.js support simple portal or teleport operations and with react this would be even easier. Not to bash on Phoenix, it’s best, I love it, but if you’re deeply nested in a component there should be a way to render stuff elsewhere to prevent collision or invalid HTML while live view keeps working correctly.

I recreated my situation in a very basic manner to outline the problem better:

defmodule MyAppWeb.EmailEditorLive do
  use MyAppWeb, :live_view
  
  def mount(_params, _session, socket) do
    changeset = %{email_subject: "", email_body: ""}
    
    {:ok, 
     socket
     |> assign(:form, to_form(changeset))}
  end

  def render(assigns) do
    ~H"""
    <div class="container mx-auto">
      <h1>Email Editor</h1>
      
      <.form for={@form} phx-submit="save">
        <div class="space-y-4">
          <div>
            <.input field={@form[:email_subject]} label="Subject" />
          </div>
          
          <.live_component
            module={MyAppWeb.RichEditorComponent}
            id="rich-editor"
            field={@form[:email_body]}
          />
          
          <div>
            <.button type="submit">Save Email</.button>
          </div>
        </div>
      </.form>
    </div>
    """
  end
  
  def handle_event("save", %{"email" => params}, socket) do
    # Handle the main form submission
    {:noreply, socket}
  end
end

defmodule MyAppWeb.RichEditorComponent do
  use MyAppWeb, :live_component
  
  def mount(socket) do
    {:ok, 
     socket
     |> assign(:show_link_modal, false)
     |> assign(:link_form, to_form(%{"url" => "", "text" => ""}))}
  end
  
  def render(assigns) do
    ~H"""
    <div>
      <div class="border p-2">
        <div class="flex gap-2">
          <button type="button" class="p-1 border rounded">Bold</button>
          <button type="button" class="p-1 border rounded">Italic</button>
          <button 
            type="button" 
            class="p-1 border rounded"
            phx-click="show_link_modal"
            phx-target={@myself}
          >
            Add Link
          </button>
        </div>
        
        <div class="mt-2">
          <textarea 
            name={@field.name}
            class="w-full h-40 border rounded"
            value={@field.value}
          ></textarea>
        </div>
      </div>

      <.modal :if={@show_link_modal} id="link-modal" show>
        <.form for={@link_form} phx-submit="save_link" phx-target={@myself}>
          <div class="space-y-4">
            <div>
              <.input field={@link_form[:text]} label="Link Text" />
            </div>
            <div>
              <.input field={@link_form[:url]} label="URL" />
            </div>
            <div class="flex justify-end gap-2">
              <.button 
                type="button"
                phx-click="cancel_link"
                phx-target={@myself}
              >
                Cancel
              </.button>
              <.button type="submit">
                Insert Link
              </.button>
            </div>
          </div>
        </.form>
      </.modal>
    </div>
    """
  end
  
  def handle_event("show_link_modal", _, socket) do
    {:noreply, assign(socket, :show_link_modal, true)}
  end
  
  def handle_event("cancel_link", _, socket) do
    {:noreply, assign(socket, :show_link_modal, false)}
  end
  
  def handle_event("save_link", %{"link" => params}, socket) do
    # Handle the link form submission
    {:noreply, 
     socket
     |> assign(:show_link_modal, false)}
  end
end
cmo

cmo

You can split the modal into its own component and put it after the other form in the HTML.

There is talk of portals in the liveview issue tracker, if you want to give your 2c.

quinten-kamphuis

quinten-kamphuis OP

Yes, agreed. Although I would need PubSub or a hook for the modal to talk to the editor. And it does not make sense for the parent component to have a link insertion modal in it, they belong in the editor since they’re tightly coupled.

This is basically the best option for now right? Portals would probably solve this so I’ll take a look at that issue. Thanks for providing your insights!

cmo

cmo

I don’t see why showing/hiding that modal or adding a link to the text should involve the backend. I would use a hook for the link adder.

You can look into using a self closing form tags for the outer form so that the editor form id not nested inside another.

quinten-kamphuis

quinten-kamphuis OP

It’s not a question of server vs client side right? In both cases we would end up with either a nested form or highly controlled input fields inside of the wrong form.

And totally don’t understand what you mean with

You can look into using a self closing form tags for the outer form so that the editor form id not nested inside another.

Forms don’t have self-closing tags according to my knowledge.

cmo

cmo

I was speaking a little off topic. Every time you send an event to the backend to change the visibility of an element, a latency fairy dies.

Sorry, empty form tags, not self closing ones.

quinten-kamphuis

quinten-kamphuis OP

Right, pretty clever! But it would make the editor no longer self-contained. Not the biggest issue since I only use it in one place, so this will solve my issue for now. It doesn’t solve the bigger problem though, we should just be able to have self contained components with forms in phoenix without them clashing with the form they’re rendered in, either with portals or something else, right?

— All posts loaded —

Where Next? Top

Trending in Questions Top

stjefim
Hello! Suppose you are building workflow (order / task / payment) processing system with the following requirements: Each workflow con...
New
jonnycharles
I’m in search of an Elixir library that offers PDF generation capabilities similar to Ruby’s Prawn. While there have been discussions abo...
New
Blokh
Hey guys, I’ve got a huge CSV ( around 10 GB ) that needs to be processed hourly Do you guys have any suggestions what is the best prac...
New
roeland
Kia ora, We have been using elixir-google-api to connect to Google Drive. However, with the updates to Tesla due to CVEs this is now bro...
New
kszambelanczyk
Hello! Could someone please give me a help/sample code, how to delete a file from s3 using waffle/waffle_ecto from Phoenix app. I creat...
New
subsaharancoder
I’ve followed the Phoenix LiveView file upload code here Uploads — Phoenix LiveView v1.0.0-rc.7 and so far everything works just fine wit...
New
Onor.io
I have what I’ve heard referred to as a “lookup table” in my database. This is a way of assigning codes to common values. One common lo...
New

Other Trending Topics Top

JesseHerrick
Hey, I’m Jesse and I’m the main contributor behind Dexter, a full-featured, lightning-fast Elixir LSP optimized for large codebases. It s...
New
garrison
Hobbes is a low-level distributed database for the Elixir programming language. Hobbes provides a simple, safe, and scalable storage lay...
New
jimsynz
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
mcass19
ExRatatui lets you cook up rich terminal UIs in Elixir, powered by Rust’s ratatui via Rustler NIFs. Build interactive terminal applicatio...
New
netoum
Corex is an accessible, unstyled UI component library for Phoenix that integrates Zag.js state machines using Vanilla JavaScript and Live...
New
Damirados
Hello everyone. After busy few months I am happy to announce v0.1.0 of Emerge &amp; Solve. They are GUI (Emerge) and State management (S...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews