From be8bd0a868a5636181b33b670992911f4fb69a39 Mon Sep 17 00:00:00 2001 From: Edison Yap Date: Tue, 30 Mar 2021 16:47:09 +0200 Subject: [PATCH] Ability to pass LiveComponent as field This commit enables user to pass in custom LiveComponent for more complex form field. User do it like so: ``` form do field :newsletter_id, :component, render: SuggestionComponent, id: :newsletter_suggestion end ``` This creates a stateful LiveComponent that can update your form input values. This works with a few assumption: - Your component has to be stateful, so you need to pass in `:id` - Your component has to use `phx-keyup`, because `phx-change` doesn't work outside of `form`. - Your component cannot have `
`, because HTML5 spec mandates that you cannot have nested form tags. In your Suggestion component, you can show user the available suggestions, then when they click, you should handle the event and then let the FormComponent know. You do this by: > send self(), {:pick, {:your_field_name, value: value}} This will forward the request to the parent LiveView, which will then broadcast it to the FormComponent. For example, say you are editing a schema called Listing. It has a field called `newsletter_id` (belongs_to), and you want to have suggestions when user is typing so they can search in-line. You would then send it as: > send self(), {:pick, {:newsletter_id, value: 4}} This would create a `hidden_input` on your ``, which would update `:newsletter_id` to be `4`. --- lib/backoffice/live/form_component.ex | 11 +++++++++++ lib/backoffice/live/form_component.html.leex | 4 ++++ lib/backoffice/resources.ex | 6 ++++++ lib/backoffice/views/resource_view.ex | 14 +++++++------- 4 files changed, 28 insertions(+), 7 deletions(-) diff --git a/lib/backoffice/live/form_component.ex b/lib/backoffice/live/form_component.ex index 0d7945e..cddc7e0 100644 --- a/lib/backoffice/live/form_component.ex +++ b/lib/backoffice/live/form_component.ex @@ -12,9 +12,20 @@ defmodule Backoffice.FormComponent do socket |> assign(assigns) |> assign(:resource, resource) + |> assign(:hidden_fields, []) |> assign(:changeset, changeset)} end + def update(%{pick: field}, socket) do + hidden_fields = socket.assigns.hidden_fields + + socket = + socket + |> assign(:hidden_fields, Keyword.merge(hidden_fields, List.wrap(field))) + + {:ok, socket} + end + @impl true def handle_event("validate", %{"resource" => resource_params}, socket) do resource = socket.assigns.resource diff --git a/lib/backoffice/live/form_component.html.leex b/lib/backoffice/live/form_component.html.leex index 2adbcc6..4e518b6 100644 --- a/lib/backoffice/live/form_component.html.leex +++ b/lib/backoffice/live/form_component.html.leex @@ -14,6 +14,10 @@ <% end %> + <%= for {field, opts} <- @hidden_fields do %> + <%= hidden_input f, field, opts %> + <% end %> +
diff --git a/lib/backoffice/resources.ex b/lib/backoffice/resources.ex index e220cef..ddc2acf 100644 --- a/lib/backoffice/resources.ex +++ b/lib/backoffice/resources.ex @@ -101,6 +101,12 @@ defmodule Backoffice.Resources do )} end + def handle_info({field, value}, socket) do + send_update(Backoffice.FormComponent, [{:id, socket.assigns.resource.id}, {field, value}]) + + {:noreply, socket} + end + defp apply_action(socket, :new, page_opts) do socket |> assign(:form_fields, Backoffice.Resources.get_form_fields(__MODULE__, :new)) diff --git a/lib/backoffice/views/resource_view.ex b/lib/backoffice/views/resource_view.ex index f290593..004a315 100644 --- a/lib/backoffice/views/resource_view.ex +++ b/lib/backoffice/views/resource_view.ex @@ -152,18 +152,18 @@ defmodule Backoffice.ResourceView do # TODO: Would be nice to support LiveComponent for more complex component # For example, I would like to have a drop-down suggestion logic as I type. - # defp do_form_field(form, field, :component, opts) do - # component = Keyword.fetch!(opts, :component) - # opts = Keyword.merge(opts, form: form, field: field) + defp do_form_field(form, field, :component, opts) do + component = Keyword.fetch!(opts, :render) + opts = Keyword.merge(opts, value: input_value(form, field)) - # live_component(socket, component, opts) - # end + live_component(_, component, opts) + end # Q: Are there any pitfall to allowing user render fields like this? defp do_form_field(form, field, :custom, opts) do - slot = Keyword.fetch!(opts, :render) + render = Keyword.fetch!(opts, :render) - slot.(form, field) + render.(form, field) end defp do_form_field(form, field, _type, opts) do