How to use select/option to dynamically change page context

Basic question here (I’m just starting with LiveView and relative Phoenix newbie, too). :confused:

I’d like to build a page that dynamically reloads context when the user chooses a new option from a select list.

Functionally, like having a menu of links on the left, picking one, and the page redraws. But, instead of a long list of items, just a select input.

My problem is getting the a handle_event() on change of the select. (Key point, I don’t want the user to pick an item and them hit a "submit” button… idea is they just pick a new item in the popup list, and the page dynamically changes).

Here’s one (of many) variations I’ve tried, the problem with this one is that it doesn’t send the team ID to handle_event() (I realize this is a bit of a mess right, but… just my latest experiment):

<.form id="navigation" action={~p"/log"} method="post">
  <select :if={@teams} class="select select-info" phx-change="navigate">
    <option disabled selected>Pick a team</option>
    <option :for={team <- @teams} value={team.id} phx-value-id={team.id}>
      {team.name}
    </option>
  </select>
</.form>

Obviously, it’s for picking different teams… them the page updates, with various team details.

The above doesn’t work because I don’t get the value I need, team.id, instead I get this in the handler:

[debug] HANDLE EVENT "navigate" in WasteWalkWeb.LogLive
  Parameters: %{"_target" => ["undefined"]}

What I’m shooting for is a simple handle_event("navigate", %{"id" => id}, socket) function.

Doing this with a simple list of value works, but trying to move it into a select has me stumped. Thanks.

Try adding a name attribute to the select.

You would add a phx-validate binding as described here: Form bindings — Phoenix LiveView v1.1.8

Then in your live view module you add:

def handle_event("validate", %{"team" => params}, socket) do
  team_id = params["team_id"]
  {:noreply, assign(socket, form: form)}
end

If you don’t need the form backed by a schema/changeset you can just define a map with the fields and declate you form assign like this ( Phoenix.Component — Phoenix LiveView v1.1.8 )

assign(socket, form: to_form(%{team_id: ""}, as: :team))

So in the end your form component looks like this:

<.form for={@form} phx-validate="validate">

Thank you… that was it! :+1:

Thank you, that was helpful. I changed things up using a “validate” call.

I still feel like the “final” implementation here is a bit odd looking:

<!-- HEEx -->
<.form for={@team_select}>
  <select name="team_id" :if={@teams} class="select select-info" phx-change="select_team">
    <option disabled selected>Pick a team</option>
    <option :for={team <- @teams} value={team.id} phx-value-id={team.id}>{team.name}</option>
  </select>
</.form>

# handler
def handle_event("select_team", %{"team_id" => team_id}, socket) do
  {:ok, team} = WasteWalk.Teams.get_by_id(team_id, actor: socket.assigns.current_user)
  {:noreply, assign(socket, :team, team)}
end

I’m still figuring out the core components.

Is it odd that I’m mixing <.form/> and native HTML <select> elements? I had expected something more like this, but I’m not clear on how this would work with select and option

<.form for={@team_select} phx-validate="validate"}
  <.input_for :let={@teams} ...>
    <.input phx-value-id={team.id}>{team.name}</.input>
  </.input>
</.form>
<.form for={@team_select} phx-change="select_team">
  <.input 
    field={@team_select["team_id"]} 
    class="select select-info" 
    type="select" 
    prompt="Pick a team" 
    options={Enum.map(@teams, &{&1.name, &1.id})}
  />
</.form>

Thanks @LostKobrakai, very helpful. Your version is responsive (for some reason, using phx-validate doesn’t seem to work… thought it would?), but minor tweak gets us there: ended up tweaking syntax on field.

Here’s the final impl (also renamed “team_select” to “team_form” for clarity):

<.form for={@team_form} phx-change="select_team">
  <.input 
    field={@team_form["id"]} 
    class="select select-info" 
    type="select" 
    prompt="Pick a team"
    options={Enum.map(@teams, &{&1.name, &1.id})}
  />
</.form>

def handle_event("select_team", %{"team" => %{"id" => id}}, socket) do
  {:ok, team} = WasteWalk.Teams.get_by_id(id, actor: socket.assigns.current_user)
  {:noreply, assign(socket, team: team)}
end

def mount(_params, _session, socket) do
  {:ok, teams} = WasteWalk.Teams.get_all(actor: socket.assigns.current_user)

  socket =
    socket
    |> assign(team: nil)
    |> assign(teams: teams)
    |> assign(team_form: to_form(%{"team_id" => ""}, as: :team))
  {:ok, socket}
end

Sorry for the confusion, that’s on me! The is no phx-validate! i just got it mixed up because i always write phx-change="validate".