ream88
Handling events in Phoenix LiveComponents
Hello,
I’m currently struggling while programming the following reusable auto-complete component:
defmodule AppWeb.AutoCompleteField do
use AppWeb, :component
defmacro __using__(opts \\ []) do
fun = Keyword.fetch!(opts, :search_function)
quote do
@impl true
def handle_event("search", %{"_target" => [name]} = params, socket) do
%{^name => value} = params
%{id: id} = socket.assigns
pid = self()
spawn(fn →
items = unquote(fun).(Map.fetch!(params, name))
send_update(pid, __MODULE__, id: id, items: items)
end)
{:noreply, socket}
end
@impl true
def handle_event("select", params, socket) do
IO.inspect(params, label: "select")
# TODO: send_update
{:noreply, assign(socket, items: [])}
end
@impl true
def handle_event("select", %{"index" => index}, socket) do
index = String.to_integer(index)
value = Enum.at(socket.assigns.items, index)
{:noreply, assign(socket, items: [], value: value)}
end
end
end
attr :items, :list, default: []
attr :"list-class", :string, default: ""
attr :"item-class", :string, default: ""
attr :"container-class", :string, default: ""
attr :rest, :global
def autocomplete_field(assigns) do
~H"""
<div class={Map.get(assigns, :"container-class")}>
<input
role="combobox"
type="text"
{@rest}
phx-change="search"
phx-debounce="500"
aria-controls="options"
aria-expanded="false"
/>
<%= if @items != [] do %>
<ul role="listbox" class={Map.get(assigns, :"list-class")}>
<%= for {suggestion, index} <- Enum.with_index(@items) do %>
<li
role="option"
class={Map.get(assigns, :"item-class")}
phx-click="select"
phx-value-index={index}
>
<%= render_slot(@inner_block, suggestion) %>
</li>
<% end %>
</ul>
<% end %>
</div>
"""
end
end
It mostly works, the only problem is this that the first event, the debounced search is properly sent to the parent live view like this:
[debug] HANDLE EVENT
Component: AppWeb.LocationLive.FormComponent
View: AppWeb.LocationLive.Index
Event: "search"
Parameters: %{"_target" => ["name"], "name" => "a"}
But the following select event is sent incorrectly, hence the missing Component debug statement:
[debug] HANDLE EVENT
View: AppWeb.LocationLive.Index
Event: "select"
Parameters: %{"index" => "0", "value" => 0}
Any ideas what I’m doing wrong? Or understand incorrectly?
Marked As Solved
ream88
Thanks a lot @codeanpeace and @trisolaran, solved it by providing an explicit phx-target attribute and forwarding it to all elements:
defmodule AppWeb.AutoCompleteField do
use AppWeb, :component
defmacro __using__(opts \\ []) do
fun = Keyword.fetch!(opts, :search_function)
quote do
@impl true
def handle_event("search", %{"_target" => [name]} = params, socket) do
%{^name => value} = params
%{id: id} = socket.assigns
pid = self()
spawn(fn ->
items = unquote(fun).(Map.fetch!(params, name))
send_update(pid, __MODULE__, id: id, items: items)
end)
{:noreply, socket}
end
@impl true
def handle_event("select", params, socket) do
IO.inspect(params, label: "select")
# TODO: send_update
{:noreply, assign(socket, items: [])}
end
@impl true
def handle_event("select", %{"index" => index}, socket) do
index = String.to_integer(index)
value = Enum.at(socket.assigns.items, index)
{:noreply, assign(socket, items: [], value: value)}
end
end
end
attr :items, :list, default: []
attr :"phx-target", :any, required: true
attr :"list-class", :string, default: ""
attr :"item-class", :string, default: ""
attr :"container-class", :string, default: ""
attr :rest, :global
def autocomplete_field(assigns) do
~H"""
<div class={Map.get(assigns, :"container-class")}>
<input
role="combobox"
type="text"
{@rest}
phx-change="search"
phx-debounce="500"
phx-target={Map.get(assigns, :"phx-target")}
aria-controls="options"
aria-expanded="false"
/>
<%= if @items != [] do %>
<ul role="listbox" class={Map.get(assigns, :"list-class")}>
<%= for {suggestion, index} <- Enum.with_index(@items) do %>
<li
role="option"
class={Map.get(assigns, :"item-class")}
phx-click="select"
phx-value-index={index}
phx-target={Map.get(assigns, :"phx-target")}
>
<%= render_slot(@inner_block, suggestion) %>
</li>
<% end %>
</ul>
<% end %>
</div>
"""
end
end
Also Liked
codeanpeace
Off the top of my head, I’d try setting phx-target within the <li> as described in the Targeting Component Events docs. You’ll probably need to pass in the parent LiveComponent’s @myself assign. My guess is that the <input> is working either because phx-target is set by the <form> it’s nested under or passed in via @rest.
trisolaran
You need to set phx-target otherwise the phx-click event will be sent to your live view.
Your function component needs to receive the target via assign and set it for its phx-click event.
Basically what @codeanpeace said ![]()







