Phoenix 1.7. & LiveView 0.20.2 - simple dropdown selector question

I am trying to build an admin panel - in one field I want a simple dropdown menu.
I found a way to do this, but I think I found a round-about way and hoping there is a straight forward way.

Here is what I did:

  1. add all the users to the assigns (so I could list them in the dropdown)
  2. hide the normal field that submits the user_id (but keep the value there) so that on save the value is submitted (probably possible to do from the select, but I couldn’t figure out how).
  3. add a select with all the users (and with the initial ‘selected’ value the same as the user_id in the form.
  4. wrote a handler to update the user_id in the changeset when the selected value changes.

Here is the code (for better or worse - but it works):

  def render(assigns) do
    ~H"""
    <div>
      <.header>
        <%= @title %>
        <:subtitle>Use this form to manage domains records in your database.</:subtitle>
      </.header>

      <.simple_form
        for={@form}
        id="domains-form"
        phx-target={@myself}
        phx-change="validate"
        phx-submit="save"
      >
        <.input field={@form[:domain_name]} type="text" label="Domain name" />
        <.input field={@form[:domain_url]} type="text" label="Domain URL" />
        <.input field={@form[:domain_descriptioin]} type="text" label="Domain descriptioin" />
        <%!-- <.input field={@form[:user_id]} type="number" label="User ID" /> --%>
        <.input field={@form[:user_id]} type="hidden" />

        <label for="user_id">User</label>
        <select name="user_id" id="user_id" phx-change="select_user">
          <%= for user <- @users do %>
            <%= if user.id == @domains.user_id do %>
              <option value={user.id} selected><%= user.username %></option>
            <% else %>
              <option value={user.id}><%= user.username %></option>
            <% end %>
            <%!-- <option value={user.id}><%= user.username %></option> --%>
            <%!-- <% selected = if user.id == @form[:user_id], do: "selected", else: "" %> --%>
            <%!-- <option value={user.id} {selected}><%= user.username %></option> --%>
          <% end %>
        </select>
        <:actions>
          <.button phx-disable-with="Saving...">Save Domains</.button>
        </:actions>
      </.simple_form>
    </div>
    """
  end

  @impl true
  def update(%{domains: domains} = assigns, socket) do
    # add the full list of users for the dropdown selector
    users = ClearSync.Core.Accounts.list_users()
    socket = assign(socket, users: users)
    changeset = WorkDomains.change_domains(domains)

    {:ok,
     socket
     |> assign(assigns)
     |> assign_form(changeset)}
  end

  # update the user chosen in the changeset when the selector changes who is selected
  def handle_event("select_user", %{"user_id" => user_id}, socket) do]
    changeset =
      socket.assigns.domains
      |> WorkDomains.change_domains(%{"user_id" => String.to_integer(user_id)})

    # |> WorkDomains.change_domains(%{"user_id" => parsed_user_id})

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

This works, but

  1. it seems clumsy, I am assuming I have overlooked something fundamental
  2. my hidden .input field is hidden, but takes up visible white space

Note: I haven’t caught up on understanding the newest LiveView changes - I am sure my approach reflects that, in addition to helping me cleanup the whitespace and create a more elegant solution, If its clear what fundamental misunderstanding(s) I have - I would appreciate knowing where I can learn more - I will try to go through some liveview 1.7 tutorials, but if there is a short cut short of starting over, I would appreciate that.

Thanks,

Bill

1 Like

I am relatively new, but will need something like this (and am working through a book, so I can find some things :slight_smile: ). Hope this helps.

A shortcut might be to generate a new application, and have a look through CoreComponents for the html elements that you use. <.input type="select"> will generate a dropdown and save the result in a field.
I hardcoded the options here so it is easier to see where the id goes.

<.input
          field={@domains[:user_id]}
          type="select"
          label="User"
          options={[{"Luke", "1"}, {"Leia", "2"}]}
 />

I used a few threads here to find the <.input type="select", but reading through CoreComponents was the most useful. With the value= attribute I could choose the second value, but I don’t know if that is necessary when you have a changeset with the field.

1 Like

@Mostalive covered it for the most part; using the build in core_components you can do something like this as long as you’re passing the users in.

  <.input
    field={f[:user_id]}
    type="select"
    label="User"
    options={Enum.map(@users, &{&1.username, &1.id})}
    prompt="Select a user"
  />

It might be better to just pass the list of tuples into the component instead of the list of full users depending on the situation.

You only need to pass in a “value” if you want to pre-select a specific value. Using the code above the component is smart enough to pre-select the correct user if you use the form component on an edit page.

2 Likes

Thanks for the example - works well. Definately, much simpler. :slight_smile:

2 Likes

Thanks for pointing me in the right direction and the documentaion.

2 Likes