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

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

  import LiveAdmin, only: [record_label: 2, 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) %>
      <% 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: @selected_option.id),
        id: input_id(@form, @field) <> "_hidden"
      ) %>
      <%= if @selected_option do %>
        <a
          href="#"
          phx-click={JS.push("select", value: %{id: nil}, target: @myself, page_loading: true)}
          class="button__remove"
        />
        <%= record_label(@selected_option, @resource) %>
      <% else %>
        <.dropdown
          :let={option}
          id={input_id(@form, @field) <> "_dropdown"}
          label="Select"
          items={
            Enum.filter(
              @options,
              &(&1.id != 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: %{id: option.id},
                target: @myself,
                page_loading: true
              )
            }
          >
            <%= record_label(option, @resource) %>
          </a>
        </.dropdown>
      <% end %>
    </div>
    """
  end

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

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

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

    {:noreply, socket}
  end

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

  defp assign_selected_option(
         socket = %{assigns: %{selected_option: %{id: selected_option_id}}},
         id
       )
       when selected_option_id == id,
       do: socket

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