diff --git a/demo/lib/demo/ecto_factory.ex b/demo/lib/demo/ecto_factory.ex index c7165a5a1..d490d33ae 100644 --- a/demo/lib/demo/ecto_factory.ex +++ b/demo/lib/demo/ecto_factory.ex @@ -65,6 +65,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 7baa1bfad..cb4d9bc6a 100644 --- a/demo/lib/demo/product.ex +++ b/demo/lib/demo/product.ex @@ -17,6 +17,11 @@ defmodule Demo.Product do field :price, Money.Ecto.Amount.Type + 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 @@ -29,6 +34,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, @@ -42,4 +50,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 e1480bb96..c2ef6489f 100644 --- a/demo/lib/demo_web/live/product_live.ex +++ b/demo/lib/demo_web/live/product_live.ex @@ -85,6 +85,23 @@ 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", + 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 a0388ec62..f29be8133 100644 --- a/lib/backpex/fields/inline_crud.ex +++ b/lib/backpex/fields/inline_crud.ex @@ -1,8 +1,8 @@ defmodule Backpex.Fields.InlineCRUD do @config_schema [ type: [ - doc: "The type of the field.", - type: {:in, [:embed, :assoc]}, + doc: "The type of the field. One of `:embed`, `:embed_one` or `:assoc`.", + type: {:in, [:embed, :assoc, :embed_one]}, required: true ], child_fields: [ @@ -23,7 +23,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 @@ -31,7 +31,11 @@ defmodule Backpex.Fields.InlineCRUD do #{NimbleOptions.docs(@config_schema)} - ### EmbedsMany + > #### Important {: .info} + > + > Everything is currently handled by plain text input. + + ### 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. @@ -42,8 +46,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 @@ -112,6 +116,13 @@ 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: [get_value(assigns, :value)], else: assigns[:value]) + ) + ~H"""