Basic Understanding of Live Form

I am trying to get a basic for to do my bidding.
I am very much a beginnner so many things are a bit cloudy.
I have a heex template below:

<main class="px-4 py-20 sm:px-6 lg:px-8">
  <.form for={@form} phx-change="validate" phx-submit="Go">
    <div class="p-2">
      <div>
        <.input
          name="version"
          field={@form[:version_id]}
          options={@versions}
          type="select"
          phx-change="select-version"
          value=""
          label="Select a Version"
        />
      </div>

      <div>
        <.input
          name="customer"
          field={@form[:customer_id]}
          options={[]}
          type="select"
          phx-change="select-customer"
          value=""
          label="Select a Customer:"
        />
      </div>
      <div>
        <.button>
          Go
        </.button>
      </div>
    </div>
  </.form>
  <pre><%= inspect assigns, pretty: true %></pre>
</main>

There are two <selects one is dependent on the other.
So the value selected from versions will populate the other selector with a list of customers that belong to that version.

The related code looks like this at the moment.

defmodule CustomerbuilderWeb.MainLive.Index do
  use CustomerbuilderWeb, :live_view

  alias Customerbuilder.Builder

  @impl true
  def mount(_params, _session, socket) do

    {:ok,
     socket
     |> assign(:versions, get_versions())
     |> clear_form()}
  end

  @impl true
  def handle_event("select-version", %{"id" => id}, socket) do
    {:noreply, stream(socket, :version_collection, id)}
  end

  @impl true
  def handle_event("select-customer", %{"id" => id}, socket) do
    {:noreply, stream(socket, :version_collection, id)}
  end

  def assign_form(socket, changeset) do
    assign(socket, :form, to_form(changeset))
  end

  def clear_form(socket) do
    form =
      socket.assigns
      |> to_form()

    assign(socket, :form, form)
  end

  defp get_versions() do
    for version <- Builder.list_version(), do: {version.name, version.id}
  end
end

Once the version and customer is set the Go button will take those values and send a POST request to an api (using HTTPPoison?) to kick off a build.

Then a kind of polling to api monitor the progress of the build to update the UI, which I haven’t even started.

a map with atom keys was given to a form. Maps are always considered parameters                                                      and therefore must have string keys, got: %{versions: [{"Version-8.6.2",1}, {"Version-8.6.1", 2}, {"Version-8.5.3", 3},

I am very grateful for any interest here.

1 Like

Looks interesting! Thanks for sharing. Just to confirm - are you looking for advice/insight on how to get to the functionality describe starting from the above, or saying that there’s a more particular problem with how it should already be working?

Thanks.

I am trying to get any of it working… it is really understanding exactly how it all hangs together.

mount : this is like a init for the live view and you can add models? to the socket for transfer across the websocket

I have added an array of key/value pairs (which I get t from the db) to the versions variable for use by the template. and then send it to a form was a schemaless changeset.

I expect the handle_event for select-version to do something which it really doesn’t at the moment but would do something like

for version <- Builder.get_customer_by_version_id(id), do: {customer.name, customer.id}

and assign that to the socket for the customer <.select

It is the form bit I am unsure of

field={@form[:version_id]}

not sure how to set this or retrieve it for example

The data set as assigns and/or streams in the server side socket isn’t transfered across the websocket. It’s the rendered HTML template and subsequent changes/diffs that get sent to the client side via the websocket.

defmodule LiveCalWeb.OrganizationUserSearchLive do
  use LiveCalWeb, :live_view

  # note: options are structured as a list of tuples representing `option` text and value
  # e.g. `["Admin": "admin", "User": "user"]` as described in the `Phoenix.HTML.Form.options_for_select/2` docs

  def mount(_params, _session, socket) do
    organization_options = for org <- get_organizations(), do: {org.name, org.id}

    {:ok,
     socket
     |> assign(:form, to_form(%{"organization_id" => nil, "user_id" => nil}))
     |> assign(:organization_options, organization_options)
     |> assign(:user_options, [])}
  end

  def handle_event("select-organization", %{"organization_id" => org_id} = form_params, socket) do
    user_options = for user <- get_organization_users(org_id), do: {user.name, user.id}

    {:noreply,
      socket
      |> assign(:form, to_form(form_params))
      |> assign(:user_options, user_options)}
  end

  def handle_event("submit", %{"organization_id" => organization_id, "user_id" => user_id}, socket) do
    IO.puts("~~ form submitted for organzation id: #{organization_id} and user id: #{user_id}~~")
    {:noreply, socket}
  end

  def render(assigns) do
    ~H"""
    <.form for={@form} phx-submit="submit">
      <.input field={@form[:organization_id]} phx-change="select-organization"
        type="select"
        label="First select an organization to load its users:"
        placeholder="organization"
        options={@organization_options}
        prompt="-- select organization --"
      />
      <.input field={@form[:user_id]}
        type="select"
        label="Now select an organization user:"
        placeholder="user"
        options={@user_options}
        prompt="-- select user --"
      />
      <button>Submit</button>
    </.form>
    """
  end

  # stubbed users and organizations
  defp get_organizations(), do: [%{id: 1, name: "OrgA"}, %{id: 2, name: "OrgB"}]
  defp get_organization_users(organization_id) do
    case organization_id do
      "1" -> [%{id: 1, name: "John from OrgA"}, %{id: 2, name: "Jules from OrgA"}]
      "2" -> [%{id: 3, name: "Jack from OrgB"}, %{id: 4, name: "Jill from OrgB"}]
    end
  end
end

# bonus: if you add `:let={f}` inside the `<.form ...>` component and `:if={f.params["organization_id"]}` inside the user `<.input ...>`, you can hide the user dropdown until an organization is selected
3 Likes

Appreciate the time and effort you have put in here @codeanpeace .

It really helps to see examples like this as I can piece together how it works by connecting the names of things.

2 Likes