Add ID of html input element to :phx_change event?

Greetings,

I have a LiveView form that has 6 inputs, 2 of which are markdown inputs. It works well. However, it is doing significant extra processing that might be avoided if the input ID was delivered with the form’s phx_change event; specifically, in my handler, I could skip markdown transformation if the user is not currently changing a markdown input.

I.e., something like…

  def handle_event("validate", %{"mystruct" => mystruct} = data, socket) do
    if (data["input_id"] != "mystruct_body") do
      changeset = Mystruct.changeset(%Mystruct{}, mystruct)
      {:noreply, assign(socket, changeset: changeset)}
    else
      html_body =  Earmark.as_html!(mystruct["body"])
      changeset = Mystruct.changeset(%Mystruct{}, mystruct)
      {:noreply, assign(socket, changeset: changeset, html_body: html_body)}
    end
  end

I am wondering if it would wise to have the framework deliver the html ID of the input element with every :phx_change event. It seems like there might be other uses for this.

Thanks,
David Alm

Could you just add hidden elements to the form that have the same values? This sounds a lot like handling checkboxes, for example, for which that works ok.

If I understand what you’re saying, the purpose of the hidden input would be to hold the value of the input id that has focus. But how would I set the value of that input (without writing javascript)? Javascript would have to monitor focus or keyup to get the current input element.

Basically, what I was suggesting is that the liveview javascript might be able to easily grab the id the current input and send it with :phx_change.

The way I interpreted it was to duplicate each value in the form - the hidden value wouldn’t get changed. Then you could simply compare the exposed values to the hidden values to determine which input changed.


Update: If you can access the original struct (in the socket assign perhaps) that was used to populate the form you can leverage changeset to tell you which fields have changed.

defmodule User do
  use Ecto.Schema

  @primary_key false
  embedded_schema do
    field(:username, :string)
    field(:email, :string)
  end
end

defmodule Playground do
  import Ecto.Changeset

  def play do
    user = %User{username: "john", email: "john@example.com"}

    params =
      user
      |> Map.from_struct()
      |> Map.put(:email, "john@gmail.com")

    changeset = cast(user, params, [:username, :email])
    fields_changed = Map.keys(changeset.changes)

    IO.puts(inspect(user))
    IO.puts(inspect(params))
    IO.puts(inspect(fields_changed))
  end
end
%User{email: "john@example.com", username: "john"}
%{email: "john@gmail.com", username: "john"}
[:email]
1 Like

I really just recalled how a separate element is needed to actually send the value of a checkbox and thought this might be similar. I was not thinking about the fact that the original post is concerned with a change event, not a submit event. Comparing is a sensible thing to attempt that I won’t claim to have thought of.

I realize that this may not be helpful, but the drab library does what the original post suggested - each event includes with it a map of the attributes of the sending element. In some ways the fact that Live View doesn’t do this makes you set up better boundaries (in Drab I sometimes did dumb things like setting class attributes that only existed to pass data to event handlers). That said it is a nice feature for cases like this.

1 Like

This is in the works. All events will send a map containing metadata about the event. For phx-chagne, one such metadata will be the input that triggered the event. Keep an eye on the issue tracker for when it lands.

4 Likes

Thanks everyone. Good stuff.