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






















