From fa4bbd944c23b296a3c080f47fdaf942e5334b78 Mon Sep 17 00:00:00 2001 From: thanos Date: Wed, 1 Oct 2025 06:06:01 -0400 Subject: [PATCH 1/8] added the handling for embeds_one to the InlineCRUD field --- lib/backpex/fields/inline_crud.ex | 63 +++++++++++++++++-------------- 1 file changed, 35 insertions(+), 28 deletions(-) diff --git a/lib/backpex/fields/inline_crud.ex b/lib/backpex/fields/inline_crud.ex index 9fca07f26..853540aa1 100644 --- a/lib/backpex/fields/inline_crud.ex +++ b/lib/backpex/fields/inline_crud.ex @@ -2,7 +2,7 @@ defmodule Backpex.Fields.InlineCRUD do @config_schema [ type: [ doc: "The type of the field.", - type: {:in, [:embed, :assoc]}, + type: {:in, [:embed, :assoc, :embed_one]}, required: true ], child_fields: [ @@ -32,7 +32,7 @@ defmodule Backpex.Fields.InlineCRUD do > > Everything is currently handled by plain text input. - ### EmbedsMany + ### EmbedsMany and EmbedsOne The field in the migration must be of type `:map`. You also need to use ecto's `cast_embed/2` in the changeset. @@ -97,6 +97,10 @@ defmodule Backpex.Fields.InlineCRUD do @impl Backpex.Field def render_value(assigns) do + assigns = + assigns + |> assign(:value, if(assigns[:field_options].type == :embed_one, do: [assigns[:value]], else: assigns[:value])) + ~H"""
@@ -157,34 +161,37 @@ defmodule Backpex.Fields.InlineCRUD do phx-throttle={Backpex.Field.throttle(child_field_options, assigns)} /> - -
- -
+ <%= if @field_options.type != :embed_one do %> +
+ +
+ <% end %> - - + <%= if @field_options.type != :embed_one do %> + + <% end %> - - + <%= if @field_options.type != :embed_one do %> + + <% end %> <%= if help_text = Backpex.Field.help_text(@field_options, assigns) do %> {help_text} <% end %> @@ -195,7 +202,7 @@ defmodule Backpex.Fields.InlineCRUD do @impl Backpex.Field def association?({_name, %{type: :assoc}} = _field), do: true - def association?({_name, %{type: :embed}} = _field), do: false + def association?({_name, %{type: _}} = _field), do: false @impl Backpex.Field def schema({name, _field_options}, schema) do From 0e1c9e399d3005dd45ac990bcb7c0ea767ae0811 Mon Sep 17 00:00:00 2001 From: thanos Date: Sat, 18 Oct 2025 09:25:00 -0400 Subject: [PATCH 2/8] added demo based on producst as discussed in #1583 --- .tool-versions | 2 -- demo/lib/demo/ecto_factory.ex | 4 ++++ demo/lib/demo/product.ex | 14 ++++++++++++ demo/lib/demo_web/live/product_live.ex | 19 ++++++++++++++++ .../20251018130237_products_add_more_info.exs | 9 ++++++++ lib/backpex/fields/inline_crud.ex | 22 ++++++++++++++----- 6 files changed, 62 insertions(+), 8 deletions(-) delete mode 100644 .tool-versions create mode 100644 demo/priv/repo/migrations/20251018130237_products_add_more_info.exs diff --git a/.tool-versions b/.tool-versions deleted file mode 100644 index 0583ee187..000000000 --- a/.tool-versions +++ /dev/null @@ -1,2 +0,0 @@ -erlang 28.0.4 -elixir 1.18.4 diff --git a/demo/lib/demo/ecto_factory.ex b/demo/lib/demo/ecto_factory.ex index c06a08114..38dff94eb 100644 --- a/demo/lib/demo/ecto_factory.ex +++ b/demo/lib/demo/ecto_factory.ex @@ -61,6 +61,10 @@ defmodule Demo.EctoFactory do quantity: Enum.random(0..1_000), manufacturer: "https://example.com/", price: Enum.random(50..5_000_000), + more_info: %{ + weight: Enum.random(1..100), + goes_well_with: Faker.Food.description() + }, suppliers: build_list(Enum.random(0..5), :supplier), short_links: build_list(Enum.random(0..5), :short_link) } diff --git a/demo/lib/demo/product.ex b/demo/lib/demo/product.ex index 325c577d9..85dddc93d 100644 --- a/demo/lib/demo/product.ex +++ b/demo/lib/demo/product.ex @@ -19,6 +19,11 @@ defmodule Demo.Product do currency: :EUR, opts: [separator: ".", delimiter: ",", symbol_on_right: true, symbol_space: true] + embeds_one :more_info, MoreInfo do + field :weight, :integer + field :goes_well_with, :string + end + has_many :suppliers, Supplier, on_replace: :delete, on_delete: :delete_all has_many :short_links, ShortLink, on_replace: :delete, on_delete: :delete_all, foreign_key: :product_id @@ -31,6 +36,9 @@ defmodule Demo.Product do def changeset(product, attrs, _metadata \\ []) do product |> cast(attrs, @required_fields ++ @optional_fields) + |> cast_embed(:more_info, + with: &more_info_changeset/2 + ) |> cast_assoc(:suppliers, with: &Demo.Supplier.changeset/2, sort_param: :suppliers_order, @@ -44,4 +52,10 @@ defmodule Demo.Product do |> validate_required(@required_fields) |> validate_length(:images, max: 2) end + + def more_info_changeset(more_info, attrs) do + more_info + |> cast(attrs, [ :weight, :goes_well_with]) + |> validate_required([:weight]) + end end diff --git a/demo/lib/demo_web/live/product_live.ex b/demo/lib/demo_web/live/product_live.ex index 1465bceb3..481684e34 100644 --- a/demo/lib/demo_web/live/product_live.ex +++ b/demo/lib/demo_web/live/product_live.ex @@ -85,6 +85,25 @@ defmodule DemoWeb.ProductLive do label: "Price", align: :right }, + more_info: %{ + module: Backpex.Fields.InlineCRUD, + label: "More Info", + type: :embed_one, + except: [:index], + child_fields: [ + + weight: %{ + module: Backpex.Fields.Text, + label: "Ave. Weight (kg)" + }, + goes_well_with: %{ + module: Backpex.Fields.Textarea, + label: "Goes well with", + input_type: :textarea, + rows: 5 + } + ], + }, suppliers: %{ module: Backpex.Fields.InlineCRUD, label: "Suppliers", diff --git a/demo/priv/repo/migrations/20251018130237_products_add_more_info.exs b/demo/priv/repo/migrations/20251018130237_products_add_more_info.exs new file mode 100644 index 000000000..0669b52d0 --- /dev/null +++ b/demo/priv/repo/migrations/20251018130237_products_add_more_info.exs @@ -0,0 +1,9 @@ +defmodule Demo.Repo.Migrations.ProductsAddMoreInfo do + use Ecto.Migration + + def change do + alter table(:products) do + add :more_info, :map + end + end +end diff --git a/lib/backpex/fields/inline_crud.ex b/lib/backpex/fields/inline_crud.ex index 853540aa1..48a7bafd5 100644 --- a/lib/backpex/fields/inline_crud.ex +++ b/lib/backpex/fields/inline_crud.ex @@ -1,7 +1,7 @@ defmodule Backpex.Fields.InlineCRUD do @config_schema [ type: [ - doc: "The type of the field.", + doc: "The type of the field. One of `:embed`, `:embed_one` or `:assoc`.", type: {:in, [:embed, :assoc, :embed_one]}, required: true ], @@ -20,7 +20,7 @@ defmodule Backpex.Fields.InlineCRUD do ] @moduledoc """ - A field to handle inline CRUD operations. It can be used with either an `embeds_many` or `has_many` (association) type column. + A field to handle inline CRUD operations. It can be used with either an `embeds_many`, `embeds_one`, or `has_many` (association) type column. ## Field-specific options @@ -43,8 +43,8 @@ defmodule Backpex.Fields.InlineCRUD do ... |> cast_embed(:your_field, with: &your_field_changeset/2, - sort_param: :your_field_order, - drop_param: :your_field_delete + sort_param: :your_field_order, # not required for embeds_one + drop_param: :your_field_delete # not required for embeds_one ) ... end @@ -99,7 +99,10 @@ defmodule Backpex.Fields.InlineCRUD do def render_value(assigns) do assigns = assigns - |> assign(:value, if(assigns[:field_options].type == :embed_one, do: [assigns[:value]], else: assigns[:value])) + |> assign( + :value, + if(assigns[:field_options].type == :embed_one, do: [get_value(assigns, :value)], else: assigns[:value]) + ) ~H"""
@@ -202,7 +205,7 @@ defmodule Backpex.Fields.InlineCRUD do @impl Backpex.Field def association?({_name, %{type: :assoc}} = _field), do: true - def association?({_name, %{type: _}} = _field), do: false + def association?({_name, %{type: _type}} = _field), do: false @impl Backpex.Field def schema({name, _field_options}, schema) do @@ -218,4 +221,11 @@ defmodule Backpex.Fields.InlineCRUD do do: input_type defp input_type(_child_field_options), do: :text + + defp get_value(assigns, field) do + case Map.get(assigns, field, %{}) do + nil -> %{} + value -> value + end + end end From 6b6f4f9de460dbe9629372a48fe71f565c3c7a5f Mon Sep 17 00:00:00 2001 From: thanos Date: Sat, 18 Oct 2025 09:54:36 -0400 Subject: [PATCH 3/8] fixed format --- .tool-versions | 2 +- demo/lib/demo/product.ex | 4 ++-- demo/lib/demo_web/live/product_live.ex | 33 +++++++++++++------------- 3 files changed, 19 insertions(+), 20 deletions(-) diff --git a/.tool-versions b/.tool-versions index b2f3ccbc5..bf44d4afd 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1,2 +1,2 @@ erlang 28.1 -elixir 1.18.4 +elixir 1.18.4-otp-28 diff --git a/demo/lib/demo/product.ex b/demo/lib/demo/product.ex index 1202d8785..cb4d9bc6a 100644 --- a/demo/lib/demo/product.ex +++ b/demo/lib/demo/product.ex @@ -53,7 +53,7 @@ defmodule Demo.Product do def more_info_changeset(more_info, attrs) do more_info - |> cast(attrs, [ :weight, :goes_well_with]) + |> cast(attrs, [:weight, :goes_well_with]) |> validate_required([:weight]) - end + end end diff --git a/demo/lib/demo_web/live/product_live.ex b/demo/lib/demo_web/live/product_live.ex index 2007f2261..38c8ef431 100644 --- a/demo/lib/demo_web/live/product_live.ex +++ b/demo/lib/demo_web/live/product_live.ex @@ -86,23 +86,22 @@ defmodule DemoWeb.ProductLive do align: :right }, more_info: %{ - module: Backpex.Fields.InlineCRUD, - label: "More Info", - type: :embed_one, - except: [:index], - child_fields: [ - - weight: %{ - module: Backpex.Fields.Text, - label: "Ave. Weight (kg)" - }, - goes_well_with: %{ - module: Backpex.Fields.Textarea, - label: "Goes well with", - input_type: :textarea, - rows: 5 - } - ], + module: Backpex.Fields.InlineCRUD, + label: "More Info", + type: :embed_one, + except: [:index], + child_fields: [ + weight: %{ + module: Backpex.Fields.Text, + label: "Ave. Weight (kg)" + }, + goes_well_with: %{ + module: Backpex.Fields.Textarea, + label: "Goes well with", + input_type: :textarea, + rows: 5 + } + ] }, suppliers: %{ module: Backpex.Fields.InlineCRUD, From 5fe0ef7c42b28a62e9e640d222c548af4261d093 Mon Sep 17 00:00:00 2001 From: thanos Date: Sun, 19 Oct 2025 21:54:41 -0400 Subject: [PATCH 4/8] Revert .tool-versions change --- .tool-versions | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.tool-versions b/.tool-versions index bf44d4afd..b2f3ccbc5 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1,2 +1,2 @@ erlang 28.1 -elixir 1.18.4-otp-28 +elixir 1.18.4 From 5d49592ac969f297a94fa71f978514b512c0ab54 Mon Sep 17 00:00:00 2001 From: thanos Date: Fri, 5 Dec 2025 22:52:28 -0500 Subject: [PATCH 5/8] removed input_type from textarea- not sure why it was there --- demo/lib/demo_web/live/product_live.ex | 1 - 1 file changed, 1 deletion(-) diff --git a/demo/lib/demo_web/live/product_live.ex b/demo/lib/demo_web/live/product_live.ex index 5cddd6aa7..d3b133264 100644 --- a/demo/lib/demo_web/live/product_live.ex +++ b/demo/lib/demo_web/live/product_live.ex @@ -99,7 +99,6 @@ defmodule DemoWeb.ProductLive do goes_well_with: %{ module: Backpex.Fields.Textarea, label: "Goes well with", - input_type: :textarea, rows: 5 } ], From ee8287feb0da61802371d780f9c9ad418134242f Mon Sep 17 00:00:00 2001 From: thanos Date: Fri, 5 Dec 2025 22:56:38 -0500 Subject: [PATCH 6/8] removed input_type --- demo/lib/demo_web/live/product_live.ex | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/demo/lib/demo_web/live/product_live.ex b/demo/lib/demo_web/live/product_live.ex index 721d788c2..c2ef6489f 100644 --- a/demo/lib/demo_web/live/product_live.ex +++ b/demo/lib/demo_web/live/product_live.ex @@ -86,7 +86,6 @@ defmodule DemoWeb.ProductLive do align: :right }, more_info: %{ -<<<<<<< HEAD module: Backpex.Fields.InlineCRUD, label: "More Info", type: :embed_one, @@ -99,28 +98,9 @@ defmodule DemoWeb.ProductLive do goes_well_with: %{ module: Backpex.Fields.Textarea, label: "Goes well with", - input_type: :textarea, rows: 5 } ] -======= - module: Backpex.Fields.InlineCRUD, - label: "More Info", - type: :embed_one, - except: [:index], - child_fields: [ - - weight: %{ - module: Backpex.Fields.Text, - label: "Ave. Weight (kg)" - }, - goes_well_with: %{ - module: Backpex.Fields.Textarea, - label: "Goes well with", - rows: 5 - } - ], ->>>>>>> naymspace-develop }, suppliers: %{ module: Backpex.Fields.InlineCRUD, From 6887f039d2e17fe7b5cbad57edbc262db9769343 Mon Sep 17 00:00:00 2001 From: thanos Date: Fri, 5 Dec 2025 23:05:12 -0500 Subject: [PATCH 7/8] removed unsued input_type function --- lib/backpex/fields/inline_crud.ex | 5 ----- 1 file changed, 5 deletions(-) diff --git a/lib/backpex/fields/inline_crud.ex b/lib/backpex/fields/inline_crud.ex index aaede2348..f9ee5c315 100644 --- a/lib/backpex/fields/inline_crud.ex +++ b/lib/backpex/fields/inline_crud.ex @@ -240,11 +240,6 @@ defmodule Backpex.Fields.InlineCRUD do defp child_field_class(%{class: class} = _child_field_options, _assigns) when is_binary(class), do: class defp child_field_class(_child_field_options, _assigns), do: "flex-1" - defp input_type(%{input_type: input_type} = _child_field_options) when input_type in [:text, :textarea], - do: input_type - - defp input_type(_child_field_options), do: :text - defp get_value(assigns, field) do case Map.get(assigns, field, %{}) do nil -> %{} From 36a60ce57c98f728870b487743318853d98273b9 Mon Sep 17 00:00:00 2001 From: thanos Date: Fri, 5 Dec 2025 23:20:57 -0500 Subject: [PATCH 8/8] fixed ARIA issue --- lib/backpex/fields/inline_crud.ex | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/backpex/fields/inline_crud.ex b/lib/backpex/fields/inline_crud.ex index f9ee5c315..f29be8133 100644 --- a/lib/backpex/fields/inline_crud.ex +++ b/lib/backpex/fields/inline_crud.ex @@ -197,9 +197,9 @@ defmodule Backpex.Fields.InlineCRUD do value={f_nested.index} class="hidden" /> - -
- +
+ {Backpex.__("Delete", @live_resource)} +
@@ -207,7 +207,7 @@ defmodule Backpex.Fields.InlineCRUD do
<%= if @field_options.type != :embed_one do %> - + <% end %> <%= if @field_options.type != :embed_one do %>