diff --git a/demo/lib/demo_web/live/post_live.ex b/demo/lib/demo_web/live/post_live.ex index ebf78aa06..40be4dd20 100644 --- a/demo/lib/demo_web/live/post_live.ex +++ b/demo/lib/demo_web/live/post_live.ex @@ -110,7 +110,7 @@ defmodule DemoWeb.PostLive do module: Backpex.Fields.Textarea, label: "Body", rows: 10, - except: [:index, :resource_action], + except: [:index], align_label: :center }, published: %{ @@ -123,7 +123,7 @@ defmodule DemoWeb.PostLive do module: Backpex.Fields.Boolean, label: "Show likes", select: dynamic([post: p], fragment("? > 0", p.likes)), - except: [:index, :resource_action, :show] + except: [:index, :show] }, likes: %{ module: Backpex.Fields.Number, diff --git a/demo/lib/demo_web/live/product_live.ex b/demo/lib/demo_web/live/product_live.ex index a3350d7e2..1465bceb3 100644 --- a/demo/lib/demo_web/live/product_live.ex +++ b/demo/lib/demo_web/live/product_live.ex @@ -51,7 +51,7 @@ defmodule DemoWeb.ProductLive do assigns -> ~H"

{Backpex.HTML.pretty_value(@value)}

" end, - except: [:index, :resource_action] + except: [:index] }, name: %{ module: Backpex.Fields.Text, diff --git a/demo/lib/demo_web/live/short_link_live.ex b/demo/lib/demo_web/live/short_link_live.ex index 14e9e3da5..c774a21d0 100644 --- a/demo/lib/demo_web/live/short_link_live.ex +++ b/demo/lib/demo_web/live/short_link_live.ex @@ -22,6 +22,7 @@ defmodule DemoWeb.ShortLinkLive do def can?(_assigns, :delete, _item), do: false def can?(_assigns, _action, _item), do: true + @impl Backpex.LiveResource def return_to(_socket, _assigns, :edit, _form_action, _item) do # since the primary key might be updated, we go to the index page ~p"/admin/short-links" diff --git a/guides/fields/visibility.md b/guides/fields/visibility.md index a870da1f3..8ee90bb11 100644 --- a/guides/fields/visibility.md +++ b/guides/fields/visibility.md @@ -6,7 +6,7 @@ You can change the visibility of fields in certain views. You can use the `only` and `except` options to define the views where a field should be visible. The `only` option will show the field only in the specified views, while the `except` option will show the field in all views except the specified ones. The options have to be a list of view names. -The following values are supported: `:new`, `:edit`, `:show`, `:index` and `:resource_action`. +The following values are supported: `:new`, `:edit`, `:show` and `:index`. ```elixir # in your resource configuration file diff --git a/guides/filter/how-to-add-a-filter.md b/guides/filter/how-to-add-a-filter.md index eb3d1a79b..a03d2f5c0 100644 --- a/guides/filter/how-to-add-a-filter.md +++ b/guides/filter/how-to-add-a-filter.md @@ -27,7 +27,7 @@ defmodule MyAppWeb.Filters.PostCategorySelect do def prompt, do: "Select category ..." @impl Backpex.Filters.Select - def options do + def options(_assigns) do query = from p in Post, join: c in Category, diff --git a/guides/live_resource/navigation.md b/guides/live_resource/navigation.md index 59de432f1..75ad800aa 100644 --- a/guides/live_resource/navigation.md +++ b/guides/live_resource/navigation.md @@ -34,4 +34,4 @@ When working with forms (in `:new` or `:edit` live actions), the following form - `:save` - When a form is successfully submitted aka the "Save" button was clicked - `:cancel` - When a form submission is canceled aka the "Cancel" button was clicked -For all other live actions, the form_action will be `nil`. \ No newline at end of file +For all other live actions, the form_action will be `nil`. diff --git a/guides/upgrading/v0.15.md b/guides/upgrading/v0.15.md index 94ef7ad4e..8a6557d65 100644 --- a/guides/upgrading/v0.15.md +++ b/guides/upgrading/v0.15.md @@ -54,3 +54,34 @@ Note that it is now also possible to configure the layout as a function: use Backpex.LiveResource, layout: &MyAppWeb.admin/1 ``` + +## Resource and adapter functions have been updated + +We've updated some functions in `Backpex.Resource` and the adapter modules (`Backpex.Adapters.Ecto` and `Backpex.Adapters.Ash`) to include the fields as an additional parameter. + +The following functions are affected: + +`Backpex.Resource`: +- `list/3` -> `Backpex.Resource.list/4` +- `count/3` -> `Backpex.Resource.count/4` +- `get/3` -> `Backpex.Resource.get/4` +- `get!/3` -> `Backpex.Resource.get!/4` + +`Backpex.Adapter` (including `Backpex.Adapters.Ecto` and `Backpex.Adapters.Ash`): +- `list/3` -> `c:Backpex.Adapter.list/4` +- `count/3` -> `c:Backpex.Adapter.count/4` +- `get/3` -> `c:Backpex.Adapter.get/4` + +For example: + +```elixir +# before +Resource.get(primary_value, socket.assigns, live_resource) +# after +Resource.get(primary_value, fields, socket.assigns, live_resource) +``` + +## `:only`/`:except` field option changes + +You no longer need to pass `:resource_action` in addition to `:index` to the fields `:only`/`:except` option. +Before, it was needed to make fields visible behind the backdrop of the resource action modal. diff --git a/guides/upgrading/v0.7.md b/guides/upgrading/v0.7.md index dc7df0cb6..65bbba358 100644 --- a/guides/upgrading/v0.7.md +++ b/guides/upgrading/v0.7.md @@ -39,10 +39,10 @@ end We have updated certain functions in `Backpex.Resource`. The following functions are affected: -- `Backpex.Resource.update/6` (`update/5` before) -- [`Backpex.Resource.insert/6`]() (`insert/5` before) -- [`Backpex.Resource.change/7`]() -- [`Backpex.Resource.put_assocs/2`]() (has been removed) +- `update/6` (`update/5` before) +- `insert/6` (`insert/5` before) +- `change/7` +- `put_assocs/2` (has been removed) If you call one of these functions in your application, you will probably need to update the function call. @@ -85,4 +85,4 @@ def label(_assigns, _item) do end ``` -Read more about the new `item` parameter in [the item action guide](/guides/actions/item-actions.md#implementing-an-item-action). \ No newline at end of file +Read more about the new `item` parameter in [the item action guide](/guides/actions/item-actions.md#implementing-an-item-action). diff --git a/lib/backpex/adapter.ex b/lib/backpex/adapter.ex index 274783dca..cad675185 100644 --- a/lib/backpex/adapter.ex +++ b/lib/backpex/adapter.ex @@ -27,41 +27,51 @@ defmodule Backpex.Adapter do Should return `nil` if no result was found. """ - @callback get(term(), map(), module()) :: {:ok, struct() | nil} | {:error, term()} + @callback get(primary_value :: term(), fields :: list(), assigns :: map(), live_resource :: module()) :: + {:ok, struct() | nil} | {:error, term()} @doc """ Returns a list of items by given criteria. """ - @callback list(keyword(), map(), module()) :: {:ok, list()} + @callback list(criteria :: keyword(), fields :: list(), assigns :: map(), live_resource :: module()) :: {:ok, list()} @doc """ Gets the total count of the current live_resource. Possibly being constrained the item query and the search- and filter options. """ - @callback count(keyword(), map(), module()) :: {:ok, non_neg_integer()} + @callback count(criteria :: keyword(), fields :: list(), assigns :: map(), live_resource :: module()) :: + {:ok, non_neg_integer()} @doc """ Inserts given item. """ - @callback insert(struct(), module()) :: {:ok, struct()} | {:error, term()} + @callback insert(item :: struct(), live_resource :: module()) :: {:ok, struct()} | {:error, term()} @doc """ Updates given item. """ - @callback update(struct(), module()) :: {:ok, struct()} | {:error, term()} + @callback update(item :: struct(), live_resource :: module()) :: {:ok, struct()} | {:error, term()} @doc """ Updates given items. """ - @callback update_all(list(struct()), keyword(), module()) :: {:ok, non_neg_integer()} + @callback update_all(items :: list(struct()), updates :: keyword(), live_resource :: module()) :: + {:ok, non_neg_integer()} @doc """ Applies a change to a given item. """ - @callback change(struct(), map(), term(), list(), module(), keyword()) :: Ecto.Changeset.t() + @callback change( + item :: struct(), + attrs :: map(), + fields :: term(), + assigns :: list(), + live_resource :: module(), + opts :: keyword() + ) :: Ecto.Changeset.t() @doc """ Deletes multiple items. """ - @callback delete_all(list(struct()), module()) :: {:ok, term()} | {:error, term()} + @callback delete_all(items :: list(struct()), live_resource :: module()) :: {:ok, term()} | {:error, term()} end diff --git a/lib/backpex/adapters/ash.ex b/lib/backpex/adapters/ash.ex index ab7d259d1..8e501e4e1 100644 --- a/lib/backpex/adapters/ash.ex +++ b/lib/backpex/adapters/ash.ex @@ -29,7 +29,7 @@ if Code.ensure_loaded?(Ash) do Returns `nil` if no result was found. """ @impl Backpex.Adapter - def get(primary_value, _assigns, live_resource) do + def get(primary_value, _fields, _assigns, live_resource) do resource = live_resource.adapter_config(:resource) primary_key = live_resource.config(:primary_key) @@ -42,7 +42,7 @@ if Code.ensure_loaded?(Ash) do Returns a list of items by given criteria. """ @impl Backpex.Adapter - def list(_criteria, _assigns, live_resource) do + def list(_criteria, _fields, _assigns, live_resource) do live_resource.adapter_config(:resource) |> Ash.read() end @@ -51,7 +51,7 @@ if Code.ensure_loaded?(Ash) do Returns the number of items matching the given criteria. """ @impl Backpex.Adapter - def count(_criteria, _assigns, live_resource) do + def count(_criteria, _fields, _assigns, live_resource) do live_resource.adapter_config(:resource) |> Ash.count() end diff --git a/lib/backpex/adapters/ecto.ex b/lib/backpex/adapters/ecto.ex index 3c4b1b982..b76425dc9 100644 --- a/lib/backpex/adapters/ecto.ex +++ b/lib/backpex/adapters/ecto.ex @@ -75,10 +75,10 @@ defmodule Backpex.Adapters.Ecto do Gets a database record with the given primary key value. """ @impl Backpex.Adapter - def get(primary_value, assigns, live_resource) do + def get(primary_value, fields, assigns, live_resource) do repo = live_resource.adapter_config(:repo) - record_query(primary_value, assigns, live_resource) + record_query(primary_value, assigns, fields, live_resource) |> repo.one() |> then(fn result -> {:ok, result} end) end @@ -87,10 +87,10 @@ defmodule Backpex.Adapters.Ecto do Returns a list of items by given criteria. """ @impl Backpex.Adapter - def list(criteria, assigns, live_resource) do + def list(criteria, fields, assigns, live_resource) do repo = live_resource.adapter_config(:repo) - list_query(criteria, assigns, live_resource) + list_query(criteria, fields, assigns, live_resource) |> repo.all() |> then(fn items -> {:ok, items} end) end @@ -99,10 +99,10 @@ defmodule Backpex.Adapters.Ecto do Returns the number of items matching the given criteria. """ @impl Backpex.Adapter - def count(criteria, assigns, live_resource) do + def count(criteria, fields, assigns, live_resource) do repo = live_resource.adapter_config(:repo) - list_query(criteria, assigns, live_resource) + list_query(criteria, fields, assigns, live_resource) |> exclude(:preload) |> exclude(:select) |> subquery() @@ -115,11 +115,10 @@ defmodule Backpex.Adapters.Ecto do TODO: Should be private. """ - def list_query(criteria, assigns, live_resource) do + def list_query(criteria, fields, assigns, live_resource) do schema = live_resource.adapter_config(:schema) item_query = live_resource.adapter_config(:item_query) full_text_search = live_resource.config(:full_text_search) - fields = live_resource.validated_fields() associations = associations(fields, schema) schema @@ -350,10 +349,9 @@ defmodule Backpex.Adapters.Ecto do # --- PRIVATE - defp record_query(primary_value, assigns, live_resource) do + defp record_query(primary_value, assigns, fields, live_resource) do schema = live_resource.adapter_config(:schema) item_query = live_resource.adapter_config(:item_query) - fields = live_resource.validated_fields() schema_name = name_by_schema(schema) primary_key = live_resource.config(:primary_key) primary_type = schema.__schema__(:type, primary_key) diff --git a/lib/backpex/field.ex b/lib/backpex/field.ex index 552183acd..89bb3a0fb 100644 --- a/lib/backpex/field.ex +++ b/lib/backpex/field.ex @@ -93,11 +93,11 @@ defmodule Backpex.Field do ], only: [ doc: "Define the only views where this field should be visible.", - type: {:list, {:in, [:new, :edit, :show, :index, :resource_action]}} + type: {:list, {:in, [:new, :edit, :show, :index]}} ], except: [ doc: "Define the views where this field should not be visible.", - type: {:list, {:in, [:new, :edit, :show, :index, :resource_action]}} + type: {:list, {:in, [:new, :edit, :show, :index]}} ], translate_error: [ doc: """ @@ -398,7 +398,7 @@ defmodule Backpex.Field do Handles index editable. """ def handle_index_editable(socket, value, change) do - %{assigns: %{item: item, live_resource: live_resource, fields: fields} = assigns} = socket + %{assigns: %{item: item, fields: fields, live_resource: live_resource} = assigns} = socket if not live_resource.can?(assigns, :edit, item) do raise Backpex.ForbiddenError diff --git a/lib/backpex/fields/has_many_through.ex b/lib/backpex/fields/has_many_through.ex index da63984d5..04f1ebb87 100644 --- a/lib/backpex/fields/has_many_through.ex +++ b/lib/backpex/fields/has_many_through.ex @@ -154,16 +154,10 @@ defmodule Backpex.Fields.HasManyThrough do - - @@ -171,7 +165,7 @@ defmodule Backpex.Fields.HasManyThrough do - - -
+ {label} + {label}
+ <.live_component id={"child_table_#{name}_#{index}"} module={field_options.module} @@ -183,7 +177,7 @@ defmodule Backpex.Fields.HasManyThrough do {assigns} /> + <.live_component id={"pivot_table_#{name}_#{index}"} module={field_options.module} @@ -252,13 +246,13 @@ defmodule Backpex.Fields.HasManyThrough do
{label} {label} @@ -271,7 +265,7 @@ defmodule Backpex.Fields.HasManyThrough do :for={{listable, index} <- Enum.with_index(@listables)} class="border-b-[1px] border-base-content/10 last:border-b-0" > - + <.live_component id={"child_table_#{name}_#{index}"} module={field_options.module} @@ -281,7 +275,7 @@ defmodule Backpex.Fields.HasManyThrough do {assigns} /> + <.live_component id={"pivot_table_#{name}_#{index}"} module={field_options.module} @@ -414,7 +408,11 @@ defmodule Backpex.Fields.HasManyThrough do {:noreply, socket} end - defp action_fields(fields, assigns, action), do: LiveResource.filtered_fields_by_action(fields, assigns, action) + defp action_fields(fields, action) do + # Currently, the fields are only filtered by action, not by the fields `can?` option. + # See https://github.com/naymspace/backpex/pull/1271 + LiveResource.fields_by_action(fields, action) + end defp assign_fallback_child_fields(assigns) do case Map.has_key?(assigns.field_options, :child_fields) do @@ -422,7 +420,10 @@ defmodule Backpex.Fields.HasManyThrough do assigns false -> - fields = assigns.field_options.live_resource.validated_fields() + # It's currently not supported to add a `can?` options to the child fields. Therefore we + # are passing an empty map as the assigns. + # See https://github.com/naymspace/backpex/pull/1271 + fields = assigns.field_options.live_resource.fields(:index, %{}) new_field_options = Map.put(assigns.field_options, :child_fields, fields) assigns diff --git a/lib/backpex/filters/select.ex b/lib/backpex/filters/select.ex index 051b780c6..b0174a919 100644 --- a/lib/backpex/filters/select.ex +++ b/lib/backpex/filters/select.ex @@ -1,6 +1,6 @@ defmodule Backpex.Filters.Select do @moduledoc """ - Tbe select filter renders a select box for the implemented `options/0` and `prompt/0` callbacks. The `prompt/0` callback defines the key for the `nil` value added as first option. + The select filter renders a select box for the implemented `options/1` and `prompt/0` callbacks. The `prompt/0` callback defines the key for the `nil` value added as first option. See the following example for an implementation of an event status filter. @@ -14,7 +14,7 @@ defmodule Backpex.Filters.Select do def prompt, do: "Select an option..." @impl Backpex.Filters.Select - def options, do: [ + def options(_assigns), do: [ {"Open", :open}, {"Close", :close}, ] diff --git a/lib/backpex/html/resource.ex b/lib/backpex/html/resource.ex index dcd4500f6..7ba505b53 100644 --- a/lib/backpex/html/resource.ex +++ b/lib/backpex/html/resource.ex @@ -102,7 +102,7 @@ defmodule Backpex.HTML.Resource do attr :fields, :list, required: true, doc: "list of all fields provided by the resource configuration" def resource_field(assigns) do - %{name: name, item: item, fields: fields, live_resource: live_resource} = assigns + %{name: name, item: item, live_resource: live_resource, fields: fields} = assigns {_name, field_options} = field = Enum.find(fields, fn {field_name, _field_options} -> field_name == name end) diff --git a/lib/backpex/html/resource/resource_index.html.heex b/lib/backpex/html/resource/resource_index.html.heex index 922639564..0e3fde187 100644 --- a/lib/backpex/html/resource/resource_index.html.heex +++ b/lib/backpex/html/resource/resource_index.html.heex @@ -12,7 +12,7 @@ id={:modal} live_resource={@live_resource} action_type={:resource} - {Map.drop(assigns, [:socket, :flash])} + {Map.drop(assigns, [:socket, :flash, :fields])} /> diff --git a/lib/backpex/live_components/form_component.ex b/lib/backpex/live_components/form_component.ex index 3fe455173..bdf7cacf0 100644 --- a/lib/backpex/live_components/form_component.ex +++ b/lib/backpex/live_components/form_component.ex @@ -21,11 +21,26 @@ defmodule Backpex.FormComponent do |> ok() end + # item action defp update_assigns(%{assigns: %{action_type: :item}} = socket) do + %{action_to_confirm: action_to_confirm} = socket.assigns + socket - |> assign_fields() + |> assign_new(:fields, fn -> action_to_confirm.module.fields() end) + |> assign(:save_label, action_to_confirm.module.confirm_label(socket.assigns)) end + # resource action + defp update_assigns(%{assigns: %{action_type: :resource}} = socket) do + %{resource_action: resource_action} = socket.assigns + + socket + |> assign_new(:fields, fn -> resource_action.module.fields() end) + |> assign(:save_label, ResourceAction.name(resource_action, :label)) + |> maybe_assign_uploads() + end + + # default form defp update_assigns(%{assigns: assigns} = socket) do socket |> apply_action(assigns.live_action) @@ -41,26 +56,12 @@ defmodule Backpex.FormComponent do assign_new(socket, :removed_uploads, fn -> Keyword.new() end) end - defp assign_fields(%{assigns: %{action_to_confirm: action_to_confirm}} = socket) do - socket - |> assign_new(:fields, fn -> action_to_confirm.module.fields() end) - |> assign(:save_label, action_to_confirm.module.confirm_label(socket.assigns)) - end - defp apply_action(socket, action) when action in [:edit, :new] do socket |> assign(:save_label, Backpex.__("Save", socket.assigns.live_resource)) |> maybe_assign_continue_label() end - defp apply_action(socket, :resource_action) do - %{assigns: %{resource_action: resource_action}} = socket - - socket - |> assign(:save_label, ResourceAction.name(resource_action, :label)) - |> assign(:fields, resource_action.module.fields()) - end - defp maybe_assign_continue_label(socket) do case socket.assigns.live_resource.config(:save_and_continue_button?) do true -> assign(socket, :continue_label, Backpex.__("Save & Continue editing", socket.assigns.live_resource)) @@ -77,7 +78,6 @@ defmodule Backpex.FormComponent do def handle_event("validate", %{"change" => change, "_target" => target}, %{assigns: %{action_type: :item}} = socket) do %{assigns: %{item: item, fields: fields} = assigns} = socket - changeset_function = &assigns.action_to_confirm.module.changeset/3 target = Enum.at(target, 1) @@ -105,11 +105,7 @@ defmodule Backpex.FormComponent do end def handle_event("validate", %{"change" => change, "_target" => target}, socket) do - %{ - live_resource: live_resource, - item: item, - fields: fields - } = socket.assigns + %{live_resource: live_resource, fields: fields, item: item} = socket.assigns target = Enum.at(target, 1) assocs = Map.get(socket.assigns, :assocs, []) @@ -149,7 +145,7 @@ defmodule Backpex.FormComponent do upload_key = String.to_existing_atom(upload_key) field = - socket.assigns.fields() + socket.assigns.fields |> Enum.find(fn {_name, field_options} -> Map.has_key?(field_options, :upload_key) and Map.get(field_options, :upload_key) == upload_key end) @@ -207,7 +203,7 @@ defmodule Backpex.FormComponent do defp handle_save(socket, key, params, save_type \\ "save") defp handle_save(socket, :new, params, save_type) do - %{assigns: %{live_resource: live_resource, item: item, live_action: live_action} = assigns} = socket + %{assigns: %{live_resource: live_resource, fields: fields, item: item, live_action: live_action} = assigns} = socket opts = [ assocs: Map.get(assigns, :assocs, []), @@ -219,7 +215,7 @@ defmodule Backpex.FormComponent do end ] - case Resource.insert(item, params, socket.assigns, live_resource, opts) do + case Resource.insert(item, params, fields, socket.assigns, live_resource, opts) do {:ok, item} -> return_to = return_to_path(save_type, live_resource, socket, socket.assigns, live_action, item) @@ -252,8 +248,8 @@ defmodule Backpex.FormComponent do %{ live_resource: live_resource, item: item, - fields: fields, - live_action: live_action + live_action: live_action, + fields: fields } = socket.assigns opts = [ @@ -300,10 +296,10 @@ defmodule Backpex.FormComponent do assigns: %{ live_resource: live_resource, + fields: fields, resource_action: resource_action, item: item, - return_to: return_to, - fields: fields + return_to: return_to } = assigns } = socket @@ -351,9 +347,9 @@ defmodule Backpex.FormComponent do assigns: %{ live_resource: live_resource, + fields: fields, selected_items: selected_items, action_to_confirm: action_to_confirm, - fields: fields, return_to: return_to } = assigns } = socket diff --git a/lib/backpex/live_resource.ex b/lib/backpex/live_resource.ex index 3ef39410d..8d5296f38 100644 --- a/lib/backpex/live_resource.ex +++ b/lib/backpex/live_resource.ex @@ -274,7 +274,7 @@ defmodule Backpex.LiveResource do def pubsub, do: LiveResource.pubsub(__MODULE__) - def validated_fields, do: LiveResource.validated_fields(__MODULE__) + def fields(live_action, assigns), do: LiveResource.fields(__MODULE__, live_action, assigns) @impl Backpex.LiveResource def can?(_assigns, _action, _item), do: true @@ -474,9 +474,19 @@ defmodule Backpex.LiveResource do end @doc """ - Returns the fields of the given `Backpex.LiveResource` validated against each fields config schema. + Returns the fields of the given `Backpex.LiveResource`. + + Each field is validated against each fields config schema and filtered by the `live_action` and + the fields `can?` options. """ - def validated_fields(live_resource) do + def fields(live_resource, live_action, assigns) do + live_resource + |> validated_fields() + |> fields_by_action(live_action) + |> fields_by_can(assigns) + end + + defp validated_fields(live_resource) do live_resource.fields() |> Enum.map(fn {name, options} = field -> options.module.validate_config!(field, live_resource) @@ -612,17 +622,40 @@ defmodule Backpex.LiveResource do Returns filtered fields by a certain action. ## Example - iex> Backpex.LiveResource.filtered_fields_by_action([field1: %{label: "Field1"}, field2: %{label: "Field2"}], %{}, :index) + iex> Backpex.LiveResource.fields_by_action([field1: %{label: "Field1"}, field2: %{label: "Field2"}], :index) [field1: %{label: "Field1"}, field2: %{label: "Field2"}] - iex> Backpex.LiveResource.filtered_fields_by_action([field1: %{label: "Field1", except: [:show]}, field2: %{label: "Field2"}], %{}, :show) + iex> Backpex.LiveResource.fields_by_action([field1: %{label: "Field1", except: [:show]}, field2: %{label: "Field2"}], :show) [field2: %{label: "Field2"}] - iex> Backpex.LiveResource.filtered_fields_by_action([field1: %{label: "Field1", only: [:index]}, field2: %{label: "Field2"}], %{}, :show) + iex> Backpex.LiveResource.fields_by_action([field1: %{label: "Field1", only: [:index]}, field2: %{label: "Field2"}], :show) + [field2: %{label: "Field2"}] + """ + def fields_by_action(fields, :resource_action), do: fields_by_action(fields, :index) + + def fields_by_action(fields, action) do + fields + |> Keyword.filter(fn {_name, field_options} -> + filter_field_by_action(field_options, action) + end) + end + + @doc """ + Returns filtered fields by the result of the implemented `can?` function. + + ## Example + > Backpex.LiveResource.fields_by_can([field1: %{label: "Field1"}], %{}) + [field1: %{label: "Field1"}] + > Backpex.LiveResource.fields_by_can([field1: %{label: "Field1", can?: fn _assigns -> true end}, field2: %{label: "Field2", can?: fn _assigns -> true end}], %{}) + [field1: %{label: "Field1"}, field2: %{label: "Field2"}] + > Backpex.LiveResource.fields_by_can([field1: %{label: "Field1", can?: fn _assigns -> false end}, field2: %{label: "Field2", can?: fn _assigns -> true end}], %{}) [field2: %{label: "Field2"}] + > Backpex.LiveResource.fields_by_can([field1: %{label: "Field1", can?: fn _assigns -> false end}], %{}) + [] + """ - def filtered_fields_by_action(fields, assigns, action) do + def fields_by_can(fields, assigns) do fields |> Keyword.filter(fn {_name, field_options} -> - can_view_field?(field_options, assigns) and filter_field_by_action(field_options, action) + can_view_field?(field_options, assigns) end) end @@ -692,13 +725,14 @@ defmodule Backpex.LiveResource do def build_criteria(assigns) do %{ live_resource: live_resource, - fields: fields, filters: filters, query_options: query_options, - init_order: init_order + init_order: init_order, + fields: fields } = assigns schema = live_resource.adapter_config(:schema) + field = Enum.find(fields, fn {name, _field_options} -> name == query_options.order_by end) order = diff --git a/lib/backpex/live_resource/form.ex b/lib/backpex/live_resource/form.ex index 8bf382e90..d133a421b 100644 --- a/lib/backpex/live_resource/form.ex +++ b/lib/backpex/live_resource/form.ex @@ -16,9 +16,9 @@ defmodule Backpex.LiveResource.Form do |> assign(:live_resource, live_resource) |> assign(:panels, live_resource.panels()) |> assign(:fluid?, live_resource.config(:fluid?)) + |> assign(:fields, live_resource.fields(live_action, socket.assigns)) |> assign(:params, params) |> assign(:page_title, page_title(live_resource, live_action)) - |> assign_fields(live_action) |> assign_item(live_action) |> can?(live_resource, live_action) |> assign_changeset(live_action) @@ -75,12 +75,12 @@ defmodule Backpex.LiveResource.Form do end defp assign_item(socket, :edit = _live_action) do - %{live_resource: live_resource, params: params} = socket.assigns + %{live_resource: live_resource, fields: fields, params: params} = socket.assigns backpex_id = Map.fetch!(params, "backpex_id") primary_value = URI.decode(backpex_id) - item = Resource.get!(primary_value, socket.assigns, live_resource) + item = Resource.get!(primary_value, fields, socket.assigns, live_resource) assign(socket, :item, item) end @@ -97,18 +97,10 @@ defmodule Backpex.LiveResource.Form do socket end - defp assign_fields(socket, live_action) do - fields = - socket.assigns.live_resource.validated_fields() - |> LiveResource.filtered_fields_by_action(socket.assigns, live_action) - - assign(socket, :fields, fields) - end - defp assign_changeset(socket, live_action) do %{live_resource: live_resource, item: item, fields: fields} = socket.assigns - changeset_fun = changeset_fun(live_resource, live_action) + changeset_fun = changeset_fun(live_resource, live_action) LiveResource.assign_changeset(socket, changeset_fun, item, fields, live_action) end diff --git a/lib/backpex/live_resource/index.ex b/lib/backpex/live_resource/index.ex index e37b7f535..95cd70055 100644 --- a/lib/backpex/live_resource/index.ex +++ b/lib/backpex/live_resource/index.ex @@ -24,6 +24,7 @@ defmodule Backpex.LiveResource.Index do |> assign(:live_resource, live_resource) |> assign(:panels, live_resource.panels()) |> assign(:fluid?, live_resource.config(:fluid?)) + |> assign(:fields, live_resource.fields(socket.assigns.live_action, socket.assigns)) |> assign( :create_button_label, Backpex.__({"New %{resource}", %{resource: live_resource.singular_name()}}, live_resource) @@ -334,11 +335,9 @@ defmodule Backpex.LiveResource.Index do end defp assign_active_fields(socket, session) do - fields = - socket.assigns.live_resource.validated_fields() - |> LiveResource.filtered_fields_by_action(socket.assigns, :index) + %{fields: fields, live_resource: live_resource} = socket.assigns - saved_fields = get_in(session, ["backpex", "column_toggle", "#{socket.assigns.live_resource}"]) || %{} + saved_fields = get_in(session, ["backpex", "column_toggle", "#{live_resource}"]) || %{} active_fields = Enum.map(fields, fn {name, %{label: label}} -> @@ -349,8 +348,7 @@ defmodule Backpex.LiveResource.Index do }} end) - socket - |> assign(:active_fields, active_fields) + assign(socket, :active_fields, active_fields) end defp field_active?(name, saved_fields) do @@ -362,12 +360,11 @@ defmodule Backpex.LiveResource.Index do end defp update_item(socket, item) do - %{live_resource: live_resource, items: items} = socket.assigns + %{live_resource: live_resource, fields: fields, items: items} = socket.assigns primary_value = LiveResource.primary_value(item, live_resource) primary_value_str = to_string(primary_value) - - {:ok, updated_item} = Resource.get(primary_value, socket.assigns, live_resource) + {:ok, updated_item} = Resource.get(primary_value, fields, socket.assigns, live_resource) updated_items = Enum.map(items, fn current_item -> @@ -431,12 +428,10 @@ defmodule Backpex.LiveResource.Index do end defp apply_index(socket) do - %{live_resource: live_resource, params: params} = socket.assigns + %{live_resource: live_resource, params: params, fields: fields} = socket.assigns if not live_resource.can?(socket.assigns, :index, nil), do: raise(Backpex.ForbiddenError) - fields = live_resource.validated_fields() |> LiveResource.filtered_fields_by_action(socket.assigns, :index) - per_page_options = live_resource.config(:per_page_options) per_page_default = live_resource.config(:per_page_default) init_order = live_resource.config(:init_order) @@ -451,7 +446,7 @@ defmodule Backpex.LiveResource.Index do filters: LiveResource.filter_options(valid_filter_params, filters) ] - {:ok, item_count} = Resource.count(count_criteria, socket.assigns, live_resource) + {:ok, item_count} = Resource.count(count_criteria, fields, socket.assigns, live_resource) per_page = params @@ -459,8 +454,8 @@ defmodule Backpex.LiveResource.Index do |> LiveResource.value_in_permitted_or_default(per_page_options, per_page_default) total_pages = LiveResource.calculate_total_pages(item_count, per_page) - page = params |> LiveResource.parse_integer("page", 1) |> LiveResource.validate_page(total_pages) + page = params |> LiveResource.parse_integer("page", 1) |> LiveResource.validate_page(total_pages) page_options = %{page: page, per_page: per_page} order_options = LiveResource.order_options_by_params(params, fields, init_order, socket.assigns) @@ -485,7 +480,6 @@ defmodule Backpex.LiveResource.Index do |> assign(:action_to_confirm, nil) |> assign(:selected_items, []) |> assign(:select_all, false) - |> assign(:fields, fields) |> maybe_redirect_to_default_filters() |> assign_items() |> maybe_assign_metrics() @@ -550,12 +544,7 @@ defmodule Backpex.LiveResource.Index do end defp refresh_items(socket) do - %{ - live_resource: live_resource, - params: params, - fields: fields, - query_options: query_options - } = socket.assigns + %{live_resource: live_resource, params: params, query_options: query_options, fields: fields} = socket.assigns schema = live_resource.adapter_config(:schema) filters = LiveResource.active_filters(socket.assigns) @@ -566,7 +555,7 @@ defmodule Backpex.LiveResource.Index do filters: LiveResource.filter_options(valid_filter_params, filters) ] - {:ok, item_count} = Resource.count(count_criteria, socket.assigns, live_resource) + {:ok, item_count} = Resource.count(count_criteria, fields, socket.assigns, live_resource) %{page: page, per_page: per_page} = query_options total_pages = LiveResource.calculate_total_pages(item_count, per_page) new_query_options = Map.put(query_options, :page, LiveResource.validate_page(page, total_pages)) @@ -582,9 +571,9 @@ defmodule Backpex.LiveResource.Index do defp maybe_assign_metrics(socket) do %{ live_resource: live_resource, - fields: fields, query_options: query_options, - metric_visibility: metric_visibility + metric_visibility: metric_visibility, + fields: fields } = socket.assigns repo = live_resource.adapter_config(:repo) @@ -599,7 +588,7 @@ defmodule Backpex.LiveResource.Index do filters: LiveResource.filter_options(query_options, filters) ] - query = EctoAdapter.list_query(criteria, socket.assigns, live_resource) + query = EctoAdapter.list_query(criteria, fields, socket.assigns, live_resource) case Backpex.Metric.metrics_visible?(metric_visibility, live_resource) do true -> @@ -622,8 +611,12 @@ defmodule Backpex.LiveResource.Index do end defp assign_items(socket) do - criteria = LiveResource.build_criteria(socket.assigns) - {:ok, items} = Resource.list(criteria, socket.assigns, socket.assigns.live_resource) + %{assigns: %{live_resource: live_resource, fields: fields} = assigns} = socket + + {:ok, items} = + assigns + |> LiveResource.build_criteria() + |> Resource.list(fields, assigns, live_resource) assign(socket, :items, items) end diff --git a/lib/backpex/live_resource/show.ex b/lib/backpex/live_resource/show.ex index 3a6515ae5..ba3b303c8 100644 --- a/lib/backpex/live_resource/show.ex +++ b/lib/backpex/live_resource/show.ex @@ -4,7 +4,6 @@ defmodule Backpex.LiveResource.Show do import Phoenix.Component - alias Backpex.LiveResource alias Backpex.Resource alias Backpex.Router alias Phoenix.LiveView @@ -22,9 +21,9 @@ defmodule Backpex.LiveResource.Show do |> assign(:live_resource, live_resource) |> assign(:panels, live_resource.panels()) |> assign(:fluid?, live_resource.config(:fluid?)) + |> assign(:fields, live_resource.fields(:show, socket.assigns)) |> assign(:page_title, live_resource.singular_name()) |> assign(:params, params) - |> assign_fields() |> assign_item() |> ok() end @@ -52,12 +51,10 @@ defmodule Backpex.LiveResource.Show do end defp assign_item(socket) do - %{live_resource: live_resource, params: params} = socket.assigns - + %{live_resource: live_resource, fields: fields, params: params} = socket.assigns backpex_id = Map.fetch!(params, "backpex_id") primary_value = URI.decode(backpex_id) - - item = Resource.get!(primary_value, socket.assigns, live_resource) + item = Resource.get!(primary_value, fields, socket.assigns, live_resource) if not live_resource.can?(socket.assigns, :show, item), do: raise(Backpex.ForbiddenError) @@ -65,12 +62,4 @@ defmodule Backpex.LiveResource.Show do |> assign(:item, item) |> assign(:return_to, Router.get_path(socket, live_resource, params, :show, item)) end - - defp assign_fields(socket) do - fields = - socket.assigns.live_resource.validated_fields() - |> LiveResource.filtered_fields_by_action(socket.assigns, :show) - - assign(socket, :fields, fields) - end end diff --git a/lib/backpex/resource.ex b/lib/backpex/resource.ex index 9bc355560..b62b767a4 100644 --- a/lib/backpex/resource.ex +++ b/lib/backpex/resource.ex @@ -19,20 +19,20 @@ defmodule Backpex.Resource do search: {"hello", [:title, :description]} ] """ - def list(criteria, assigns, live_resource) do + def list(criteria, fields, assigns, live_resource) do adapter = live_resource.config(:adapter) - adapter.list(criteria, assigns, live_resource) + adapter.list(criteria, fields, assigns, live_resource) end @doc """ Gets the total count of the current live_resource. Possibly being constrained the item query and the search- and filter options. """ - def count(criteria, assigns, live_resource) do + def count(criteria, fields, assigns, live_resource) do adapter = live_resource.config(:adapter) - adapter.count(criteria, assigns, live_resource) + adapter.count(criteria, fields, assigns, live_resource) end @doc """ @@ -46,17 +46,17 @@ defmodule Backpex.Resource do * `assigns` (map): The current assigns of the socket. * `live_resource` (module): The `Backpex.LiveResource` module. """ - def get(primary_value, assigns, live_resource) do + def get(primary_value, fields, assigns, live_resource) do adapter = live_resource.config(:adapter) - adapter.get(primary_value, assigns, live_resource) + adapter.get(primary_value, fields, assigns, live_resource) end @doc """ - Same as `get/3` but returns the result or raises an error. + Same as `get/4` but returns the result or raises an error. """ - def get!(primary_value, assigns, live_resource) do - case get(primary_value, assigns, live_resource) do + def get!(primary_value, fields, assigns, live_resource) do + case get(primary_value, fields, assigns, live_resource) do {:ok, nil} -> raise Backpex.NoResultsError {:ok, result} -> result {:error, _error} -> raise Backpex.NoResultsError @@ -92,11 +92,10 @@ defmodule Backpex.Resource do * `attrs` (map): A map of parameters that will be passed to the `changeset_function`. * TODO: docs """ - def insert(item, attrs, assigns, live_resource, opts) do + def insert(item, attrs, fields, assigns, live_resource, opts) do {after_save_fun, opts} = Keyword.pop(opts, :after_save_fun, &{:ok, &1}) adapter = live_resource.config(:adapter) - fields = live_resource.validated_fields() item |> change(attrs, fields, assigns, live_resource, Keyword.put(opts, :action, :insert))