Phoenix_live_view: phx-change event does not fire for forms in tables

Created on 18 Mar 2019  Â·  2Comments  Â·  Source: phoenixframework/phoenix_live_view

Environment

  • Elixir version (elixir -v): 1.7.4 (Also duplicated with 1.8.1)
  • Phoenix version (mix deps): 1.4.2
  • NodeJS version (node -v): 11.3.0
  • NPM version (npm -v): 6.9.0
  • Operating system: macOSMojave 10.14.3
  • Browser: Chrome 72.0.3626.119

Expected behavior

When using a form that is embedded in a table like so:

    def render(assigns) do
      ~L"""
      <h2>Listing Things</h2>

      <table>
        <thead>
          <tr>
            <th>Name</th>
            <th>Number</th>
            <th></th>
          </tr>
        </thead>
        <tbody>
      <%= for thing <- @things do %>
          <tr>
            <td><%= thing.name %></td>
            <td><%= thing.number %></td>
            <td>
              <button phx-click="delete_thing" phx-value="<%= thing.id %>">Delete</button>
            </td>
          </tr>
      <% end %>
          <tr>
            <%= form_for @changeset, "#", [phx_change: :validate, phx_submit: :save_new_thing], fn f -> %>
              <td>
                <%= text_input f, :name %>
                <%= error_tag f, :name %>
              </td>
              <td>
                <%= text_input f, :number %>
                <%= error_tag f, :number %>
              </td>
              <td><%= submit "Save", phx_disable_with: "Saving..." %></td>
            <% end %>
          </tr>
        </tbody>
      </table>
      <button phx-click="new_empty_thing">New Thing</button>
      """
    end

I'd expect the "validate" event to fire such that it can be handled via

    def handle_event("validate", %{"thing" => params}, socket) do
      changeset =
        %Thing{}
        |> Thing.changeset(params)
        |> Map.put(:action, :insert)

      {:noreply, assign(socket, changeset: changeset)}
    end

or be hit by my handler for unhandled events:

    def handle_event(unhandled_event, payload, socket) do
      IO.inspect unhandled_event, label: "unhandled_event"
      {:noreply, socket}
    end

Actual behavior

No event is received by LiveView handle_event/3 function.

Other things of note:

I tried changing the render/1 function such that the form was not inside the table like so:

    def render(assigns) do
      ~L"""
      <h2>Listing Things</h2>

      <table>
        <thead>
          <tr>
            <th>Name</th>
            <th>Number</th>
            <th></th>
          </tr>
        </thead>
        <tbody>
      <%= for thing <- @things do %>
          <tr>
            <td><%= thing.name %></td>
            <td><%= thing.number %></td>
            <td>
              <button phx-click="delete_thing" phx-value="<%= thing.id %>">Delete</button>
            </td>
          </tr>
      <% end %>
          <tr>
          </tr>
        </tbody>
      </table>
      <button phx-click="new_empty_thing">New Thing</button>
      <%= form_for @changeset, "#", [phx_change: :validate, phx_submit: :save_new_thing], fn f -> %>

        <%= label f, :name %>
        <%= text_input f, :name %>
        <%= error_tag f, :name %>

        <%= label f, :number %>
        <%= text_input f, :number %>
        <%= error_tag f, :number %>

        <%= submit "Save", phx_disable_with: "Saving..." %>
      <% end %>
      """
    end

This results in the validation event properly firing and being handled as I'd expect.

The phx-submit event does properly fire when the form is inside of the table, resulting in a Thing record being created.

When the phx-submit event fires but there is a validation error, the changeset properly updates and the error validations appear when the form is in the table.

The handler for save looks like so:

    def handle_event("save_new_thing", %{"thing" => params}, socket) do
      case Validation.create_thing(params) do
        {:ok, thing} ->
          notify_subscribers(:save)
          all_things = fetch_all_things()
          changeset = Validation.change_thing()
          {:noreply, assign(socket, things: all_things, changeset: changeset)}

        {:error, %Ecto.Changeset{} = changeset} ->
          {:noreply, assign(socket, changeset: changeset)}
      end
    end

Most helpful comment

This appears to be an issue with invalid HTML. The browser is rewriting the HTML for the cells to be outside the form, and thus the inputs as well:

In this case, you need to generate the form inside the table cell or avoid the table. I don't believe there's anything we can do in this case.

All 2 comments

Hi @mbramson, can you please push this to a sample app on GitHub? It will make things much easier for us to reproduce and fix the error. Thank you.

This appears to be an issue with invalid HTML. The browser is rewriting the HTML for the cells to be outside the form, and thus the inputs as well:

In this case, you need to generate the form inside the table cell or avoid the table. I don't believe there's anything we can do in this case.

Was this page helpful?
0 / 5 - 0 ratings