为什么单击复选框会触发 Phoenix LiveView 事件?
Why clicking a checkbox fires a Phoenix LiveView event?
背景
我有一个包含一些复选框的表单。此表单将根据条件显示复选框。
在表单之后我有一个按钮,它应该发送一个将执行操作的 phoenix 事件。
但是,当我单击一个复选框(select/de-select 它)时,仍然会创建 phoenix 事件。
老实说,我不知道为什么,我觉得这与我没有使用 Phoenix 表单(我使用的是纯 HTML 表单,安全性较低)有关,但我没有足够的经验想知道。
代码
@spec render(map) :: Rendered.t
def render(assigns) do
~H"""
<div class="body">
<form phx-change="filters">
<div>
<input type="hidden" name="syndicates[]" value="">
<%= for synd <- @syndicates do %>
<%= syndicate_checkbox(synd: synd, checked: synd in @selected_syndicates) %>
<% end %>
</div>
</form>
<div class={@selected_syndicates |> none_active?() |> display()}>
<p>No syndicates are active right now.</p>
</div>
<button
class={@selected_syndicates |> any_active?() |> display()}
phx-click="execute_command"
phx-value-command={@selected_command.id}
phx-value-strategy={@selected_strategy.id}>
Execute Command
</button>
</div>
"""
end
@spec display(boolean) :: String.t()
defp display(true), do: "show"
defp display(_), do: "hidden"
@spec any_active?([map]) :: boolean
defp any_active?([]), do: false
defp any_active?(_), do: true
@spec none_active?([map]) :: boolean
defp none_active?(data), do: !any_active(data)
@spec syndicate_checkbox(map) :: Rendered.t
defp syndicate_checkbox(assigns) do
assigns = Enum.into(assigns, %{})
~H"""
<div class={display(@checked)}>
<div class="row single-syndicate">
<input class="column single-checkbox" type="checkbox" id="{@synd.id}"
name="syndicates[]" value="{@synd.id}">
<label for="{@synd.id}" class="column"><%= @synd.name %></label>
</div>
</div>
"""
end
问题
基本上,我这里的objective很简单:
- if
none_active?
returns true
, 然后我显示文本并隐藏按钮
- 否则,我显示按钮并隐藏文本
但是,显示的行为让我很困惑(请参阅 停用 菜单):
后面控制台显示的错误是:
11:09:28.334 [error] GenServer #PID<0.2115.0> terminating
** (FunctionClauseError) no function clause matching in WebInterface.Live.Window.handle_event/3
(web_interface 1.1.0) lib/web_interface/live/window.ex:53: WebInterface.Live.Window.handle_event("filters", %{"_target" => ["syndicates"], "syndicates" => ["", "{@synd.id}"]}, #Phoenix.LiveView.Socket<assigns: %{__changed__: %{}, commands: [%{description: "\n Activating a syndicate will cause the app to create a sell order on warframe.market for each product of the said syndicate.\n The prices of each item will be determined accoring to a strategy that you can define.\n ", id: :activate, name: "Activate"}, %{description: "\n Deactivating a syndicate removes all sell orders from waframe.market for the given syndicate.\n ", id: :deactivate, name: "Deactivate"}, %{description: "\n Saving authentication information will allow this application to make requests in your behalf.\n It is a required step for the application to work.\n ", id: :authenticate, name: "Authenticate"}], flash: %{"info" => "Request completed: [ok: :success]"}, live_action: nil, selected_command: %{description: "\n Deactivating a syndicate removes all sell orders from waframe.market for the given syndicate.\n ", id: :deactivate, name: "Deactivate"}, selected_strategy: %{description: "\n Gets the 3 lowest prices for the given item and calculates the average.\n ", id: :top_three_average, name: "Top 3 Average"}, selected_syndicates: [%{id: "red_veil", name: "Red Veil"}], strategies: [%{description: "\n Gets the 3 lowest prices for the given item and calculates the average.\n ", id: :top_three_average, name: "Top 3 Average"}, %{description: "\n Gets the 5 lowest prices for the given item and calculates the average.\n ", id: :top_five_average, name: "Top 5 Average"}, %{description: "\n Gets the lowest price for the given item and uses it.\n ", id: :equal_to_lowest, name: "Equal to lowest"}, %{description: "\n Gets the lowest price for the given item and beats it by 1.\n ", id: :lowest_minus_one, name: "Lowest minus one"}], syndicates: [%{id: "red_veil", name: "Red Veil"}, %{id: "perrin_sequence", name: "Perrin Sequence"}, %{id: "new_loka", name: "New Loka"}, %{id: "arbiters_of_hexis", name: "Arbiters of Hexis"}, %{id: "steel_meridian", name: "Steel Meridian"}, %{id: "cephalon_suda", name: "Cephalon Suda"}, %{id: "simaris", name: "Cephalon Simaris"}]}, endpoint: WebInterface.Endpoint, id: "phx-FuUYGcft5EwElQBl", parent_pid: nil, root_pid: #PID<0.2115.0>, router: WebInterface.Router, transport_pid: #PID<0.2084.0>, view: WebInterface.Live.Window, ...>)
(phoenix_live_view 0.17.6) lib/phoenix_live_view/channel.ex:349: anonymous fn/3 in Phoenix.LiveView.Channel.view_handle_event/3
(telemetry 1.0.0) c:/Users/palme/Worskapce/fl4m3/market_manager/deps/telemetry/src/telemetry.erl:293: :telemetry.span/3
(phoenix_live_view 0.17.6) lib/phoenix_live_view/channel.ex:206: Phoenix.LiveView.Channel.handle_info/2
(stdlib 3.17.1) gen_server.erl:695: :gen_server.try_dispatch/4
(stdlib 3.17.1) gen_server.erl:771: :gen_server.handle_msg/6
(stdlib 3.17.1) proc_lib.erl:236: :proc_lib.wake_up/3
Last message: %Phoenix.Socket.Message{event: "event", join_ref: "37", payload: %{"event" => "filters", "type" => "form", "uploads" => %{}, "value" => "syndicates%5B%5D=&syndicates%5B%5D=%7B%40synd.id%7D&_target=syndicates%5B%5D"}, ref: "151", topic: "lv:phx-FuUYGcft5EwElQBl"}
State: %{components: {%{}, %{}, 1}, join_ref: "37", serializer: Phoenix.Socket.V2.JSONSerializer, socket: #Phoenix.LiveView.Socket<assigns: %{__changed__: %{}, commands: [%{description: "\n Activating a syndicate will cause the app to create a sell order on warframe.market for each product of the said syndicate.\n The prices of each item will be determined accoring to a strategy that you can define.\n ", id: :activate, name: "Activate"}, %{description: "\n Deactivating a syndicate removes all sell orders from waframe.market for the given syndicate.\n ", id: :deactivate, name: "Deactivate"}, %{description: "\n Saving authentication information will allow this application to make requests in your behalf.\n It is a required step for the application to work.\n ", id: :authenticate, name: "Authenticate"}], flash: %{"info" => "Request completed: [ok: :success]"}, live_action: nil, selected_command: %{description: "\n Deactivating a syndicate removes all sell orders from waframe.market for the given syndicate.\n ", id: :deactivate, name: "Deactivate"}, selected_strategy: %{description: "\n Gets the 3 lowest prices for the given item and calculates the average.\n ", id: :top_three_average, name: "Top 3 Average"}, selected_syndicates: [%{id: "red_veil", name: "Red Veil"}], strategies: [%{description: "\n Gets the 3 lowest prices for the given item and calculates the average.\n ", id: :top_three_average, name: "Top 3 Average"}, %{description: "\n Gets the 5 lowest prices for the given item and calculates the average.\n ", id: :top_five_average, name: "Top 5 Average"}, %{description: "\n Gets the lowest price for the given item and uses it.\n ", id: :equal_to_lowest, name: "Equal to lowest"}, %{description: "\n Gets the lowest price for the given item and beats it by 1.\n ", id: :lowest_minus_one, name: "Lowest minus one"}], syndicates: [%{id: "red_veil", name: "Red Veil"}, %{id: "perrin_sequence", name: "Perrin Sequence"}, %{id: "new_loka", name: "New Loka"}, %{id: "arbiters_of_hexis", name: "Arbiters of Hexis"}, %{id: "steel_meridian", name: "Steel Meridian"}, %{id: "cephalon_suda", name: "Cephalon Suda"}, %{id: "simaris", name: "Cephalon Simaris"}]}, endpoint: WebInterface.Endpoint, id: "phx-FuUYGcft5EwElQBl", parent_pid: nil, root_pid: #PID<0.2115.0>, router: WebInterface.Router, transport_pid: #PID<0.2084.0>, view: WebInterface.Live.Window, ...>, topic: "lv:phx-FuUYGcft5EwElQBl", upload_names: %{}, upload_pids: %{}}
问题
据我了解,发生崩溃是因为我发送了一个 filter
事件,但我没有在更高级别捕获它。
但是,我不明白为什么首先会生成此事件。我只是 selecting/deselecting 一个复选框,什么都不应该发生。
- 我做错了什么?
- 我应该用 Phoenix 表格替换这个 HTML 表格吗?如果是这样,我会去做吗?
任何表单更改都会发送一个 phx-change
事件。如果您只想在提交时发送事件,请使用 phx-submit
。
见https://hexdocs.pm/phoenix_live_view/form-bindings.html#form-events
背景
我有一个包含一些复选框的表单。此表单将根据条件显示复选框。 在表单之后我有一个按钮,它应该发送一个将执行操作的 phoenix 事件。
但是,当我单击一个复选框(select/de-select 它)时,仍然会创建 phoenix 事件。 老实说,我不知道为什么,我觉得这与我没有使用 Phoenix 表单(我使用的是纯 HTML 表单,安全性较低)有关,但我没有足够的经验想知道。
代码
@spec render(map) :: Rendered.t
def render(assigns) do
~H"""
<div class="body">
<form phx-change="filters">
<div>
<input type="hidden" name="syndicates[]" value="">
<%= for synd <- @syndicates do %>
<%= syndicate_checkbox(synd: synd, checked: synd in @selected_syndicates) %>
<% end %>
</div>
</form>
<div class={@selected_syndicates |> none_active?() |> display()}>
<p>No syndicates are active right now.</p>
</div>
<button
class={@selected_syndicates |> any_active?() |> display()}
phx-click="execute_command"
phx-value-command={@selected_command.id}
phx-value-strategy={@selected_strategy.id}>
Execute Command
</button>
</div>
"""
end
@spec display(boolean) :: String.t()
defp display(true), do: "show"
defp display(_), do: "hidden"
@spec any_active?([map]) :: boolean
defp any_active?([]), do: false
defp any_active?(_), do: true
@spec none_active?([map]) :: boolean
defp none_active?(data), do: !any_active(data)
@spec syndicate_checkbox(map) :: Rendered.t
defp syndicate_checkbox(assigns) do
assigns = Enum.into(assigns, %{})
~H"""
<div class={display(@checked)}>
<div class="row single-syndicate">
<input class="column single-checkbox" type="checkbox" id="{@synd.id}"
name="syndicates[]" value="{@synd.id}">
<label for="{@synd.id}" class="column"><%= @synd.name %></label>
</div>
</div>
"""
end
问题
基本上,我这里的objective很简单:
- if
none_active?
returnstrue
, 然后我显示文本并隐藏按钮 - 否则,我显示按钮并隐藏文本
但是,显示的行为让我很困惑(请参阅 停用 菜单):
后面控制台显示的错误是:
11:09:28.334 [error] GenServer #PID<0.2115.0> terminating
** (FunctionClauseError) no function clause matching in WebInterface.Live.Window.handle_event/3
(web_interface 1.1.0) lib/web_interface/live/window.ex:53: WebInterface.Live.Window.handle_event("filters", %{"_target" => ["syndicates"], "syndicates" => ["", "{@synd.id}"]}, #Phoenix.LiveView.Socket<assigns: %{__changed__: %{}, commands: [%{description: "\n Activating a syndicate will cause the app to create a sell order on warframe.market for each product of the said syndicate.\n The prices of each item will be determined accoring to a strategy that you can define.\n ", id: :activate, name: "Activate"}, %{description: "\n Deactivating a syndicate removes all sell orders from waframe.market for the given syndicate.\n ", id: :deactivate, name: "Deactivate"}, %{description: "\n Saving authentication information will allow this application to make requests in your behalf.\n It is a required step for the application to work.\n ", id: :authenticate, name: "Authenticate"}], flash: %{"info" => "Request completed: [ok: :success]"}, live_action: nil, selected_command: %{description: "\n Deactivating a syndicate removes all sell orders from waframe.market for the given syndicate.\n ", id: :deactivate, name: "Deactivate"}, selected_strategy: %{description: "\n Gets the 3 lowest prices for the given item and calculates the average.\n ", id: :top_three_average, name: "Top 3 Average"}, selected_syndicates: [%{id: "red_veil", name: "Red Veil"}], strategies: [%{description: "\n Gets the 3 lowest prices for the given item and calculates the average.\n ", id: :top_three_average, name: "Top 3 Average"}, %{description: "\n Gets the 5 lowest prices for the given item and calculates the average.\n ", id: :top_five_average, name: "Top 5 Average"}, %{description: "\n Gets the lowest price for the given item and uses it.\n ", id: :equal_to_lowest, name: "Equal to lowest"}, %{description: "\n Gets the lowest price for the given item and beats it by 1.\n ", id: :lowest_minus_one, name: "Lowest minus one"}], syndicates: [%{id: "red_veil", name: "Red Veil"}, %{id: "perrin_sequence", name: "Perrin Sequence"}, %{id: "new_loka", name: "New Loka"}, %{id: "arbiters_of_hexis", name: "Arbiters of Hexis"}, %{id: "steel_meridian", name: "Steel Meridian"}, %{id: "cephalon_suda", name: "Cephalon Suda"}, %{id: "simaris", name: "Cephalon Simaris"}]}, endpoint: WebInterface.Endpoint, id: "phx-FuUYGcft5EwElQBl", parent_pid: nil, root_pid: #PID<0.2115.0>, router: WebInterface.Router, transport_pid: #PID<0.2084.0>, view: WebInterface.Live.Window, ...>)
(phoenix_live_view 0.17.6) lib/phoenix_live_view/channel.ex:349: anonymous fn/3 in Phoenix.LiveView.Channel.view_handle_event/3
(telemetry 1.0.0) c:/Users/palme/Worskapce/fl4m3/market_manager/deps/telemetry/src/telemetry.erl:293: :telemetry.span/3
(phoenix_live_view 0.17.6) lib/phoenix_live_view/channel.ex:206: Phoenix.LiveView.Channel.handle_info/2
(stdlib 3.17.1) gen_server.erl:695: :gen_server.try_dispatch/4
(stdlib 3.17.1) gen_server.erl:771: :gen_server.handle_msg/6
(stdlib 3.17.1) proc_lib.erl:236: :proc_lib.wake_up/3
Last message: %Phoenix.Socket.Message{event: "event", join_ref: "37", payload: %{"event" => "filters", "type" => "form", "uploads" => %{}, "value" => "syndicates%5B%5D=&syndicates%5B%5D=%7B%40synd.id%7D&_target=syndicates%5B%5D"}, ref: "151", topic: "lv:phx-FuUYGcft5EwElQBl"}
State: %{components: {%{}, %{}, 1}, join_ref: "37", serializer: Phoenix.Socket.V2.JSONSerializer, socket: #Phoenix.LiveView.Socket<assigns: %{__changed__: %{}, commands: [%{description: "\n Activating a syndicate will cause the app to create a sell order on warframe.market for each product of the said syndicate.\n The prices of each item will be determined accoring to a strategy that you can define.\n ", id: :activate, name: "Activate"}, %{description: "\n Deactivating a syndicate removes all sell orders from waframe.market for the given syndicate.\n ", id: :deactivate, name: "Deactivate"}, %{description: "\n Saving authentication information will allow this application to make requests in your behalf.\n It is a required step for the application to work.\n ", id: :authenticate, name: "Authenticate"}], flash: %{"info" => "Request completed: [ok: :success]"}, live_action: nil, selected_command: %{description: "\n Deactivating a syndicate removes all sell orders from waframe.market for the given syndicate.\n ", id: :deactivate, name: "Deactivate"}, selected_strategy: %{description: "\n Gets the 3 lowest prices for the given item and calculates the average.\n ", id: :top_three_average, name: "Top 3 Average"}, selected_syndicates: [%{id: "red_veil", name: "Red Veil"}], strategies: [%{description: "\n Gets the 3 lowest prices for the given item and calculates the average.\n ", id: :top_three_average, name: "Top 3 Average"}, %{description: "\n Gets the 5 lowest prices for the given item and calculates the average.\n ", id: :top_five_average, name: "Top 5 Average"}, %{description: "\n Gets the lowest price for the given item and uses it.\n ", id: :equal_to_lowest, name: "Equal to lowest"}, %{description: "\n Gets the lowest price for the given item and beats it by 1.\n ", id: :lowest_minus_one, name: "Lowest minus one"}], syndicates: [%{id: "red_veil", name: "Red Veil"}, %{id: "perrin_sequence", name: "Perrin Sequence"}, %{id: "new_loka", name: "New Loka"}, %{id: "arbiters_of_hexis", name: "Arbiters of Hexis"}, %{id: "steel_meridian", name: "Steel Meridian"}, %{id: "cephalon_suda", name: "Cephalon Suda"}, %{id: "simaris", name: "Cephalon Simaris"}]}, endpoint: WebInterface.Endpoint, id: "phx-FuUYGcft5EwElQBl", parent_pid: nil, root_pid: #PID<0.2115.0>, router: WebInterface.Router, transport_pid: #PID<0.2084.0>, view: WebInterface.Live.Window, ...>, topic: "lv:phx-FuUYGcft5EwElQBl", upload_names: %{}, upload_pids: %{}}
问题
据我了解,发生崩溃是因为我发送了一个 filter
事件,但我没有在更高级别捕获它。
但是,我不明白为什么首先会生成此事件。我只是 selecting/deselecting 一个复选框,什么都不应该发生。
- 我做错了什么?
- 我应该用 Phoenix 表格替换这个 HTML 表格吗?如果是这样,我会去做吗?
任何表单更改都会发送一个 phx-change
事件。如果您只想在提交时发送事件,请使用 phx-submit
。
见https://hexdocs.pm/phoenix_live_view/form-bindings.html#form-events