lib/live_admin/components/resource/form/search_select.ex

defmodule LiveAdmin.Components.Container.Form.SearchSelect do
  use Phoenix.LiveComponent
  import Phoenix.HTML.Form
  use PhoenixHTMLHelpers

  import LiveAdmin, only: [record_label: 3, trans: 1]
  import LiveAdmin.Components

  alias Phoenix.LiveView.JS
  alias LiveAdmin.Resource

  @impl true
  def update(assigns = %{form: form, field: field}, socket) do
    socket =
      socket
      |> assign(assigns)
      |> assign(options: [])
      |> assign_selected_option(input_value(form, field))

    {:ok, socket}
  end

  @impl true
  def render(assigns = %{disabled: true}) do
    ~H"""
    <div>
      <%= if @selected_option do %>
        <%= record_label(@selected_option, @resource, @config) %>
      <% else %>
        <%= trans("None") %>
      <% end %>
    </div>
    """
  end

  @impl true
  def render(assigns) do
    ~H"""
    <div
      class="search_select"
      phx-hook="SearchSelect"
      id={input_id(@form, @field) <> "_search_select"}
    >
      <%= hidden_input(@form, @field,
        disabled: @disabled,
        value:
          if(@selected_option,
            do: Map.fetch!(@selected_option, LiveAdmin.primary_key!(@resource))
          ),
        id: input_id(@form, @field) <> "_hidden"
      ) %>
      <%= if @selected_option do %>
        <a
          href="#"
          phx-click={JS.push("select", value: %{key: nil}, target: @myself, page_loading: true)}
          class="button__remove"
        />
        <%= record_label(@selected_option, @resource, @config) %>
      <% else %>
        <.dropdown
          :let={option}
          id={input_id(@form, @field) <> "_dropdown"}
          label="Select"
          items={
            Enum.filter(
              @options,
              &(Map.fetch!(&1, LiveAdmin.primary_key!(@resource)) != input_value(@form, @field))
            )
          }
        >
          <:empty_label>
            <%= trans("No options") %>
          </:empty_label>
          <:control>
            <input
              type="text"
              id={input_id(@form, @field)}
              disabled={@disabled}
              placeholder={trans("Search")}
              autocomplete="off"
              phx-focus="load_options"
              phx-keyup="load_options"
              phx-target={@myself}
              phx-debounce={200}
            />
          </:control>
          <a
            href="#"
            phx-click={
              JS.push("select",
                value: %{key: Map.fetch!(option, LiveAdmin.primary_key!(@resource))},
                target: @myself,
                page_loading: true
              )
            }
          >
            <%= record_label(option, @resource, @config) %>
          </a>
        </.dropdown>
      <% end %>
    </div>
    """
  end

  @impl true
  def handle_event(
        "load_options",
        %{"value" => q},
        socket = %{assigns: %{resource: resource, session: session, config: config}}
      ) do
    options =
      resource
      |> Resource.list(
        [search: q, prefix: socket.assigns.prefix],
        session,
        socket.assigns.repo,
        config
      )
      |> elem(0)

    {:noreply, assign(socket, :options, options)}
  end

  def handle_event("select", %{"key" => key}, socket) do
    socket =
      socket
      |> assign_selected_option(key)
      |> push_event("change", %{})

    {:noreply, socket}
  end

  defp assign_selected_option(socket, key) when key in [nil, ""],
    do: assign(socket, :selected_option, nil)

  defp assign_selected_option(
         socket = %{assigns: %{selected_option: %{key: selected_option_key}}},
         key
       )
       when selected_option_key == key,
       do: socket

  defp assign_selected_option(socket, key),
    do:
      assign(
        socket,
        :selected_option,
        Resource.find!(key, socket.assigns.resource, socket.assigns.prefix, socket.assigns.repo)
      )
end