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
itzmidinesh
Hi all, I’m working on a Phoenix project where I need to make external HTTP requests and I’m trying to choose between Req and HTTPoison....
New
pragdave
How do I tell Hologram not to escape HTML when substituting into templates? {Textile.render(@product.long_description)} escapes the HTM...
New
leoschweizer
Hi, we run different LLM models across a cluster, with each node loading a configurable subset. Model-specific queues let nodes consume ...
New
rlipscombe
I’d like to create a file with 600 permissions (rw-------), but there’s a race condition if I create it and then change the permissions. ...
New
saleyn
Is there a way to import record definitions in the iex shell for pretty printing (similar to the rr command in the erl shell)? This woul...
New
rlipscombe
When I use Mix.install with a git dependency, using git@github.com:... how do I change the SSH identity used? I’ve got two Github accoun...
New

Other Trending Topics Top

dominicletz
Hey there, I haven’t seen a list of useful Elixir specific skills for the popular AI tools yet so I’m starting one here with my first. ...
New
raa-ziq
Hi everyone :waving_hand: I wanted to share something I’ve been building — NowBlind, a random chat app designed to help people discover ...
New
mudasobwa
I fully migrated to my own harness from Anthropic/Gemini and I think it’s time to share it. Welcome DSH, the DeepSeek Harness, fully writ...
New
KristerV
Hey. Is there anyone here who creates agents in their apps? Not talking about using agents, but creating them. I’m finding it pretty diff...
New
OndrejValenta
(I just needed to vent somewhere and LinkedIn is full of hope, or hype, I’m not sure which exactly) AI dream has many faces, but general...
New
kip
Localize is the next generation localisation library for Elixir. Think of it as ex_cldr version 3.0. The first version will be released ...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews