From 533574d0cc3103a3475dcecfb794102a8ea75337 Mon Sep 17 00:00:00 2001 From: Max Marcon Date: Fri, 1 Dec 2023 12:31:18 +0100 Subject: [PATCH] allow selection to be updated from the form --- lib/live_select.ex | 5 ++-- lib/live_select/component.ex | 47 +++++++++++++++++++++++++++++++--- test/live_select_tags_test.exs | 26 +++++++++++++++++++ test/live_select_test.exs | 22 ++++++++++++++++ 4 files changed, 94 insertions(+), 6 deletions(-) diff --git a/lib/live_select.ex b/lib/live_select.ex index 5f6b626..08953c0 100644 --- a/lib/live_select.ex +++ b/lib/live_select.ex @@ -37,7 +37,8 @@ defmodule LiveSelect do ## Options - You can pass or update the list of options the user can choose from with the `options` assign. + You can set the initial list of options the user can choose from with the `options` assign. + Afterwards, you can update the options at any time using `Phoenix.LiveView.send_update/3`. Each option will be assigned a label, which will be shown in the dropdown, and a value, which will be the value of the LiveSelect input when the option is selected. @@ -280,7 +281,7 @@ defmodule LiveSelect do attr :options, :list, doc: - ~s(initial available options to select from. See the "Options" section for details on what you can pass here) + ~s(initial available options to select from. Note that, after the initial rendering of the component, options can only be updated using `Phoenix.LiveView.send_update/3` - See the "Options" section for details) attr :value, :any, doc: "used to manually set a selection - overrides any values from the form. Must be a single element in `:single` mode, or a list of elements in `:tags` mode." diff --git a/lib/live_select/component.ex b/lib/live_select/component.ex index 6f0678e..0ae288f 100644 --- a/lib/live_select/component.ex +++ b/lib/live_select/component.ex @@ -108,6 +108,17 @@ defmodule LiveSelect.Component do def update(assigns, socket) do validate_assigns!(assigns) + initial_render = !socket.assigns[:field] + + assigns = + if !initial_render && Map.has_key?(assigns, :field) do + # do not reset option when rerendering + # TODO: missing test + Map.delete(assigns, :options) + else + assigns + end + socket = socket |> assign(assigns) @@ -131,10 +142,21 @@ defmodule LiveSelect.Component do end) |> update(:options, &normalize_options/1) |> assign(:text_input_field, String.to_atom("#{socket.assigns.field.field}_text_input")) - |> assign_new(:selection, fn - %{field: field, options: options, mode: mode} -> - set_selection(field.value, options, mode) - end) + + socket = + if assigns[:field] do + assign( + socket, + :selection, + set_selection( + decode(socket.assigns.field.value, socket.assigns.mode), + socket.assigns.options, + socket.assigns.mode + ) + ) + else + socket + end socket = if Map.has_key?(assigns, :value) do @@ -586,6 +608,23 @@ defmodule LiveSelect.Component do defp encode(value), do: Jason.encode!(value) + defp decode(nil, _), do: nil + + defp decode(value, :tags) do + value + |> List.wrap() + |> Enum.map(&decode(&1, :single)) + end + + defp decode(value, :single) when is_binary(value) do + case Jason.decode(value) do + {:ok, decoded} -> decoded + {:error, _} -> value + end + end + + defp decode(value, :single), do: value + defp already_selected?(option, selection) do option.label in Enum.map(selection, & &1.label) end diff --git a/test/live_select_tags_test.exs b/test/live_select_tags_test.exs index 35c0a93..fd2d7f7 100644 --- a/test/live_select_tags_test.exs +++ b/test/live_select_tags_test.exs @@ -444,4 +444,30 @@ defmodule LiveSelectTagsTest do }) end end + + test "selection can be updated from the form", %{conn: conn} do + stub_options([ + %{value: 1, label: "A"}, + %{value: 2, label: "B"}, + %{value: 3, label: "C"} + ]) + + {:ok, live, _html} = live(conn, "/?mode=tags") + + type(live, "ABC") + + select_nth_option(live, 1) + + type(live, "ABC") + + select_nth_option(live, 2, method: :click) + + assert_selected_multiple(live, [%{value: 1, label: "A"}, %{value: 2, label: "B"}]) + + send_update(live, + field: Phoenix.Component.to_form(%{"city_search" => [2, 3]}, as: :my_form)[:city_search] + ) + + assert_selected_multiple_static(live, [%{value: 2, label: "B"}, %{value: 3, label: "C"}]) + end end diff --git a/test/live_select_test.exs b/test/live_select_test.exs index dbcbebc..b6ae798 100644 --- a/test/live_select_test.exs +++ b/test/live_select_test.exs @@ -639,6 +639,28 @@ defmodule LiveSelectTest do assert tag =~ "with custom slot" end + test "selection can be updated from the form", %{conn: conn} do + stub_options( + A: 1, + B: 2, + C: 3 + ) + + {:ok, live, _html} = live(conn, "/") + + type(live, "ABC") + + select_nth_option(live, 2) + + assert_selected(live, :B, 2) + + send_update(live, + field: Phoenix.Component.to_form(%{"city_search" => 1}, as: :my_form)[:city_search] + ) + + assert_selected_static(live, :A, 1) + end + for style <- [:daisyui, :tailwind, :none, nil] do @style style