Communicating Between Live Components

Here’s the Live Component in question. Note that it’s a prototype and employs thousands of dirty, no good hacky hacks.

defmodule Proj.Search do
  use Phoenix.LiveComponent
  use Phoenix.HTML

  import Ecto.Query

  alias Proj.Repo

  @impl true
  def update(assigns, socket) do
    name = String.to_existing_atom("#{Map.get(assigns, :assoc)}_id")

    socket = socket
    |> assign(assigns)
    |> assign([items: [], vid: nil, index: 0, count: 0, name: name])

    {:ok, assign(socket, value: get_value(socket.assigns))}
  end

  @impl true
  def render(assigns) do
    ~H"""
    <span>
      <input
        id={@id}
        class={iclass(assigns)}
        autocomplete="off"
        phx-target={@myself}
        phx-keyup="input"
        phx-hook="SearchInput"
        autofocus={Map.get(assigns, :autofocus)}
        tabindex={Map.get(assigns, :tabindex)}
        value={@value}/>
      <div class="sc-container nueue"><%= for {item, i} <- Enum.with_index(@items) do %>
        <div class={class(i, @index)}>
          <div class="sc-tit" phx-click="fin" phx-value-id={item.id} phx-target={@myself}><%= Map.get(item, @search) %></div>
        </div>
      <% end %></div>
      <%= if not is_nil(@value) do %>
        <%= hidden_input @f, @name, value: @vid %>
      <% end %>
    </span>
    """
  end

  @impl true
  def handle_event("input", %{"key" => "ArrowUp"}, socket) do
    ind = socket.assigns.index
    index = if ind == 0, do: socket.assigns.count - 1, else: ind - 1
    s = Enum.at(socket.assigns.items, index)
    {:noreply, assign(socket, index: index, value: Map.get(s || %{}, socket.assigns.name), vid: Map.get(s || %{}, :id))}
  end

  def handle_event("input", %{"key" => "ArrowDown"}, socket) do
    ind = socket.assigns.index
    index = if ind == socket.assigns.count - 1, do: 0, else: ind + 1
    s = Enum.at(socket.assigns.items, index)
    {:noreply, assign(socket, index: index, value: Map.get(s || %{}, socket.assigns.name), vid: Map.get(s || %{}, :id))}
  end

  def handle_event("input", %{"Key" => k, "value" => val}, socket) when k in ["Tab", "Enter"] do
    if val == socket.assigns.value do
      {:noreply, socket}
    else
      handle_event("fin", %{"id" => socket.assigns.vid}, socket)
    end
  end

  def handle_event("input", %{"value" => val}, socket) do
    assigns = socket.assigns
    if assigns.value == val do
      {:noreply, socket}
    else
      q = val <> "%"
      i = if val == "" do
        []
      else
        source = Ecto.build_assoc(assigns.f.data, assigns.assoc).__struct__
        from(s in source)
        |> where([s], field(s, :active) == ^true and ilike(field(s, ^assigns.search), ^q))
        |> order_by([s], [asc: field(s, ^assigns.search)])
        |> limit(20)
        |> Repo.all()
      end

      vid = if Enum.empty?(i) do nil else Map.get(List.first(i), :id) end

      {:noreply, assign(socket, items: i, count: Enum.count(i), vid: vid, index: 0)}
    end
  end

  def handle_event("fin", %{"id" => id}, socket) do
    s = Enum.find(socket.assigns.items, fn x -> x.id == id end)
    value = Map.get(s || %{}, socket.assigns.search)
    if Map.get(socket.assigns, :affect) do
      send(self(), {:affect, id: socket.assigns.affect, value: value, vid: id})
    end
    {:noreply, assign(socket, items: [], count: 0, value: value, vid: id, index: 0)}
  end

  defp class(i, ind) do
    if i == ind do
      ["sc-t", "active"]
    else
      "sc-t"
    end
  end

  defp iclass(assigns) do
    [Map.get(assigns, :class, [])] ++ ["search"]
  end

  defp get_value(assigns) do
    (Proj.get_assoc(assigns.f, assigns.name, assigns.assoc) || %{}) |> Map.get(assigns.search)
  end
end

And the function call in the parent:

def handle_info({:affect, id: id, value: value, vid: vid}, socket) do
    send_update(Proj.Search, id: id, value: value, vid: vid)
    {:noreply, socket}
end