Can LiveView phx-click send form params also?

I have some links on a form that when clicked, clear some form inputs:

<%= link "clear", to: "#", "phx-click": "clear-time", "phx-value-pos": time_form.index %>

Is there a way to send all the form params in addition to just that phx-value-pos? It would make the logic easier, in that I could just update the params to remove the item and send it through the changeset like normal.

Currently I have to check to see if params exist in changeset first:

if socket.assigns.changeset.params["times"] do
  ...
else
  ...
end

Because the user could click the link without doing anything else to the form first, thus the changeset hasn’t been casted with any params yet.

Or is there a better way of doing this? Thanks for the help!

1 Like

Doesn’t it have to be a submit button to send everything?
If certain inputs are required can you disable the submit button until they’re not empty. You could do the clearing of the time input in the front-end. Both of these options could be achieved using alpinejs.

I think you’ll want to use the LiveView form bindings https://hexdocs.pm/phoenix_live_view/form-bindings.html

With either the phx_change or phx_submit all params will be sent over to the event.

I’m not sure what you mean, but what I’m doing seems so cumbersome for something seemingly so simple, that I feel like I’m missing something. Let me give some more info…

Here’s my link html:

<%= link "clear", to: "#", "phx-click": "clear-time", "phx-value-id": time_form.index + 1 %>

Here’s what my form looks like visually:
form

Here’s my event handler:

  def handle_event("clear-time", args, socket) do
    %{ "id" => id } = args

    params = %{
      "times" => %{
        "1" => %{"id" => "1"},
        "2" => %{"id" => "2"}
      }
    }
    params = update_in(params["times"][id], fn time ->
      Map.merge(time, %{"start_time" => "", "end_time" => ""})
    end)

    changeset = socket.assigns.changeset
    |> Ecto.Changeset.cast(params, [])
    |> Ecto.Changeset.cast_embed(:times, with: &TimedNode.Changeset.create_times/2)
    |> TimedNode.Changeset.validate_times()

    socket = assign(socket, :changeset, changeset)

    {:noreply, socket}
  end

Which only kind of works. I get duplicate validation errors click on the clear link twice, among other weird issues regarding state getting out of whack.

Contrast that to how trivial it would be if I had all the form params in the event handler:

  def handle_event("clear-time", args, socket) do
    %{ "id" => id, "params" => params } = args

    params = put_in(params["times"][id]["start_time"], nil)
    params = put_in(params["times"][id]["end_time"], nil)

    changeset = TimedNode.Changeset.update(socket.assigns.timed_node, params)
    socket = assign(socket, :changeset, changeset)

    {:noreply, socket}
  end

In other words, I could just create a brand new changeset with all the params, rather than trying to update my changeset somehow.

Thanks for the help.

Let’s say you have a form like this:

<%= f = form_for @changeset, "#", [phx_submit: :clear_time] %>
  <%= label f, :start_time %>
  <%= text_input f, :start_time %>
  <%= error_tag f, :start_time %>

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

  <%= submit "Clear" %>
</form>

You could then have this callback - and all form fields will be available in the params.

def handle_event("clear_time", params, socket) do
  #your form fields will be available in the params
  ...
  {:noreply, socket}
end

I hope this makes sense and helps.

1 Like

Here’s what I ended up doing…

def mount(params, _session, socket) do
  record = Repo.get(TimedNode, params["id"])
  params = record_to_params(timed_node)
  changeset = TimedNode.Changeset.new(record, %{})

  socket = socket
  |> assign(:record, record)
  |> assign(:params, params)
  |> assign(:changeset, changeset)

  {:ok, socket}
end

def handle_event("validate", %{"timed_node" => params}, socket) do
  %{ record: record } = socket.assigns
  changeset = TimedNode.Changeset.new(record, params)

  socket = socket
  |> assign(:changeset, changeset)
  |> assign(:params, params)

  {:noreply, socket}
end

def handle_event("clear-time", args, socket) do
  %{ "id" => id } = args
  %{ params: params, record: record } = socket.assigns

  # Clear the given field
  params = put_in(params["times"][id]["start_time"], nil)
  params = put_in(params["times"][id]["end_time"], nil)

  changeset = TimedNode.Changeset.new(record, params)

  socket = socket
  |> assign(:changeset, changeset)
  |> assign(:params, params)

  {:noreply, socket}
end

The trick is storing the params in the assigns. On mount, I have to make the params myself from my record. On validate, LiveView is sending me the params from the form, so I can just use that and store it back in the assigns. The “clear-time” handler always has a full params map to work with and can clear out whatever fields it needs to, then creates a fresh changeset from the params to send to the template and form_for. Simple.

I just wish LiveView or maybe Ecto had better support for doing this, since it seems having buttons to clear form inputs is a common UI paradigm. Yes, it’s easy with JS, but I’m using LV because I don’t want to use JS.