LiveView -- passing values from the front-end

Hi,

I have a typical LiveView render function:

def render(assigns) do
~L"""
  <div id="click_this_to_update" phx-click="search_fillter" phx-value="" >
    ...
  </div>
"""

def handle_event("search_filter", phx_value, socket) do
 ...
end

I would like to pass multiple parameters from the front-end (in my particular case, dates from a date picker component, although it could be from other UI components, but not from a form). How could I do that without using a form?

I’ve thought about just calling a JS function and updating the phx-value attribute with stringified json that contains all the needed data, and then triggering the phx-click event (or any other event), but is there an easier way somehow using params (similar with forms)?

function click_the_liveview() {
  // set phx-value
  document.getElementById('click_this_to_update').setAttribute('phx-value', "{key1: 'value1', key2: 'value2'");

  // trigger the click
  document.getElementById('click_this_to_update').click();
}

Thanks for any guidance on this.

I’ve just been using a search form and use the phx-submit event on the form to run the search. To do this, you need a form-ready data structure (i.e something that implements Phoenix.HTML.FormData protocol) - I used an Ecto embedded schema - and then use the form content to build the search query. Most essential parts of the code are below.

Template (index.html.leex)

<%= if !@search_hidden do %>
  <section class="section">
    <%= form_for @searchset, "", [phx_submit: :search_events], fn f -> %>

    <%= input f, :from_date %>
    <%= input f, :to_date %>
    <%= input f, :reported_by %>
    <%= input f, :searchtext %>

    <div class="buttons">
      <%= submit "Search", phx_disable_with: "Searching...", class: "button" %>
    </div>

    <% end %>
  </section>
<% end %>

# Render search results here (in my case, @events contains the search results)

Embedded ecto schema representing the search criteria so that the form “just works”

defmodule ThingWeb.EventSearch
  use Ecto.Schema
  import Ecto.Changeset

  embedded_schema do
    field :from_date, :date
    field :to_date, :date
    field :reported_by, :string
    field :searchtext, :string
  end

  def changeset(eventsearch, attrs \\ %{}) do
    eventsearch
    |> cast(attrs, [:from_date, :to_date, :reported_by, :searchtext])
  end
end

and then in my liveview controller it’s something along the lines of:

  def mount(_session, socket) do
  # Create default search params when mounting the liveview
    searchset =
      EventSearch.changeset(%EventSearch{})
      |> Ecto.Changeset.put_change(:from_date, Date.add(Date.utc_today, -180))
      |> Ecto.Changeset.put_change(:to_date, Date.utc_today)

  # Run the search for the default search parameters - you may not want to do this bit until the use clicks search
    {:ok, fetch(socket, searchset)}
  end

  # Render using the index.html.leex template above 
  def render(assigns), do: EventView.render("index.html", assigns)

  # Handle the phx-submit event (index.html.leex maps phx-submit to "search_events")
  def handle_event("search_events", %{"event_search" => params}, socket) do
    # Cast form params into nice struct
    searchset =
      %EventSearch{}
      |> EventSearch.changeset(params)

    {:noreply, fetch(socket, searchset)}
  end

  # Run the search, populate the @events assign and "remember" the search params so we can redraw the search form
  defp fetch(socket, searchset) do
    assign(socket, events: search_events(searchset), searchset: searchset)
  end

 # The actual search logic - takes the ecto embedded schema with the search params, generates
 # the search query and returns the query results
  defp search_events(searchset) do
   # Can't remember why I did this but it's probably important
    eventsearch = Ecto.Changeset.apply_changes(searchset)

   # Create your query however you need to
    query = from e in Event,
     select: e

    query = query
    |> add_date_range_search(eventsearch)
    |> add_reported_by_search(eventsearch)
    |> add_searchtext(eventsearch)

    # Return the search results
    Thing.Repo.all(query)
  end

I think that’s about it… I’m sure others have better ways to do liveview based searches, but this works well for me.

Thanks, yes this works using the form_for construct - I was wondering if there was an alternative to using forms and/or schema?

I have another similar screen just using a form (no schema) and wire up the phx-change event. Is there any reason you don’t want a form? This responds any time a change is made to any of the controls on the form.

Template:

<form phx-change="search_change">
...
  <input id="data_min" class="input" name="data_min" type="text" value="<%= @data_min %>" >
...
  <input id="data_max" class="input" name="data_max" type="text" value="<%= @data_max %>" >
</form>

Live Controller:

def mount(_params, socket) do
 {:ok, assign(socket, data_min: 0, data_max: 10)}
end

def handle_event("search_change", %{"data_min" => data_min, "data_max" => data_max})
 # do search stuff with data_min & data_max
end

Thanks - I guess what I have in mind is essentially using a form as a method to collect the data anyway.

In a form, the phx-change does not respond to javascript updating any of the controls’ value - do you know if there is another event that does?

Have you found a solution for this? :thinking: I have exactly the same problem - I update a textarea from JS code, and it doesn’t trigger phx-change on form in which the textarea is rendered.

You could probably trigger a change event from the javascript, which edits the value.

Yeah, I though about it but it looked too cumbersome… until I found there is a first-class support for JS interop now! So if anyone has a similar problem, I’d recommend to go this way - worked just fine for me.

1 Like