From 9aaf1f4527dddde817b713a8a1f614d45afe5785 Mon Sep 17 00:00:00 2001 From: Phil-Bastian Berndt Date: Fri, 8 Nov 2024 18:00:38 +0100 Subject: [PATCH 01/26] introduce option validation for all backpex fields --- lib/backpex/adapters/ecto.ex | 2 +- lib/backpex/field.ex | 113 ++++++++++- lib/backpex/fields/belongs_to.ex | 47 ++++- lib/backpex/fields/boolean.ex | 20 +- lib/backpex/fields/currency.ex | 22 ++- lib/backpex/fields/date.ex | 31 ++- lib/backpex/fields/date_time.ex | 31 ++- lib/backpex/fields/has_many.ex | 82 +++++--- lib/backpex/fields/has_many_through.ex | 58 ++++-- lib/backpex/fields/inline_crud.ex | 32 ++- lib/backpex/fields/multi_select.ex | 34 ++-- lib/backpex/fields/number.ex | 26 ++- lib/backpex/fields/select.ex | 21 +- lib/backpex/fields/text.ex | 25 ++- lib/backpex/fields/textarea.ex | 17 +- lib/backpex/fields/upload.ex | 262 ++++++++++++++----------- lib/backpex/fields/url.ex | 25 ++- lib/backpex/live_resource.ex | 22 ++- lib/backpex_web.ex | 1 - 19 files changed, 634 insertions(+), 237 deletions(-) diff --git a/lib/backpex/adapters/ecto.ex b/lib/backpex/adapters/ecto.ex index c93a1cf7..5df382e5 100644 --- a/lib/backpex/adapters/ecto.ex +++ b/lib/backpex/adapters/ecto.ex @@ -343,7 +343,7 @@ defmodule Backpex.Adapters.Ecto do end defp record_query(id, schema, item_query, live_resource) do - fields = live_resource.fields() + 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 d2ef4dac..1925bee4 100644 --- a/lib/backpex/field.ex +++ b/lib/backpex/field.ex @@ -1,9 +1,85 @@ # credo:disable-for-this-file Credo.Check.Refactor.CyclomaticComplexity defmodule Backpex.Field do - @moduledoc ~S''' + @config_schema [ + module: [ + doc: "The field module.", + type: :atom, + required: true + ], + label: [ + doc: "The field label.", + type: :string, + required: true + ], + render: [ + doc: "", + type: {:fun, 1} + ], + custom_alias: [ + doc: "", + type: :atom + ], + align: [ + doc: "", + type: :atom + ], + searchable: [ + doc: "", + type: :boolean + ], + orderable: [ + doc: "", + type: :boolean + ], + visible: [ + doc: "Function to change the visibility of a field. Receives the assigns and has to return a boolean.", + type: {:fun, 1} + ], + panel: [ + doc: "", + type: :atom + ], + index_editable: [ + doc: "", + type: :boolean + ], + index_column_class: [ + doc: "", + type: :string + ], + select: [ + doc: "", + type: {:struct, Ecto.Query.DynamicExpr} + ], + only: [ + doc: "", + type: {:list, :atom} + ], + except: [ + doc: "", + type: {:list, :atom} + ], + translate_error: [ + doc: """ + Function to customize error messages for a field. The function receives the error tuple and must return a tuple + with the message and metadata. + """, + type: {:fun, 1} + ] + ] + + @moduledoc """ Behaviour implemented by all fields. - A field defines how a column is rendered on index, show and edit views. In the resource configuration file you can configure a list of fields. You may create your own field by implementing this behaviour. A field has to be a [LiveComponent](https://hexdocs.pm/phoenix_live_view/Phoenix.LiveComponent.html). + A field defines how a column is rendered on index, show and edit views. In the resource configuration file you can + configure a list of fields. You may create your own field by implementing this behaviour. A field has to be a + [LiveComponent](https://hexdocs.pm/phoenix_live_view/Phoenix.LiveComponent.html). + + ### Options + + These are general field options which can be used on every field. Check the field modules for field-specific options. + + #{NimbleOptions.docs(@config_schema)} ### Example @@ -15,7 +91,7 @@ defmodule Backpex.Field do } ] end - ''' + """ import Phoenix.Component, only: [assign: 3] @doc """ @@ -97,13 +173,40 @@ defmodule Backpex.Field do @optional_callbacks render_form_readonly: 1, render_index_form: 1 + @doc """ + Returns the default config schema. + """ + def default_config_schema(), do: @config_schema + @doc """ Defines `Backpex.Field` behaviour and provides default implementations. """ - defmacro __using__(_) do - quote do + defmacro __using__(opts) do + quote bind_quoted: [opts: opts] do + @config_schema opts[:config_schema] || [] + @before_compile Backpex.Field @behaviour Backpex.Field + + use BackpexWeb, :field + + def validate_config!({name, options} = _field, live_resource) do + field_options = Keyword.new(options) + + dbg(field_options) + + case NimbleOptions.validate(field_options, Backpex.Field.default_config_schema() ++ @config_schema) do + {:ok, validated_options} -> + validated_options + + {:error, error} -> + raise """ + Configuration error for field "#{name}" in "#{live_resource}". + + #{error.message} + """ + end + end end end diff --git a/lib/backpex/fields/belongs_to.ex b/lib/backpex/fields/belongs_to.ex index 32853c37..c92561c9 100644 --- a/lib/backpex/fields/belongs_to.ex +++ b/lib/backpex/fields/belongs_to.ex @@ -1,15 +1,44 @@ defmodule Backpex.Fields.BelongsTo do + @config_schema [ + display_field: [ + doc: "The field of the relation to be used for searching, ordering and displaying values.", + type: :atom, + required: true + ], + display_field_form: [ + doc: "Field to be used to display form values.", + type: :atom + ], + live_resource: [ + doc: "The live resource of the association. Used to generate links navigating to the association.", + type: :atom + ], + source: [ + doc: "", + type: :atom + ], + options_query: [ + doc: """ + Manipulates the list of available options in the select. + + Defaults to `fn (query, _field) -> query end` which returns all entries. + """, + type: {:fun, 2} + ], + prompt: [ + doc: "The text to be displayed when no option is selected or function that receives the assigns.", + type: :string + ] + ] + @moduledoc """ A field for handling a `belongs_to` relation. - ## Options + ## Field-specific options - * `:display_field` - The field of the relation to be used for searching, ordering and displaying values. - * `:display_field_form` - Optional field to be used to display form values. - * `:live_resource` - The live resource of the association. Used to generate links navigating to the association. - * `:options_query` - Manipulates the list of available options in the select. - Defaults to `fn (query, _field) -> query end` which returns all entries. - * `:prompt` - The text to be displayed when no option is selected or function that receives the assigns. + See `Backpex.Field` for general field options. + + #{NimbleOptions.docs(@config_schema)} ## Example @@ -26,10 +55,8 @@ defmodule Backpex.Fields.BelongsTo do ] end """ - use BackpexWeb, :field - + use Backpex.Field, config_schema: @config_schema import Ecto.Query - alias Backpex.Router @impl Phoenix.LiveComponent diff --git a/lib/backpex/fields/boolean.ex b/lib/backpex/fields/boolean.ex index 77680363..7d3c6499 100644 --- a/lib/backpex/fields/boolean.ex +++ b/lib/backpex/fields/boolean.ex @@ -1,13 +1,25 @@ defmodule Backpex.Fields.Boolean do + @config_schema [ + debounce: [ + doc: "Timeout value (in milliseconds), \"blur\" or function that receives the assigns.", + type: {:or, [:pos_integer, :string, {:fun, 1}]} + ], + throttle: [ + doc: "Timeout value (in milliseconds) or function that receives the assigns.", + type: {:or, [:pos_integer, {:fun, 1}]} + ] + ] + @moduledoc """ A field for handling a boolean value. - ## Options + ## Field-specific options + + See `Backpex.Field` for general field options. - * `:debounce` - Optional integer timeout value (in milliseconds), "blur" or function that receives the assigns. - * `:throttle` - Optional integer timeout value (in milliseconds) or function that receives the assigns. + #{NimbleOptions.docs(@config_schema)} """ - use BackpexWeb, :field + use Backpex.Field, config_schema: @config_schema @impl Backpex.Field def render_value(assigns) do diff --git a/lib/backpex/fields/currency.ex b/lib/backpex/fields/currency.ex index bfff0e6a..5ca9690b 100644 --- a/lib/backpex/fields/currency.ex +++ b/lib/backpex/fields/currency.ex @@ -1,11 +1,23 @@ defmodule Backpex.Fields.Currency do + @config_schema [ + debounce: [ + doc: "Timeout value (in milliseconds), \"blur\" or function that receives the assigns.", + type: {:or, [:pos_integer, :string, {:fun, 1}]} + ], + throttle: [ + doc: "Timeout value (in milliseconds) or function that receives the assigns.", + type: {:or, [:pos_integer, {:fun, 1}]} + ] + ] + @moduledoc """ A field for handling a currency value. - ## Options + ## Field-specific options + + See `Backpex.Field` for general field options. - * `:debounce` - Optional integer timeout value (in milliseconds), "blur" or function that receives the assigns. - * `:throttle` - Optional integer timeout value (in milliseconds) or function that receives the assigns. + #{NimbleOptions.docs(@config_schema)} ## Schema @@ -34,10 +46,8 @@ defmodule Backpex.Fields.Currency do ] end """ - use BackpexWeb, :field - + use Backpex.Field import Ecto.Query - alias Backpex.Ecto.Amount.Type @impl Backpex.Field diff --git a/lib/backpex/fields/date.ex b/lib/backpex/fields/date.ex index eca388c0..592f2042 100644 --- a/lib/backpex/fields/date.ex +++ b/lib/backpex/fields/date.ex @@ -2,16 +2,35 @@ defmodule Backpex.Fields.Date do @default_format "%Y-%m-%d" + @config_schema [ + format: [ + doc: """ + Format string which will be used to format the date time value or function that formats the date time. + + Can also be a function wich receives a `DateTime` and must return a string. + """, + type: {:or, [:string, {:fun, 1}]}, + default: @default_format + ], + debounce: [ + doc: "Timeout value (in milliseconds), \"blur\" or function that receives the assigns.", + type: {:or, [:pos_integer, :string, {:fun, 1}]} + ], + throttle: [ + doc: "Timeout value (in milliseconds) or function that receives the assigns.", + type: {:or, [:pos_integer, {:fun, 1}]} + ] + ] + # credo:disable-for-next-line Credo.Check.Readability.StrictModuleLayout @moduledoc """ A field for handling a date value. - ## Options + ## Field-specific options + + See `Backpex.Field` for general field options. - * `:format` - Format string which will be used to format the date value or function that formats the date. - Defaults to `#{@default_format}`. If a function, must receive a `Date` and return a string. - * `:debounce` - Optional integer timeout value (in milliseconds), "blur" or function that receives the assigns. - * `:throttle` - Optional integer timeout value (in milliseconds) or function that receives the assigns. + #{NimbleOptions.docs(@config_schema)} ## Examples @@ -53,7 +72,7 @@ defmodule Backpex.Fields.Date do ] end """ - use BackpexWeb, :field + use Backpex.Field @impl Backpex.Field def render_value(assigns) do diff --git a/lib/backpex/fields/date_time.ex b/lib/backpex/fields/date_time.ex index e3a73380..5331b2f2 100644 --- a/lib/backpex/fields/date_time.ex +++ b/lib/backpex/fields/date_time.ex @@ -2,16 +2,35 @@ defmodule Backpex.Fields.DateTime do @default_format "%Y-%m-%d %I:%M %p" + @config_schema [ + format: [ + doc: """ + Format string which will be used to format the date time value or function that formats the date time. + + Can also be a function wich receives a `DateTime` and must return a string. + """, + type: {:or, [:string, {:fun, 1}]}, + default: @default_format + ], + debounce: [ + doc: "Timeout value (in milliseconds), \"blur\" or function that receives the assigns.", + type: {:or, [:pos_integer, :string, {:fun, 1}]} + ], + throttle: [ + doc: "Timeout value (in milliseconds) or function that receives the assigns.", + type: {:or, [:pos_integer, {:fun, 1}]} + ] + ] + # credo:disable-for-next-line Credo.Check.Readability.StrictModuleLayout @moduledoc """ A field for handling a date time value. - ## Options + ## Field-specific options + + See `Backpex.Field` for general field options. - * `:format` - Format string which will be used to format the date time value or function that formats the date time. - Defaults to `#{@default_format}`. If a function, must receive a `DateTime` and return a string. - * `:debounce` - Optional integer timeout value (in milliseconds), "blur" or function that receives the assigns. - * `:throttle` - Optional integer timeout value (in milliseconds) or function that receives the assigns. + #{NimbleOptions.docs(@config_schema)} ## Examples @@ -53,7 +72,7 @@ defmodule Backpex.Fields.DateTime do ] end """ - use BackpexWeb, :field + use Backpex.Field @impl Backpex.Field def render_value(assigns) do diff --git a/lib/backpex/fields/has_many.ex b/lib/backpex/fields/has_many.ex index 3c9f1422..eac715f7 100644 --- a/lib/backpex/fields/has_many.ex +++ b/lib/backpex/fields/has_many.ex @@ -1,24 +1,58 @@ defmodule Backpex.Fields.HasMany do + @config_schema [ + display_field: [ + doc: "The field of the relation to be used for searching, ordering and displaying values.", + type: :atom, + required: true + ], + display_field_form: [ + doc: "The field to be used to display form values.", + type: :atom + ], + live_resource: [ + doc: "The live resource of the association.", + type: :atom + ], + link_assocs: [ + doc: "Whether to automatically generate links to the association items.", + type: :boolean, + default: true + ], + options_query: [ + doc: """ + Manipulates the list of available options in the multi select. + + Defaults to `fn (query, _field) -> query end` which returns all entries. + """, + type: {:fun, 2} + ], + prompt: [ + doc: "The text to be displayed when no options are selected or function that receives the assigns.", + type: :string, + default: Backpex.translate("Select options...") + ], + not_found_text: [ + doc: "The text to be displayed when no options are found.", + type: :string, + default: Backpex.translate("No options found") + ], + query_limit: [ + doc: "Limit passed to the query to fetch new items. Set to `nil` to have no limit.", + type: {:or, [:non_neg_integer, nil]}, + default: 10 + ] + ] + @moduledoc """ A field for handling a `has_many` or `many_to_many` relation. This field can not be orderable or searchable. - ## Options + ## Field-specific options - * `:display_field` - The field of the relation to be used for searching, ordering and displaying values. - * `:display_field_form` - Optional field to be used to display form values. - * `:live_resource` - The live resource of the association. - * `:link_assocs` - Whether to automatically generate links to the association items. - Defaults to true. - * `:options_query` - Manipulates the list of available options in the multi select. - Defaults to `fn (query, _field) -> query end` which returns all entries. - * `:prompt` - The text to be displayed when no options are selected or function that receives the assigns. - Defaults to "Select options...". - * `:not_found_text` - The text to be displayed when no options are found. - Defaults to "No options found". - * `:query_limit` - Optional limit passed to the query to fetch new items. Set to `nil` to have no limit. - Defaults to 10. + See `Backpex.Field` for general field options. + + #{NimbleOptions.docs(@config_schema)} ## Example @@ -35,7 +69,7 @@ defmodule Backpex.Fields.HasMany do ] end """ - use BackpexWeb, :field + use Backpex.Field, config_schema: @config_schema import Ecto.Query import Backpex.HTML.Form alias Backpex.Adapters.Ecto, as: EctoAdapter @@ -52,7 +86,7 @@ defmodule Backpex.Fields.HasMany do end defp apply_action(socket, :index) do - assign_new(socket, :link_assocs, fn -> link_assocs(socket.assigns.field_options) end) + assign_new(socket, :link_assocs, fn -> socket.assigns.field_options[:link_assocs] end) end defp apply_action(socket, :form) do @@ -60,7 +94,7 @@ defmodule Backpex.Fields.HasMany do socket |> assign_new(:prompt, fn -> prompt(assigns, field_options) end) - |> assign_new(:not_found_text, fn -> not_found_text(field_options) end) + |> assign_new(:not_found_text, fn -> field_options[:not_found_text] end) |> assign_new(:search_input, fn -> "" end) |> assign_new(:offset, fn -> 0 end) |> assign_new(:options_count, fn -> count_options(assigns) end) @@ -232,7 +266,7 @@ defmodule Backpex.Fields.HasMany do socket = socket - |> assign(:offset, query_limit(field_options) + offset) + |> assign(:offset, field_options[:query_limit] + offset) |> assign_options(options) {:noreply, socket} @@ -356,8 +390,7 @@ defmodule Backpex.Fields.HasMany do defp assign_options(socket, other_options \\ []) do %{assigns: %{field_options: field_options, search_input: search_input, offset: offset} = assigns} = socket - - limit = query_limit(field_options) + limit = field_options[:query_limit] options = other_options ++ options(assigns, offset: offset, limit: limit, search: search_input) @@ -505,22 +538,13 @@ defmodule Backpex.Fields.HasMany do assign(socket, :errors, translate_form_errors(form[name], translate_error_fun)) end - defp query_limit(field_options), do: Map.get(field_options, :query_limit, 10) - defp display_field_form({_name, field_options} = field), do: Map.get(field_options, :display_field_form, display_field(field)) defp prompt(assigns, field_options) do case Map.get(field_options, :prompt) do - nil -> Backpex.translate("Select options...") prompt when is_function(prompt) -> prompt.(assigns) prompt -> prompt end end - - defp not_found_text(%{not_found_text: not_found_text} = _field_options), do: not_found_text - defp not_found_text(_field_options), do: Backpex.translate("No options found") - - defp link_assocs(%{link_assocs: link_assocs} = _field_options) when is_boolean(link_assocs), do: link_assocs - defp link_assocs(_field_options), do: true end diff --git a/lib/backpex/fields/has_many_through.ex b/lib/backpex/fields/has_many_through.ex index 71c24a2a..2ef6ede3 100644 --- a/lib/backpex/fields/has_many_through.ex +++ b/lib/backpex/fields/has_many_through.ex @@ -1,4 +1,42 @@ defmodule Backpex.Fields.HasManyThrough do + @config_schema [ + display_field: [ + doc: "The field of the relation to be used for displaying options in the select.", + type: :atom, + required: true + ], + live_resource: [ + doc: """ + The corresponding live resource of the association. Used to display the title of the modal and generate defaults + for `:child_fields` fields. + """, + type: :atom, + required: true + ], + sort_by: [ + doc: """ + A list of columns by which the child element output will be sorted. The sorting takes place in ascending order. + """, + type: {:list, :atom} + ], + child_fields: [ + doc: "WIP", + type: :keyword_list + ], + pivot_fields: [ + doc: "List to map additional data of the pivot table to Backpex fields.", + type: :keyword_list + ], + options_query: [ + doc: """ + Manipulates the list of available options in the select. Can be used to select additional data for the `display_field` option or to limit the available entries.", + + Defaults to `fn (query, _field) -> query end` which returns all entries. + """, + type: {:fun, 2} + ] + ] + @moduledoc """ A field for handling a `has_many` (`through`) relation. @@ -8,15 +46,11 @@ defmodule Backpex.Fields.HasManyThrough do > > This field is in beta state. Use at your own risk. - ## Options + ## Field-specific options - * `:display_field` - The field of the relation to be used for displaying options in the select. - * `:live_resource` - The corresponding live resource of the association. Used to display the title of the modal and generate defaults for `:child_fields` fields. - * `:sort_by` - A list of columns by which the child element output will be sorted. The sorting takes place in ascending order. - * `:child_fields` - WIP - * `:pivot_fields` - List to map additional data of the pivot table to Backpex fields. - * `:options_query` - Manipulates the list of available options in the select. Can be used to select additional data for the `display_field` option or to limit the available entries. - Defaults to `fn (query, _field) -> query end` which returns all entries. + See `Backpex.Field` for general field options. + + #{NimbleOptions.docs(@config_schema)} ## Example @@ -42,13 +76,10 @@ defmodule Backpex.Fields.HasManyThrough do The field requires a [`Ecto.Schema.has_many/3`](https://hexdocs.pm/ecto/Ecto.Schema.html#has_many/3) relation with a mandatory `through` option in the main schema. Any extra column in the pivot table besides the relational id's must be mapped in the `pivot_fields` option or given a default value. """ - - use BackpexWeb, :field - + use Backpex.Field, config_schema: @config_schema import Ecto.Query import Backpex.HTML.Layout, only: [modal: 1] import PhoenixHTMLHelpers.Form, only: [hidden_inputs_for: 1] - alias Backpex.LiveResource alias Ecto.Changeset @@ -420,7 +451,8 @@ defmodule Backpex.Fields.HasManyThrough do assigns false -> - new_field_options = Map.put(assigns.field_options, :child_fields, assigns.field_options.live_resource.fields()) + fields = assigns.field_options.live_resource.validated_fields() + new_field_options = Map.put(assigns.field_options, :child_fields, fields) assigns |> assign(:field, {assigns.name, new_field_options}) diff --git a/lib/backpex/fields/inline_crud.ex b/lib/backpex/fields/inline_crud.ex index d50b8350..0d1a7807 100644 --- a/lib/backpex/fields/inline_crud.ex +++ b/lib/backpex/fields/inline_crud.ex @@ -1,16 +1,32 @@ defmodule Backpex.Fields.InlineCRUD do + @config_schema [ + type: [ + doc: "The type of the field. Either `:embed` or `:assoc`.", + type: :atom, + required: true + ], + child_fields: [ + doc: """ + A list of input fields to be used. Currently only support `Backpex.Fields.Text` fields. + + You can add additional classes to child field inputs by setting the class option in the list of `child_fields`. + The class can be a string or a function that takes the assigns and must return a string. In addition, you can + optionally specify the input type of child field inputs with the `input_type` option. We currently support `:text` + and `:textarea`. The `input_type` defaults to `:text`. + """, + type: :keyword_list, + required: true + ] + ] + @moduledoc """ A field to handle inline CRUD operations. It can be used with either an `embeds_many` or `has_many` (association) type column. - ## Options + ## Field-specific options - * `:type` - The type of the field. Either `:embed` or `:assoc`. - * `:child_fields` - A list of input fields to be used. Currently only support `Backpex.Fields.Text` fields. + See `Backpex.Field` for general field options. - You can add additional classes to child field inputs by setting the class option in the list of `child_fields`. - The class can be a string or a function that takes the assigns and must return a string. - In addition, you can optionally specify the input type of child field inputs with the `input_type` option. We currently - support `:text` and `:textarea`. The `input_type` defaults to `:text`. + #{NimbleOptions.docs(@config_schema)} > #### Important {: .info} > @@ -69,7 +85,7 @@ defmodule Backpex.Fields.InlineCRUD do ] end """ - use BackpexWeb, :field + use Backpex.Field, config_schema: @config_schema @impl Phoenix.LiveComponent def update(assigns, socket) do diff --git a/lib/backpex/fields/multi_select.ex b/lib/backpex/fields/multi_select.ex index 9cf0de87..c5c9d6a4 100644 --- a/lib/backpex/fields/multi_select.ex +++ b/lib/backpex/fields/multi_select.ex @@ -1,16 +1,31 @@ defmodule Backpex.Fields.MultiSelect do + @config_schema [ + options: [ + doc: "List of options or function that receives the assigns.", + type: {:or, [:keyword_list, {:fun, 1}]}, + required: true + ], + prompt: [ + doc: "The text to be displayed when no option is selected or function that receives the assigns.", + type: :string + ], + not_found_text: [ + doc: "The text to be displayed when no options are found.", + type: :string, + default: Backpex.translate("No options found") + ] + ] + @moduledoc """ A field for handling a multi select with predefined options. This field can not be searchable. - ## Options + ## Field-specific options + + See `Backpex.Field` for general field options. - * `:options` - Required (keyword) list of options to be used for the select. - * `:prompt` - The text to be displayed when no options are selected or function that receives the assigns. - Defaults to "Select options...". - * `:not_found_text` - The text to be displayed when no options are found. - Defaults to "No options found". + #{NimbleOptions.docs(@config_schema)} ## Example @@ -24,7 +39,7 @@ defmodule Backpex.Fields.MultiSelect do }, ] """ - use BackpexWeb, :field + use Backpex.Field, config_schema: @config_schema import Backpex.HTML.Form @@ -33,7 +48,7 @@ defmodule Backpex.Fields.MultiSelect do socket = socket |> assign(assigns) - |> assign(:not_found_text, not_found_text(assigns.field_options)) + |> assign(:not_found_text, assigns.field_options[:not_found_text]) |> assign(:prompt, prompt(assigns, assigns.field_options)) |> assign(:search_input, "") |> assign_options() @@ -216,9 +231,6 @@ defmodule Backpex.Fields.MultiSelect do end end - defp not_found_text(%{not_found_text: not_found_text} = _field_options), do: not_found_text - defp not_found_text(_field_options), do: Backpex.translate("No options found") - defp prompt(assigns, field_options) do case Map.get(field_options, :prompt) do nil -> Backpex.translate("Select options...") diff --git a/lib/backpex/fields/number.ex b/lib/backpex/fields/number.ex index 7e73b24a..67a5e2d1 100644 --- a/lib/backpex/fields/number.ex +++ b/lib/backpex/fields/number.ex @@ -1,15 +1,29 @@ defmodule Backpex.Fields.Number do + @config_schema [ + placeholder: [ + doc: "Placeholder value or function that receives the assigns.", + type: {:or, [:string, {:fun, 1}]} + ], + debounce: [ + doc: "Timeout value (in milliseconds), \"blur\" or function that receives the assigns.", + type: {:or, [:pos_integer, :string, {:fun, 1}]} + ], + throttle: [ + doc: "Timeout value (in milliseconds) or function that receives the assigns.", + type: {:or, [:pos_integer, {:fun, 1}]} + ] + ] + @moduledoc """ A field for handling a number value. - ## Options + ## Field-specific options - * `:placeholder` - Optional placeholder value or function that receives the assigns. - * `:debounce` - Optional integer timeout value (in milliseconds), "blur" or function that receives the assigns. - * `:throttle` - Optional integer timeout value (in milliseconds) or function that receives the assigns. - """ - use BackpexWeb, :field + See `Backpex.Field` for general field options. + #{NimbleOptions.docs(@config_schema)} + """ + use Backpex.Field import Ecto.Query @impl Backpex.Field diff --git a/lib/backpex/fields/select.ex b/lib/backpex/fields/select.ex index 17ec32d5..c8b52e79 100644 --- a/lib/backpex/fields/select.ex +++ b/lib/backpex/fields/select.ex @@ -1,11 +1,24 @@ defmodule Backpex.Fields.Select do + @config_schema [ + options: [ + doc: "List of options or function that receives the assigns.", + type: {:or, [:keyword_list, {:fun, 1}]}, + required: true + ], + prompt: [ + doc: "The text to be displayed when no option is selected or function that receives the assigns.", + type: :string + ] + ] + @moduledoc """ A field for handling a select value. - ## Options + ## Field-specific options + + See `Backpex.Field` for general field options. - * `:options` - Required (keyword) list of options or function that receives the assigns. - * `:prompt` - The text to be displayed when no option is selected or function that receives the assigns. + #{NimbleOptions.docs(@config_schema)} ## Example @@ -20,7 +33,7 @@ defmodule Backpex.Fields.Select do ] end """ - use BackpexWeb, :field + use Backpex.Field, config_schema: @config_schema @impl Backpex.Field def render_value(assigns) do diff --git a/lib/backpex/fields/text.ex b/lib/backpex/fields/text.ex index 378054e1..d69b1e0f 100644 --- a/lib/backpex/fields/text.ex +++ b/lib/backpex/fields/text.ex @@ -1,14 +1,29 @@ defmodule Backpex.Fields.Text do + @config_schema [ + placeholder: [ + doc: "Placeholder value or function that receives the assigns.", + type: {:or, [:string, {:fun, 1}]} + ], + debounce: [ + doc: "Timeout value (in milliseconds), \"blur\" or function that receives the assigns.", + type: {:or, [:pos_integer, :string, {:fun, 1}]} + ], + throttle: [ + doc: "Timeout value (in milliseconds) or function that receives the assigns.", + type: {:or, [:pos_integer, {:fun, 1}]} + ] + ] + @moduledoc """ A field for handling a text value. - ## Options + ## Field-specific options + + See `Backpex.Field` for general field options. - * `:placeholder` - Optional placeholder value or function that receives the assigns. - * `:debounce` - Optional integer timeout value (in milliseconds), "blur" or function that receives the assigns. - * `:throttle` - Optional integer timeout value (in milliseconds) or function that receives the assigns. + #{NimbleOptions.docs(@config_schema)} """ - use BackpexWeb, :field + use Backpex.Field, config_schema: @config_schema @impl Backpex.Field def render_value(assigns) do diff --git a/lib/backpex/fields/textarea.ex b/lib/backpex/fields/textarea.ex index a0c8cebf..b290a421 100644 --- a/lib/backpex/fields/textarea.ex +++ b/lib/backpex/fields/textarea.ex @@ -1,4 +1,19 @@ defmodule Backpex.Fields.Textarea do + @config_schema [ + placeholder: [ + doc: "Placeholder value or function that receives the assigns.", + type: {:or, [:string, {:fun, 1}]} + ], + debounce: [ + doc: "Timeout value (in milliseconds), \"blur\" or function that receives the assigns.", + type: {:or, [:pos_integer, :string, {:fun, 1}]} + ], + throttle: [ + doc: "Timeout value (in milliseconds) or function that receives the assigns.", + type: {:or, [:pos_integer, {:fun, 1}]} + ] + ] + @moduledoc """ A field for handling long text values. @@ -8,7 +23,7 @@ defmodule Backpex.Fields.Textarea do * `:debounce` - Optional integer timeout value (in milliseconds), "blur" or function that receives the assigns. * `:throttle` - Optional integer timeout value (in milliseconds) or function that receives the assigns. """ - use BackpexWeb, :field + use Backpex.Field, config_schema: @config_schema @impl Backpex.Field def render_value(assigns) do diff --git a/lib/backpex/fields/upload.ex b/lib/backpex/fields/upload.ex index f92ef9f9..490098b6 100644 --- a/lib/backpex/fields/upload.ex +++ b/lib/backpex/fields/upload.ex @@ -1,124 +1,163 @@ defmodule Backpex.Fields.Upload do - @moduledoc ~S""" - A field for handling uploads. - - > #### Warning {: .warning} - > - > This field does **not** currently support `Phoenix.LiveView.UploadWriter` and direct / external uploads. - - ## Options - - * `:upload_key` (atom) - Required identifier for the upload field (the name of the upload). - * `:accept` (list) - Required filetypes that will be accepted. - * `:max_entries` (integer) - Required number of max files that can be uploaded. - * `:max_file_size` (integer) - Optional maximum file size in bytes to be allowed to uploaded. Defaults 8 MB (`8_000_000`). - * `:list_existing_files` (function) - Required function that returns a list of all uploaded files based on an item. - * `:file_label` (function) - Optional function to get the label of a single file. - * `:consume_upload` (function) - Required function to consume file uploads. - * `:put_upload_change` (function) - Required function to add file paths to the params. - * `:remove_uploads` (function) - Required function that is being called after saving an item to be able to delete removed files - - - > #### Info {: .info} - > - > The following examples copy uploads to a static folder in the application. In a production environment, you should consider uploading files to an appropriate object store. - - ## Options in detail - - The `upload_key`, `accept`, `max_entries` and `max_file_size` options are forwarded to https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.html#allow_upload/3. See the documentation for more information. - - ### `list_existing_files` - - **Parameters** - * `:socket` - The socket. - * `:item` (struct) - The item without its changes. - - The function is being used to display existing uploads. The function receives the socket and the item and has to return a list of strings. Removed files during an edit of an item are automatically removed from the list. This option is required. - - **Example** - - def list_existing_files(_socket, item), do: item.files - - ### `file_label` - - **Parameters** - * `:file` (string) - The file. - - The function can be used to modify a file label based on a file. In the following example each file will have an "_upload" suffix. This option is optional. - - **Example** - - def file_label(file), do: file <> "_upload" - - ### `consume_upload` - - **Parameters** - * `:socket` - The socket. - * `:item` (struct) - The saved item (with its changes). - * `:meta` - The upload meta. - * `:entry` - The upload entry. + @config_schema [ + upload_key: [ + doc: "Required identifier for the upload field (the name of the upload).", + type: :atom + ], + accept: [ + doc: "Required filetypes that will be accepted.", + type: {:list, :string} + ], + max_entries: [ + doc: "Required number of max files that can be uploaded.", + type: :non_neg_integer + ], + max_file_size: [ + doc: "Optional maximum file size in bytes to be allowed to uploaded.", + type: :pos_integer, + default: 8_000_000 + ], + list_existing_files: [ + doc: """ + A function being used to display existing uploads. It has to return a list of all uploaded files as strings. + Removed files during an edit of an item are automatically removed from the list. + + **Parameters** + + * `:item` (struct) - The item without its changes. + + **Example** + + def list_existing_files(item), do: item.files + """, + type: {:fun, 1}, + required: true + ], + file_label: [ + doc: """ + A function to be used to modify a file label of a single file. In the following example each file will have an + `_upload` suffix. + + **Parameters** + + * `:file` (string) - The file. + + **Example** + + def file_label(file), do: file <> "_upload" + """, + type: {:fun, 1} + ], + consume_upload: [ + doc: """ + Required function to consume file uploads. + A function to consume uploads. It is called after the item has been saved and is used to copy the files to a + specific destination. Backpex will use this function as a callback for `consume_uploaded_entries`. See + https://hexdocs.pm/phoenix_live_view/uploads.html#consume-uploaded-entries for more details. + + **Parameters** + + * `:socket` - The socket. + * `:item` (struct) - The saved item (with its changes). + * `:meta` - The upload meta. + * `:entry` - The upload entry. + + **Example** - The function is used to consume uploads. It is called after the item has been saved and is used to copy the files to a specific destination. Backpex will use this function as a callback for `consume_uploaded_entries`. See https://hexdocs.pm/phoenix_live_view/uploads.html#consume-uploaded-entries for more details. This option is required. - - **Example** - - defp consume_upload(_socket, _item, %{path: path} = _meta, entry) do - file_name = ... - file_url = ... - static_dir = ... - dest = Path.join([:code.priv_dir(:demo), "static", static_dir, file_name]) - - File.cp!(path, dest) - - {:ok, file_url} - end - - ### `put_upload_change` - - **Parameters** - * `:socket` - The socket. - * `:params` (map) - The current params that will be passed to the changeset function. - * `:item` (struct) - The item without its changes. On create will this will be an empty map. - * `uploaded_entries` (tuple) - The completed and in progress entries for the upload. - * `removed_entries` (list) - A list of removed uploads during edit. - * `action` (atom) - The action (`:validate` or `:insert`) - - This function is used to modify the params based on certain parameters. It is important because it ensures that file paths are added to the item change and therefore persisted in the database. This option is required. + defp consume_upload(_socket, _item, %{path: path} = _meta, entry) do + file_name = ... + file_url = ... + static_dir = ... + dest = Path.join([:code.priv_dir(:demo), "static", static_dir, file_name]) - **Example** + File.cp!(path, dest) - def put_upload_change(_socket, params, item, uploaded_entries, removed_entries, action) do - existing_files = item.files -- removed_entries + {:ok, file_url} + end + """, + type: {:fun, 4}, + required: true + ], + put_upload_change: [ + doc: """ + A function to modify the params based on certain parameters. It is important because it ensures that file paths + are added to the item change and therefore persisted in the database. + + **Parameters** + + * `:socket` - The socket. + * `:params` (map) - The current params that will be passed to the changeset function. + * `:item` (struct) - The item without its changes. On create will this will be an empty map. + * `uploaded_entries` (tuple) - The completed and in progress entries for the upload. + * `removed_entries` (list) - A list of removed uploads during edit. + * `action` (atom) - The action (`:validate` or `:insert`) + + **Example** + + def put_upload_change(_socket, params, item, uploaded_entries, removed_entries, action) do + existing_files = item.files -- removed_entries + + new_entries = + case action do + :validate -> + elem(uploaded_entries, 1) + + :insert -> + elem(uploaded_entries, 0) + end - new_entries = - case action do - :validate -> - elem(uploaded_entries, 1) + files = existing_files ++ Enum.map(new_entries, fn entry -> file_name(entry) end) - :insert -> - elem(uploaded_entries, 0) + Map.put(params, "images", files) end + """, + type: {:fun, 6}, + required: true + ], + remove_uploads: [ + doc: """ + A function that is being called after saving an item to be able to delete removed files. + + **Parameters** + + * `:socket` - The socket. + * `:item` (struct) - The item without its changes. + * `removed_entries` (list) - A list of removed uploads during edit. + + **Example** + + defp remove_uploads(_socket, _item, removed_entries) do + for file <- removed_entries do + file_path = ... + File.rm!(file_path) + end + end + """, + type: {:fun, 3}, + required: true + ] + ] - files = existing_files ++ Enum.map(new_entries, fn entry -> file_name(entry) end) + @moduledoc """ + A field for handling uploads. - Map.put(params, "images", files) - end + > #### Warning {: .warning} + > + > This field does **not** currently support `Phoenix.LiveView.UploadWriter` and direct / external uploads. - ### `remove_uploads` + ## Field-specific options - **Parameters** - * `:socket` - The socket. - * `:item` (struct) - The item without its changes. - * `removed_entries` (list) - A list of removed uploads during edit. + See `Backpex.Field` for general field options. - **Example** + The `upload_key`, `accept`, `max_entries` and `max_file_size` options are forwarded to + https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.html#allow_upload/3. See the documentation for more information. - defp remove_uploads(_socket, _item, removed_entries) do - for file <- removed_entries do - file_path = ... - File.rm!(file_path) - end - end + #{NimbleOptions.docs(@config_schema)} + + > #### Info {: .info} + > + > The following examples copy uploads to a static folder in the application. In a production environment, you should + consider uploading files to an appropriate object store. ## Full Single File Example @@ -241,7 +280,7 @@ defmodule Backpex.Fields.Upload do defp file_name(entry) do [ext | _] = MIME.extensions(entry.client_type) - "#{entry.uuid}.#{ext}" + entry.uuid <> "." <> ext end defp upload_dir, do: Path.join(["uploads", "user", "avatar"]) @@ -355,14 +394,13 @@ defmodule Backpex.Fields.Upload do defp file_name(entry) do [ext | _] = MIME.extensions(entry.client_type) - "#{entry.uuid}.#{ext}" + entry.uuid <> "." <> ext end defp upload_dir, do: Path.join(["uploads", "product", "images"]) end """ - use BackpexWeb, :field - + use Backpex.Field, config_schema: @config_schema alias Backpex.HTML.Form, as: BackpexForm @impl Backpex.Field diff --git a/lib/backpex/fields/url.ex b/lib/backpex/fields/url.ex index 5760c84a..b2c6c3ef 100644 --- a/lib/backpex/fields/url.ex +++ b/lib/backpex/fields/url.ex @@ -1,14 +1,29 @@ defmodule Backpex.Fields.URL do + @config_schema [ + placeholder: [ + doc: "Placeholder value or function that receives the assigns.", + type: {:or, [:string, {:fun, 1}]} + ], + debounce: [ + doc: "Timeout value (in milliseconds), \"blur\" or function that receives the assigns.", + type: {:or, [:pos_integer, :string, {:fun, 1}]} + ], + throttle: [ + doc: "Timeout value (in milliseconds) or function that receives the assigns.", + type: {:or, [:pos_integer, {:fun, 1}]} + ] + ] + @moduledoc """ A field for handling an URL value. - ## Options + ## Field-specific options + + See `Backpex.Field` for general field options. - * `:placeholder` - Optional placeholder value or function that receives the assigns. - * `:debounce` - Optional integer timeout value (in milliseconds), "blur" or function that receives the assigns. - * `:throttle` - Optional integer timeout value (in milliseconds) or function that receives the assigns. + #{NimbleOptions.docs(@config_schema)} """ - use BackpexWeb, :field + use Backpex.Field, config_schema: @config_schema @impl Backpex.Field def render_value(assigns) do diff --git a/lib/backpex/live_resource.ex b/lib/backpex/live_resource.ex index f1164fc4..67cb6ce8 100644 --- a/lib/backpex/live_resource.ex +++ b/lib/backpex/live_resource.ex @@ -257,6 +257,8 @@ defmodule Backpex.LiveResource do def config(key), do: Keyword.fetch!(@resource_opts, key) + def validated_fields, do: LiveResource.validated_fields(__MODULE__) + @impl Phoenix.LiveView def mount(params, session, socket), do: LiveResource.mount(params, session, socket) @@ -480,7 +482,7 @@ defmodule Backpex.LiveResource do defp assign_active_fields(socket, session) do fields = - socket.assigns.live_resource.fields() + socket.assigns.live_resource.validated_fields() |> filtered_fields_by_action(socket.assigns, :index) saved_fields = get_in(session, ["backpex", "column_toggle", "#{socket.assigns.live_resource}"]) || %{} @@ -611,6 +613,18 @@ defmodule Backpex.LiveResource do {:noreply, socket} end + @doc """ + Returns the fields of the given `Backpex.LiveResource` validated against each fields config schema. + """ + def validated_fields(live_resource) do + live_resource.fields() + |> Enum.map(fn {name, options} = field -> + options.module.validate_config!(field, live_resource) + |> Enum.into(%{}) + |> then(&{name, &1}) + end) + end + defp apply_action(socket, :index) do socket |> assign(:page_title, socket.assigns.plural_name) @@ -644,7 +658,7 @@ defmodule Backpex.LiveResource do singular_name: singular_name } = socket.assigns - fields = live_resource.fields() |> filtered_fields_by_action(socket.assigns, :show) + fields = live_resource.validated_fields() |> filtered_fields_by_action(socket.assigns, :show) primary_value = URI.decode(socket.assigns.params["backpex_id"]) item = Resource.get!(primary_value, socket.assigns, live_resource) @@ -666,7 +680,7 @@ defmodule Backpex.LiveResource do unless live_resource.can?(socket.assigns, :new, nil), do: raise(Backpex.ForbiddenError) - fields = live_resource.fields() |> filtered_fields_by_action(socket.assigns, :new) + fields = live_resource.validated_fields() |> filtered_fields_by_action(socket.assigns, :new) empty_item = schema.__struct__() socket @@ -732,7 +746,7 @@ defmodule Backpex.LiveResource do unless live_resource.can?(socket.assigns, :index, nil), do: raise(Backpex.ForbiddenError) - fields = live_resource.fields() |> filtered_fields_by_action(socket.assigns, :index) + fields = live_resource.validated_fields() |> 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) diff --git a/lib/backpex_web.ex b/lib/backpex_web.ex index f1964253..d0a6c69b 100644 --- a/lib/backpex_web.ex +++ b/lib/backpex_web.ex @@ -24,7 +24,6 @@ defmodule BackpexWeb do def field do quote do use Phoenix.Component, global_prefixes: ~w(x-) - use Backpex.Field use Phoenix.LiveComponent alias Backpex.HTML alias Backpex.HTML.Form, as: BackpexForm From d8b6e216d78e145dfd4e7e2021613025ae811310 Mon Sep 17 00:00:00 2001 From: Phil-Bastian Berndt Date: Fri, 8 Nov 2024 18:29:12 +0100 Subject: [PATCH 02/26] fix and improve config_schema handling --- lib/backpex/field.ex | 4 +--- lib/backpex/fields/currency.ex | 2 +- lib/backpex/fields/date.ex | 9 +++------ lib/backpex/fields/date_time.ex | 9 +++------ lib/backpex/fields/number.ex | 2 +- lib/backpex/fields/textarea.ex | 8 ++++---- 6 files changed, 13 insertions(+), 21 deletions(-) diff --git a/lib/backpex/field.ex b/lib/backpex/field.ex index 1925bee4..91976513 100644 --- a/lib/backpex/field.ex +++ b/lib/backpex/field.ex @@ -176,7 +176,7 @@ defmodule Backpex.Field do @doc """ Returns the default config schema. """ - def default_config_schema(), do: @config_schema + def default_config_schema, do: @config_schema @doc """ Defines `Backpex.Field` behaviour and provides default implementations. @@ -193,8 +193,6 @@ defmodule Backpex.Field do def validate_config!({name, options} = _field, live_resource) do field_options = Keyword.new(options) - dbg(field_options) - case NimbleOptions.validate(field_options, Backpex.Field.default_config_schema() ++ @config_schema) do {:ok, validated_options} -> validated_options diff --git a/lib/backpex/fields/currency.ex b/lib/backpex/fields/currency.ex index 5ca9690b..0ad96fde 100644 --- a/lib/backpex/fields/currency.ex +++ b/lib/backpex/fields/currency.ex @@ -46,7 +46,7 @@ defmodule Backpex.Fields.Currency do ] end """ - use Backpex.Field + use Backpex.Field, config_schema: @config_schema import Ecto.Query alias Backpex.Ecto.Amount.Type diff --git a/lib/backpex/fields/date.ex b/lib/backpex/fields/date.ex index 592f2042..e7db8b93 100644 --- a/lib/backpex/fields/date.ex +++ b/lib/backpex/fields/date.ex @@ -1,7 +1,5 @@ # credo:disable-for-this-file Credo.Check.Design.DuplicatedCode defmodule Backpex.Fields.Date do - @default_format "%Y-%m-%d" - @config_schema [ format: [ doc: """ @@ -10,7 +8,7 @@ defmodule Backpex.Fields.Date do Can also be a function wich receives a `DateTime` and must return a string. """, type: {:or, [:string, {:fun, 1}]}, - default: @default_format + default: "%Y-%m-%d" ], debounce: [ doc: "Timeout value (in milliseconds), \"blur\" or function that receives the assigns.", @@ -22,7 +20,6 @@ defmodule Backpex.Fields.Date do ] ] - # credo:disable-for-next-line Credo.Check.Readability.StrictModuleLayout @moduledoc """ A field for handling a date value. @@ -72,11 +69,11 @@ defmodule Backpex.Fields.Date do ] end """ - use Backpex.Field + use Backpex.Field, config_schema: @config_schema @impl Backpex.Field def render_value(assigns) do - format = Map.get(assigns.field_options, :format, @default_format) + format = assigns.field_options[:format] value = cond do diff --git a/lib/backpex/fields/date_time.ex b/lib/backpex/fields/date_time.ex index 5331b2f2..1da16c34 100644 --- a/lib/backpex/fields/date_time.ex +++ b/lib/backpex/fields/date_time.ex @@ -1,7 +1,5 @@ # credo:disable-for-this-file Credo.Check.Design.DuplicatedCode defmodule Backpex.Fields.DateTime do - @default_format "%Y-%m-%d %I:%M %p" - @config_schema [ format: [ doc: """ @@ -10,7 +8,7 @@ defmodule Backpex.Fields.DateTime do Can also be a function wich receives a `DateTime` and must return a string. """, type: {:or, [:string, {:fun, 1}]}, - default: @default_format + default: "%Y-%m-%d %I:%M %p" ], debounce: [ doc: "Timeout value (in milliseconds), \"blur\" or function that receives the assigns.", @@ -22,7 +20,6 @@ defmodule Backpex.Fields.DateTime do ] ] - # credo:disable-for-next-line Credo.Check.Readability.StrictModuleLayout @moduledoc """ A field for handling a date time value. @@ -72,11 +69,11 @@ defmodule Backpex.Fields.DateTime do ] end """ - use Backpex.Field + use Backpex.Field, config_schema: @config_schema @impl Backpex.Field def render_value(assigns) do - format = Map.get(assigns.field_options, :format, @default_format) + format = assigns.field_options[:format] value = cond do diff --git a/lib/backpex/fields/number.ex b/lib/backpex/fields/number.ex index 67a5e2d1..c536b924 100644 --- a/lib/backpex/fields/number.ex +++ b/lib/backpex/fields/number.ex @@ -23,7 +23,7 @@ defmodule Backpex.Fields.Number do #{NimbleOptions.docs(@config_schema)} """ - use Backpex.Field + use Backpex.Field, config_schema: @config_schema import Ecto.Query @impl Backpex.Field diff --git a/lib/backpex/fields/textarea.ex b/lib/backpex/fields/textarea.ex index b290a421..0173007a 100644 --- a/lib/backpex/fields/textarea.ex +++ b/lib/backpex/fields/textarea.ex @@ -17,11 +17,11 @@ defmodule Backpex.Fields.Textarea do @moduledoc """ A field for handling long text values. - ## Options + ## Field-specific options - * `:placeholder` - Optional placeholder value or function that receives the assigns. - * `:debounce` - Optional integer timeout value (in milliseconds), "blur" or function that receives the assigns. - * `:throttle` - Optional integer timeout value (in milliseconds) or function that receives the assigns. + See `Backpex.Field` for general field options. + + #{NimbleOptions.docs(@config_schema)} """ use Backpex.Field, config_schema: @config_schema From 122a9433831265956120d28d40d3d810bc1e39e0 Mon Sep 17 00:00:00 2001 From: Phil-Bastian Berndt Date: Thu, 14 Nov 2024 18:44:19 +0100 Subject: [PATCH 03/26] do not translate on compile --- lib/backpex/fields/has_many.ex | 21 ++++++++++++++------- lib/backpex/fields/multi_select.ex | 11 +++++++---- 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/lib/backpex/fields/has_many.ex b/lib/backpex/fields/has_many.ex index 93bd0550..3bd3fdb7 100644 --- a/lib/backpex/fields/has_many.ex +++ b/lib/backpex/fields/has_many.ex @@ -27,14 +27,20 @@ defmodule Backpex.Fields.HasMany do type: {:fun, 2} ], prompt: [ - doc: "The text to be displayed when no options are selected or function that receives the assigns.", - type: :string, - default: Backpex.translate("Select options...") + doc: """ + The text to be displayed when no options are selected or function that receives the assigns. + + The default value is `"Select options..."`. + """, + type: :string ], not_found_text: [ - doc: "The text to be displayed when no options are found.", - type: :string, - default: Backpex.translate("No options found") + doc: """ + The text to be displayed when no options are found. + + The default value is `"No options found"`. + """, + type: :string ], query_limit: [ doc: "Limit passed to the query to fetch new items. Set to `nil` to have no limit.", @@ -94,7 +100,7 @@ defmodule Backpex.Fields.HasMany do socket |> assign_new(:prompt, fn -> prompt(assigns, field_options) end) - |> assign_new(:not_found_text, fn -> field_options[:not_found_text] end) + |> assign_new(:not_found_text, fn -> field_options[:not_found_text] || Backpex.translate("No options found") end) |> assign_new(:search_input, fn -> "" end) |> assign_new(:offset, fn -> 0 end) |> assign_new(:options_count, fn -> count_options(assigns) end) @@ -542,6 +548,7 @@ defmodule Backpex.Fields.HasMany do defp prompt(assigns, field_options) do case Map.get(field_options, :prompt) do + nil -> Backpex.translate("Select options...") prompt when is_function(prompt) -> prompt.(assigns) prompt -> prompt end diff --git a/lib/backpex/fields/multi_select.ex b/lib/backpex/fields/multi_select.ex index c5c9d6a4..4d49e9ed 100644 --- a/lib/backpex/fields/multi_select.ex +++ b/lib/backpex/fields/multi_select.ex @@ -10,9 +10,12 @@ defmodule Backpex.Fields.MultiSelect do type: :string ], not_found_text: [ - doc: "The text to be displayed when no options are found.", - type: :string, - default: Backpex.translate("No options found") + doc: """ + The text to be displayed when no options are found. + + The default value is `"No options found"`. + """, + type: :string ] ] @@ -48,7 +51,7 @@ defmodule Backpex.Fields.MultiSelect do socket = socket |> assign(assigns) - |> assign(:not_found_text, assigns.field_options[:not_found_text]) + |> assign(:not_found_text, assigns.field_options[:not_found_text] || Backpex.translate("No options found")) |> assign(:prompt, prompt(assigns, assigns.field_options)) |> assign(:search_input, "") |> assign_options() From bdd23563be33229829f60788f180b164d32a40c0 Mon Sep 17 00:00:00 2001 From: Phil-Bastian Berndt Date: Thu, 14 Nov 2024 18:48:27 +0100 Subject: [PATCH 04/26] add render_form field option --- lib/backpex/field.ex | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/backpex/field.ex b/lib/backpex/field.ex index 91976513..b30a3676 100644 --- a/lib/backpex/field.ex +++ b/lib/backpex/field.ex @@ -15,6 +15,10 @@ defmodule Backpex.Field do doc: "", type: {:fun, 1} ], + render_form: [ + doc: "A function to overwrite the template used in forms. It should take `assigns` and return a HEEX template.", + type: {:fun, 1} + ], custom_alias: [ doc: "", type: :atom From be22ffeb13454ebbbdfb70d4a88aaed043756fa7 Mon Sep 17 00:00:00 2001 From: Phil-Bastian Berndt Date: Thu, 14 Nov 2024 18:54:43 +0100 Subject: [PATCH 05/26] add upgrade guide for field usage --- guides/upgrading/v0.9.md | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/guides/upgrading/v0.9.md b/guides/upgrading/v0.9.md index 08b270a4..85a78cc9 100644 --- a/guides/upgrading/v0.9.md +++ b/guides/upgrading/v0.9.md @@ -70,3 +70,30 @@ end Although the change is relatively small, if you are using public functions of the `Backpex.LiveResource` directly, check the updated function definitions in the module documentation. + +## Refactor custom fields + +In case you built your own custom fields: We changed the way how to use the `Backpex.Field`. + +Before: + +```elixir + use BackpexWeb, :field +``` + +After: + +```elixir + use Backpex.Field +``` + +In case your field has field-specific configuration options, you need to provide those when using `Backpex.Field`: + +```elixir + @config_schema [ + # see https://hexdocs.pm/nimble_options/NimbleOptions.html + # or any other core backpex field for examples... + ] + + use Backpex.Field, config_schema: @config_schema +``` From da27dc3f6550bc19e595df0bcfe06da3269532b7 Mon Sep 17 00:00:00 2001 From: Phil-Bastian Berndt Date: Thu, 14 Nov 2024 18:58:23 +0100 Subject: [PATCH 06/26] fix typo --- lib/backpex/fields/textarea.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/backpex/fields/textarea.ex b/lib/backpex/fields/textarea.ex index 8841f379..106200d1 100644 --- a/lib/backpex/fields/textarea.ex +++ b/lib/backpex/fields/textarea.ex @@ -13,7 +13,7 @@ defmodule Backpex.Fields.Textarea do type: {:or, [:pos_integer, {:fun, 1}]} ], rows: [ - docs: "Number of visible text lines for the control. If it is not specified.", + doc: "Number of visible text lines for the control. If it is not specified.", type: :non_neg_integer, default: 2 ] From 333da2b2d032bfcde7ae8c268d3e604a54715913 Mon Sep 17 00:00:00 2001 From: Phil-Bastian Berndt Date: Thu, 14 Nov 2024 18:58:34 +0100 Subject: [PATCH 07/26] remove deprecated option --- demo/lib/demo_web/live/short_link_live.ex | 1 - lib/backpex/fields/belongs_to.ex | 4 ---- 2 files changed, 5 deletions(-) diff --git a/demo/lib/demo_web/live/short_link_live.ex b/demo/lib/demo_web/live/short_link_live.ex index 3c0ad817..9506f4d9 100644 --- a/demo/lib/demo_web/live/short_link_live.ex +++ b/demo/lib/demo_web/live/short_link_live.ex @@ -47,7 +47,6 @@ defmodule DemoWeb.ShortLinkLive do product: %{ module: Backpex.Fields.BelongsTo, label: "Product", - source: Demo.Product, display_field: :name, prompt: "Choose product..." } diff --git a/lib/backpex/fields/belongs_to.ex b/lib/backpex/fields/belongs_to.ex index c92561c9..00b52428 100644 --- a/lib/backpex/fields/belongs_to.ex +++ b/lib/backpex/fields/belongs_to.ex @@ -13,10 +13,6 @@ defmodule Backpex.Fields.BelongsTo do doc: "The live resource of the association. Used to generate links navigating to the association.", type: :atom ], - source: [ - doc: "", - type: :atom - ], options_query: [ doc: """ Manipulates the list of available options in the select. From 3009a32cf91267562b82c6d1ce9da25e323e0a21 Mon Sep 17 00:00:00 2001 From: Phil-Bastian Berndt Date: Thu, 14 Nov 2024 19:32:06 +0100 Subject: [PATCH 08/26] improve field option docs --- guides/fields/custom-fields.md | 2 +- lib/backpex/field.ex | 50 ++++++++++++++++++++++++---------- lib/backpex/fields/textarea.ex | 2 +- 3 files changed, 37 insertions(+), 17 deletions(-) diff --git a/guides/fields/custom-fields.md b/guides/fields/custom-fields.md index b95e4e5e..c9ad41c0 100644 --- a/guides/fields/custom-fields.md +++ b/guides/fields/custom-fields.md @@ -13,7 +13,7 @@ When creating your own custom field, you can use the `field` macro from the `Bac The simplest version of a custom field would look like this: ```elixir -use BackpexWeb, :field +use Backpex.Field @impl Backpex.Field def render_value(assigns) do diff --git a/lib/backpex/field.ex b/lib/backpex/field.ex index b30a3676..ecbf4af4 100644 --- a/lib/backpex/field.ex +++ b/lib/backpex/field.ex @@ -12,7 +12,7 @@ defmodule Backpex.Field do required: true ], render: [ - doc: "", + doc: "A function to overwrite the template used . It should take `assigns` and return a HEEX template.", type: {:fun, 1} ], render_form: [ @@ -20,19 +20,23 @@ defmodule Backpex.Field do type: {:fun, 1} ], custom_alias: [ - doc: "", + doc: "A custom alias for the field.", type: :atom ], align: [ - doc: "", - type: :atom + doc: "Align the fields of a resource in the index view.", + type: {:in, [:left, :center, :right]} + ], + align_label: [ + doc: "Align the labels of the fields in the edit view.", + type: {:in, [:top, :center, :bottom]} ], searchable: [ - doc: "", + doc: "Define wether this field should be searchable on the index view.", type: :boolean ], orderable: [ - doc: "", + doc: "Define wether this field should be orderable on the index view.", type: :boolean ], visible: [ @@ -40,28 +44,44 @@ defmodule Backpex.Field do type: {:fun, 1} ], panel: [ - doc: "", + doc: "Group field into panel. Also see the [panels](/guides/authorization/panels.md) guide.", type: :atom ], index_editable: [ - doc: "", + doc: """ + Define wether this field should be editable on the index view. Also see the + [index edit](/guides/authorization/index-edit.md) guide. + """, type: :boolean ], index_column_class: [ - doc: "", - type: :string + doc: """ + Add additional class(es) to the index column. + In case of a function it takes the `assigns` and should return a string. + """, + type: {:or, [:string, {:fun, 1}]} ], select: [ - doc: "", + doc: """ + Define a dynamic select query expression for this field. + + ### Example + + full_name: %{ + module: Backpex.Fields.Text, + label: "Full Name", + select: dynamic([user: u], fragment("concat(?, ' ', ?)", u.first_name, u.last_name)), + } + """, type: {:struct, Ecto.Query.DynamicExpr} ], only: [ - doc: "", - type: {:list, :atom} + doc: "Define the only views where this field should be visible.", + type: {:list, {:in, [:new, :edit, :show, :index, :resource_action]}} ], except: [ - doc: "", - type: {:list, :atom} + doc: "Define the views where this field should not be visible.", + type: {:list, {:in, [:new, :edit, :show, :index, :resource_action]}} ], translate_error: [ doc: """ diff --git a/lib/backpex/fields/textarea.ex b/lib/backpex/fields/textarea.ex index 106200d1..578e72c4 100644 --- a/lib/backpex/fields/textarea.ex +++ b/lib/backpex/fields/textarea.ex @@ -13,7 +13,7 @@ defmodule Backpex.Fields.Textarea do type: {:or, [:pos_integer, {:fun, 1}]} ], rows: [ - doc: "Number of visible text lines for the control. If it is not specified.", + doc: "Number of visible text lines for the control.", type: :non_neg_integer, default: 2 ] From 616afd44ed33ca8d206f773a3efbbd530d52fe1b Mon Sep 17 00:00:00 2001 From: Phil-Bastian Berndt Date: Thu, 14 Nov 2024 19:48:30 +0100 Subject: [PATCH 09/26] improve type option validation --- lib/backpex/fields/inline_crud.ex | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/backpex/fields/inline_crud.ex b/lib/backpex/fields/inline_crud.ex index 0d1a7807..3bfc7ae6 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. Either `:embed` or `:assoc`.", - type: :atom, + doc: "The type of the field.", + type: {:in, [:embed, :assoc]}, required: true ], child_fields: [ From d1c6098a5527dc44adcae4405e919e29a828e3f1 Mon Sep 17 00:00:00 2001 From: Phil-Bastian Berndt Date: Tue, 3 Dec 2024 09:36:40 +0100 Subject: [PATCH 10/26] =?UTF-8?q?add=20default=20option=C3=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/backpex/field.ex | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/backpex/field.ex b/lib/backpex/field.ex index ecbf4af4..b6399b57 100644 --- a/lib/backpex/field.ex +++ b/lib/backpex/field.ex @@ -11,6 +11,12 @@ defmodule Backpex.Field do type: :string, required: true ], + default: [ + doc: """ + A function to assign default values to fields. Also see the [field defaults](/guides/fields/defaults.md) guide. + """, + type: {:fun, 1} + ], render: [ doc: "A function to overwrite the template used . It should take `assigns` and return a HEEX template.", type: {:fun, 1} From 388016fef07ebdde9ffd4199510db3c999529053 Mon Sep 17 00:00:00 2001 From: Phil-Bastian Berndt Date: Tue, 3 Dec 2024 09:40:05 +0100 Subject: [PATCH 11/26] move debounce and throttle to general options --- lib/backpex/field.ex | 8 ++++++++ lib/backpex/fields/boolean.ex | 11 +---------- lib/backpex/fields/currency.ex | 11 +---------- lib/backpex/fields/date.ex | 8 -------- lib/backpex/fields/date_time.ex | 20 +------------------- lib/backpex/fields/number.ex | 8 -------- lib/backpex/fields/text.ex | 8 -------- lib/backpex/fields/textarea.ex | 8 -------- lib/backpex/fields/url.ex | 8 -------- 9 files changed, 11 insertions(+), 79 deletions(-) diff --git a/lib/backpex/field.ex b/lib/backpex/field.ex index b6399b57..f6950a23 100644 --- a/lib/backpex/field.ex +++ b/lib/backpex/field.ex @@ -95,6 +95,14 @@ defmodule Backpex.Field do with the message and metadata. """, type: {:fun, 1} + ], + debounce: [ + doc: "Timeout value (in milliseconds), \"blur\" or function that receives the assigns.", + type: {:or, [:pos_integer, :string, {:fun, 1}]} + ], + throttle: [ + doc: "Timeout value (in milliseconds) or function that receives the assigns.", + type: {:or, [:pos_integer, {:fun, 1}]} ] ] diff --git a/lib/backpex/fields/boolean.ex b/lib/backpex/fields/boolean.ex index 7d3c6499..58a23c61 100644 --- a/lib/backpex/fields/boolean.ex +++ b/lib/backpex/fields/boolean.ex @@ -1,14 +1,5 @@ defmodule Backpex.Fields.Boolean do - @config_schema [ - debounce: [ - doc: "Timeout value (in milliseconds), \"blur\" or function that receives the assigns.", - type: {:or, [:pos_integer, :string, {:fun, 1}]} - ], - throttle: [ - doc: "Timeout value (in milliseconds) or function that receives the assigns.", - type: {:or, [:pos_integer, {:fun, 1}]} - ] - ] + @config_schema [] @moduledoc """ A field for handling a boolean value. diff --git a/lib/backpex/fields/currency.ex b/lib/backpex/fields/currency.ex index 0ad96fde..bb5aa5b3 100644 --- a/lib/backpex/fields/currency.ex +++ b/lib/backpex/fields/currency.ex @@ -1,14 +1,5 @@ defmodule Backpex.Fields.Currency do - @config_schema [ - debounce: [ - doc: "Timeout value (in milliseconds), \"blur\" or function that receives the assigns.", - type: {:or, [:pos_integer, :string, {:fun, 1}]} - ], - throttle: [ - doc: "Timeout value (in milliseconds) or function that receives the assigns.", - type: {:or, [:pos_integer, {:fun, 1}]} - ] - ] + @config_schema [] @moduledoc """ A field for handling a currency value. diff --git a/lib/backpex/fields/date.ex b/lib/backpex/fields/date.ex index e7db8b93..1324c006 100644 --- a/lib/backpex/fields/date.ex +++ b/lib/backpex/fields/date.ex @@ -9,14 +9,6 @@ defmodule Backpex.Fields.Date do """, type: {:or, [:string, {:fun, 1}]}, default: "%Y-%m-%d" - ], - debounce: [ - doc: "Timeout value (in milliseconds), \"blur\" or function that receives the assigns.", - type: {:or, [:pos_integer, :string, {:fun, 1}]} - ], - throttle: [ - doc: "Timeout value (in milliseconds) or function that receives the assigns.", - type: {:or, [:pos_integer, {:fun, 1}]} ] ] diff --git a/lib/backpex/fields/date_time.ex b/lib/backpex/fields/date_time.ex index 1da16c34..dc39e22c 100644 --- a/lib/backpex/fields/date_time.ex +++ b/lib/backpex/fields/date_time.ex @@ -1,24 +1,6 @@ # credo:disable-for-this-file Credo.Check.Design.DuplicatedCode defmodule Backpex.Fields.DateTime do - @config_schema [ - format: [ - doc: """ - Format string which will be used to format the date time value or function that formats the date time. - - Can also be a function wich receives a `DateTime` and must return a string. - """, - type: {:or, [:string, {:fun, 1}]}, - default: "%Y-%m-%d %I:%M %p" - ], - debounce: [ - doc: "Timeout value (in milliseconds), \"blur\" or function that receives the assigns.", - type: {:or, [:pos_integer, :string, {:fun, 1}]} - ], - throttle: [ - doc: "Timeout value (in milliseconds) or function that receives the assigns.", - type: {:or, [:pos_integer, {:fun, 1}]} - ] - ] + @config_schema [] @moduledoc """ A field for handling a date time value. diff --git a/lib/backpex/fields/number.ex b/lib/backpex/fields/number.ex index c536b924..a0eef0f1 100644 --- a/lib/backpex/fields/number.ex +++ b/lib/backpex/fields/number.ex @@ -3,14 +3,6 @@ defmodule Backpex.Fields.Number do placeholder: [ doc: "Placeholder value or function that receives the assigns.", type: {:or, [:string, {:fun, 1}]} - ], - debounce: [ - doc: "Timeout value (in milliseconds), \"blur\" or function that receives the assigns.", - type: {:or, [:pos_integer, :string, {:fun, 1}]} - ], - throttle: [ - doc: "Timeout value (in milliseconds) or function that receives the assigns.", - type: {:or, [:pos_integer, {:fun, 1}]} ] ] diff --git a/lib/backpex/fields/text.ex b/lib/backpex/fields/text.ex index d69b1e0f..ee66a270 100644 --- a/lib/backpex/fields/text.ex +++ b/lib/backpex/fields/text.ex @@ -3,14 +3,6 @@ defmodule Backpex.Fields.Text do placeholder: [ doc: "Placeholder value or function that receives the assigns.", type: {:or, [:string, {:fun, 1}]} - ], - debounce: [ - doc: "Timeout value (in milliseconds), \"blur\" or function that receives the assigns.", - type: {:or, [:pos_integer, :string, {:fun, 1}]} - ], - throttle: [ - doc: "Timeout value (in milliseconds) or function that receives the assigns.", - type: {:or, [:pos_integer, {:fun, 1}]} ] ] diff --git a/lib/backpex/fields/textarea.ex b/lib/backpex/fields/textarea.ex index 578e72c4..6def18cd 100644 --- a/lib/backpex/fields/textarea.ex +++ b/lib/backpex/fields/textarea.ex @@ -4,14 +4,6 @@ defmodule Backpex.Fields.Textarea do doc: "Placeholder value or function that receives the assigns.", type: {:or, [:string, {:fun, 1}]} ], - debounce: [ - doc: "Timeout value (in milliseconds), \"blur\" or function that receives the assigns.", - type: {:or, [:pos_integer, :string, {:fun, 1}]} - ], - throttle: [ - doc: "Timeout value (in milliseconds) or function that receives the assigns.", - type: {:or, [:pos_integer, {:fun, 1}]} - ], rows: [ doc: "Number of visible text lines for the control.", type: :non_neg_integer, diff --git a/lib/backpex/fields/url.ex b/lib/backpex/fields/url.ex index b2c6c3ef..1f702328 100644 --- a/lib/backpex/fields/url.ex +++ b/lib/backpex/fields/url.ex @@ -3,14 +3,6 @@ defmodule Backpex.Fields.URL do placeholder: [ doc: "Placeholder value or function that receives the assigns.", type: {:or, [:string, {:fun, 1}]} - ], - debounce: [ - doc: "Timeout value (in milliseconds), \"blur\" or function that receives the assigns.", - type: {:or, [:pos_integer, :string, {:fun, 1}]} - ], - throttle: [ - doc: "Timeout value (in milliseconds) or function that receives the assigns.", - type: {:or, [:pos_integer, {:fun, 1}]} ] ] From eed23d1af9712a19767efd128b516bbb12e42dfc Mon Sep 17 00:00:00 2001 From: Phil-Bastian Berndt Date: Tue, 3 Dec 2024 09:45:42 +0100 Subject: [PATCH 12/26] allow functions for align_label and index_editable --- lib/backpex/field.ex | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/backpex/field.ex b/lib/backpex/field.ex index f6950a23..8cae9452 100644 --- a/lib/backpex/field.ex +++ b/lib/backpex/field.ex @@ -35,7 +35,7 @@ defmodule Backpex.Field do ], align_label: [ doc: "Align the labels of the fields in the edit view.", - type: {:in, [:top, :center, :bottom]} + type: {:or, [{:in, [:top, :center, :bottom]}, {:fun, 1}]} ], searchable: [ doc: "Define wether this field should be searchable on the index view.", @@ -58,7 +58,7 @@ defmodule Backpex.Field do Define wether this field should be editable on the index view. Also see the [index edit](/guides/authorization/index-edit.md) guide. """, - type: :boolean + type: {:or, [:boolean, {:fun, 1}]} ], index_column_class: [ doc: """ From ea65695dd1d756b70615751e5ac7bfc6f4c6fdf5 Mon Sep 17 00:00:00 2001 From: Phil-Bastian Berndt Date: Tue, 3 Dec 2024 09:50:53 +0100 Subject: [PATCH 13/26] skip autolink to ecto module to prevent warning when building docs --- mix.exs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/mix.exs b/mix.exs index c44dbaa1..f4089a74 100644 --- a/mix.exs +++ b/mix.exs @@ -102,7 +102,10 @@ defmodule Backpex.MixProject do """ end - end + end, + skip_code_autolink_to: [ + "Ecto.Query.DynamicExpr" + ] ] end From 420f59cc78ae675d870b1d80aadb386d8a57cdd5 Mon Sep 17 00:00:00 2001 From: Phil-Bastian Berndt Date: Tue, 3 Dec 2024 09:55:50 +0100 Subject: [PATCH 14/26] improve select options --- lib/backpex/fields/select.ex | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/backpex/fields/select.ex b/lib/backpex/fields/select.ex index c8b52e79..5ac96860 100644 --- a/lib/backpex/fields/select.ex +++ b/lib/backpex/fields/select.ex @@ -2,12 +2,12 @@ defmodule Backpex.Fields.Select do @config_schema [ options: [ doc: "List of options or function that receives the assigns.", - type: {:or, [:keyword_list, {:fun, 1}]}, + type: {:or, [{:list, :any}, {:fun, 1}]}, required: true ], prompt: [ doc: "The text to be displayed when no option is selected or function that receives the assigns.", - type: :string + type: {:or, [:string, {:fun, 1}]} ] ] From fd3fb54543e324130389f4b082152b818e733b1e Mon Sep 17 00:00:00 2001 From: Phil-Bastian Berndt Date: Tue, 3 Dec 2024 10:00:20 +0100 Subject: [PATCH 15/26] default max_entries to 1 --- demo/lib/demo_web/live/user_live.ex | 1 - demo/lib/demo_web/resource_actions/upload.ex | 1 - lib/backpex/fields/upload.ex | 6 +++--- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/demo/lib/demo_web/live/user_live.ex b/demo/lib/demo_web/live/user_live.ex index 4f9387f3..0b52edd1 100644 --- a/demo/lib/demo_web/live/user_live.ex +++ b/demo/lib/demo_web/live/user_live.ex @@ -64,7 +64,6 @@ defmodule DemoWeb.UserLive do label: "Avatar", upload_key: :avatar, accept: ~w(.jpg .jpeg .png), - max_entries: 1, max_file_size: 512_000, put_upload_change: &put_upload_change/6, consume_upload: &consume_upload/4, diff --git a/demo/lib/demo_web/resource_actions/upload.ex b/demo/lib/demo_web/resource_actions/upload.ex index 1783793c..ce421651 100644 --- a/demo/lib/demo_web/resource_actions/upload.ex +++ b/demo/lib/demo_web/resource_actions/upload.ex @@ -18,7 +18,6 @@ defmodule DemoWeb.ResourceActions.Upload do label: "Upload", upload_key: :upload, accept: ~w(.jpg .jpeg .png), - max_entries: 1, put_upload_change: &put_upload_change/6, consume_upload: &consume_upload/4, remove_uploads: &remove_uploads/3, diff --git a/lib/backpex/fields/upload.ex b/lib/backpex/fields/upload.ex index 490098b6..52770487 100644 --- a/lib/backpex/fields/upload.ex +++ b/lib/backpex/fields/upload.ex @@ -9,8 +9,9 @@ defmodule Backpex.Fields.Upload do type: {:list, :string} ], max_entries: [ - doc: "Required number of max files that can be uploaded.", - type: :non_neg_integer + doc: "Number of max files that can be uploaded.", + type: :non_neg_integer, + default: 1 ], max_file_size: [ doc: "Optional maximum file size in bytes to be allowed to uploaded.", @@ -210,7 +211,6 @@ defmodule Backpex.Fields.Upload do label: "Avatar", upload_key: :avatar, accept: ~w(.jpg .jpeg .png), - max_entries: 1, max_file_size: 512_000, put_upload_change: &put_upload_change/6, consume_upload: &consume_upload/4, From 63d9106338c9ccaef75031e7418bec1aea3c8f90 Mon Sep 17 00:00:00 2001 From: Phil-Bastian Berndt Date: Tue, 3 Dec 2024 10:04:21 +0100 Subject: [PATCH 16/26] improve upload options --- lib/backpex/fields/upload.ex | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/backpex/fields/upload.ex b/lib/backpex/fields/upload.ex index 52770487..bd824457 100644 --- a/lib/backpex/fields/upload.ex +++ b/lib/backpex/fields/upload.ex @@ -2,11 +2,13 @@ defmodule Backpex.Fields.Upload do @config_schema [ upload_key: [ doc: "Required identifier for the upload field (the name of the upload).", - type: :atom + type: :atom, + required: true ], accept: [ - doc: "Required filetypes that will be accepted.", - type: {:list, :string} + doc: "List of filetypes that will be accepted or `:any`.", + type: {:or, [{:list, :string}, :atom]}, + default: :any ], max_entries: [ doc: "Number of max files that can be uploaded.", From 3744458555328873f9cfa841a9a2c6dfc269b1fa Mon Sep 17 00:00:00 2001 From: Phil-Bastian Berndt Date: Tue, 3 Dec 2024 10:08:29 +0100 Subject: [PATCH 17/26] Revert "move debounce and throttle to general options" This reverts commit 388016fef07ebdde9ffd4199510db3c999529053. --- lib/backpex/field.ex | 8 -------- lib/backpex/fields/boolean.ex | 11 ++++++++++- lib/backpex/fields/currency.ex | 11 ++++++++++- lib/backpex/fields/date.ex | 8 ++++++++ lib/backpex/fields/date_time.ex | 20 +++++++++++++++++++- lib/backpex/fields/number.ex | 8 ++++++++ lib/backpex/fields/text.ex | 8 ++++++++ lib/backpex/fields/textarea.ex | 8 ++++++++ lib/backpex/fields/url.ex | 8 ++++++++ 9 files changed, 79 insertions(+), 11 deletions(-) diff --git a/lib/backpex/field.ex b/lib/backpex/field.ex index 8cae9452..7f0b6124 100644 --- a/lib/backpex/field.ex +++ b/lib/backpex/field.ex @@ -95,14 +95,6 @@ defmodule Backpex.Field do with the message and metadata. """, type: {:fun, 1} - ], - debounce: [ - doc: "Timeout value (in milliseconds), \"blur\" or function that receives the assigns.", - type: {:or, [:pos_integer, :string, {:fun, 1}]} - ], - throttle: [ - doc: "Timeout value (in milliseconds) or function that receives the assigns.", - type: {:or, [:pos_integer, {:fun, 1}]} ] ] diff --git a/lib/backpex/fields/boolean.ex b/lib/backpex/fields/boolean.ex index 58a23c61..7d3c6499 100644 --- a/lib/backpex/fields/boolean.ex +++ b/lib/backpex/fields/boolean.ex @@ -1,5 +1,14 @@ defmodule Backpex.Fields.Boolean do - @config_schema [] + @config_schema [ + debounce: [ + doc: "Timeout value (in milliseconds), \"blur\" or function that receives the assigns.", + type: {:or, [:pos_integer, :string, {:fun, 1}]} + ], + throttle: [ + doc: "Timeout value (in milliseconds) or function that receives the assigns.", + type: {:or, [:pos_integer, {:fun, 1}]} + ] + ] @moduledoc """ A field for handling a boolean value. diff --git a/lib/backpex/fields/currency.ex b/lib/backpex/fields/currency.ex index bb5aa5b3..0ad96fde 100644 --- a/lib/backpex/fields/currency.ex +++ b/lib/backpex/fields/currency.ex @@ -1,5 +1,14 @@ defmodule Backpex.Fields.Currency do - @config_schema [] + @config_schema [ + debounce: [ + doc: "Timeout value (in milliseconds), \"blur\" or function that receives the assigns.", + type: {:or, [:pos_integer, :string, {:fun, 1}]} + ], + throttle: [ + doc: "Timeout value (in milliseconds) or function that receives the assigns.", + type: {:or, [:pos_integer, {:fun, 1}]} + ] + ] @moduledoc """ A field for handling a currency value. diff --git a/lib/backpex/fields/date.ex b/lib/backpex/fields/date.ex index 1324c006..e7db8b93 100644 --- a/lib/backpex/fields/date.ex +++ b/lib/backpex/fields/date.ex @@ -9,6 +9,14 @@ defmodule Backpex.Fields.Date do """, type: {:or, [:string, {:fun, 1}]}, default: "%Y-%m-%d" + ], + debounce: [ + doc: "Timeout value (in milliseconds), \"blur\" or function that receives the assigns.", + type: {:or, [:pos_integer, :string, {:fun, 1}]} + ], + throttle: [ + doc: "Timeout value (in milliseconds) or function that receives the assigns.", + type: {:or, [:pos_integer, {:fun, 1}]} ] ] diff --git a/lib/backpex/fields/date_time.ex b/lib/backpex/fields/date_time.ex index dc39e22c..1da16c34 100644 --- a/lib/backpex/fields/date_time.ex +++ b/lib/backpex/fields/date_time.ex @@ -1,6 +1,24 @@ # credo:disable-for-this-file Credo.Check.Design.DuplicatedCode defmodule Backpex.Fields.DateTime do - @config_schema [] + @config_schema [ + format: [ + doc: """ + Format string which will be used to format the date time value or function that formats the date time. + + Can also be a function wich receives a `DateTime` and must return a string. + """, + type: {:or, [:string, {:fun, 1}]}, + default: "%Y-%m-%d %I:%M %p" + ], + debounce: [ + doc: "Timeout value (in milliseconds), \"blur\" or function that receives the assigns.", + type: {:or, [:pos_integer, :string, {:fun, 1}]} + ], + throttle: [ + doc: "Timeout value (in milliseconds) or function that receives the assigns.", + type: {:or, [:pos_integer, {:fun, 1}]} + ] + ] @moduledoc """ A field for handling a date time value. diff --git a/lib/backpex/fields/number.ex b/lib/backpex/fields/number.ex index a0eef0f1..c536b924 100644 --- a/lib/backpex/fields/number.ex +++ b/lib/backpex/fields/number.ex @@ -3,6 +3,14 @@ defmodule Backpex.Fields.Number do placeholder: [ doc: "Placeholder value or function that receives the assigns.", type: {:or, [:string, {:fun, 1}]} + ], + debounce: [ + doc: "Timeout value (in milliseconds), \"blur\" or function that receives the assigns.", + type: {:or, [:pos_integer, :string, {:fun, 1}]} + ], + throttle: [ + doc: "Timeout value (in milliseconds) or function that receives the assigns.", + type: {:or, [:pos_integer, {:fun, 1}]} ] ] diff --git a/lib/backpex/fields/text.ex b/lib/backpex/fields/text.ex index ee66a270..d69b1e0f 100644 --- a/lib/backpex/fields/text.ex +++ b/lib/backpex/fields/text.ex @@ -3,6 +3,14 @@ defmodule Backpex.Fields.Text do placeholder: [ doc: "Placeholder value or function that receives the assigns.", type: {:or, [:string, {:fun, 1}]} + ], + debounce: [ + doc: "Timeout value (in milliseconds), \"blur\" or function that receives the assigns.", + type: {:or, [:pos_integer, :string, {:fun, 1}]} + ], + throttle: [ + doc: "Timeout value (in milliseconds) or function that receives the assigns.", + type: {:or, [:pos_integer, {:fun, 1}]} ] ] diff --git a/lib/backpex/fields/textarea.ex b/lib/backpex/fields/textarea.ex index 6def18cd..578e72c4 100644 --- a/lib/backpex/fields/textarea.ex +++ b/lib/backpex/fields/textarea.ex @@ -4,6 +4,14 @@ defmodule Backpex.Fields.Textarea do doc: "Placeholder value or function that receives the assigns.", type: {:or, [:string, {:fun, 1}]} ], + debounce: [ + doc: "Timeout value (in milliseconds), \"blur\" or function that receives the assigns.", + type: {:or, [:pos_integer, :string, {:fun, 1}]} + ], + throttle: [ + doc: "Timeout value (in milliseconds) or function that receives the assigns.", + type: {:or, [:pos_integer, {:fun, 1}]} + ], rows: [ doc: "Number of visible text lines for the control.", type: :non_neg_integer, diff --git a/lib/backpex/fields/url.ex b/lib/backpex/fields/url.ex index 1f702328..b2c6c3ef 100644 --- a/lib/backpex/fields/url.ex +++ b/lib/backpex/fields/url.ex @@ -3,6 +3,14 @@ defmodule Backpex.Fields.URL do placeholder: [ doc: "Placeholder value or function that receives the assigns.", type: {:or, [:string, {:fun, 1}]} + ], + debounce: [ + doc: "Timeout value (in milliseconds), \"blur\" or function that receives the assigns.", + type: {:or, [:pos_integer, :string, {:fun, 1}]} + ], + throttle: [ + doc: "Timeout value (in milliseconds) or function that receives the assigns.", + type: {:or, [:pos_integer, {:fun, 1}]} ] ] From ac060a9f184b96e6451d6eb95eb2becf35283c07 Mon Sep 17 00:00:00 2001 From: Phil-Bastian Berndt Date: Tue, 3 Dec 2024 10:12:08 +0100 Subject: [PATCH 18/26] add readonly option to supported fields --- lib/backpex/fields/date.ex | 4 ++++ lib/backpex/fields/date_time.ex | 4 ++++ lib/backpex/fields/number.ex | 4 ++++ lib/backpex/fields/text.ex | 4 ++++ lib/backpex/fields/textarea.ex | 4 ++++ 5 files changed, 20 insertions(+) diff --git a/lib/backpex/fields/date.ex b/lib/backpex/fields/date.ex index e7db8b93..55b43845 100644 --- a/lib/backpex/fields/date.ex +++ b/lib/backpex/fields/date.ex @@ -17,6 +17,10 @@ defmodule Backpex.Fields.Date do throttle: [ doc: "Timeout value (in milliseconds) or function that receives the assigns.", type: {:or, [:pos_integer, {:fun, 1}]} + ], + readonly: [ + doc: "Sets the field to readonly. Also see the [panels](/guides/fields/readonly.md) guide.", + type: {:or, [:boolean, {:fun, 1}]} ] ] diff --git a/lib/backpex/fields/date_time.ex b/lib/backpex/fields/date_time.ex index 1da16c34..3b435dba 100644 --- a/lib/backpex/fields/date_time.ex +++ b/lib/backpex/fields/date_time.ex @@ -17,6 +17,10 @@ defmodule Backpex.Fields.DateTime do throttle: [ doc: "Timeout value (in milliseconds) or function that receives the assigns.", type: {:or, [:pos_integer, {:fun, 1}]} + ], + readonly: [ + doc: "Sets the field to readonly. Also see the [panels](/guides/fields/readonly.md) guide.", + type: {:or, [:boolean, {:fun, 1}]} ] ] diff --git a/lib/backpex/fields/number.ex b/lib/backpex/fields/number.ex index c536b924..1b48dcdf 100644 --- a/lib/backpex/fields/number.ex +++ b/lib/backpex/fields/number.ex @@ -11,6 +11,10 @@ defmodule Backpex.Fields.Number do throttle: [ doc: "Timeout value (in milliseconds) or function that receives the assigns.", type: {:or, [:pos_integer, {:fun, 1}]} + ], + readonly: [ + doc: "Sets the field to readonly. Also see the [panels](/guides/fields/readonly.md) guide.", + type: {:or, [:boolean, {:fun, 1}]} ] ] diff --git a/lib/backpex/fields/text.ex b/lib/backpex/fields/text.ex index d69b1e0f..10ce57aa 100644 --- a/lib/backpex/fields/text.ex +++ b/lib/backpex/fields/text.ex @@ -11,6 +11,10 @@ defmodule Backpex.Fields.Text do throttle: [ doc: "Timeout value (in milliseconds) or function that receives the assigns.", type: {:or, [:pos_integer, {:fun, 1}]} + ], + readonly: [ + doc: "Sets the field to readonly. Also see the [panels](/guides/fields/readonly.md) guide.", + type: {:or, [:boolean, {:fun, 1}]} ] ] diff --git a/lib/backpex/fields/textarea.ex b/lib/backpex/fields/textarea.ex index 578e72c4..ec597d66 100644 --- a/lib/backpex/fields/textarea.ex +++ b/lib/backpex/fields/textarea.ex @@ -16,6 +16,10 @@ defmodule Backpex.Fields.Textarea do doc: "Number of visible text lines for the control.", type: :non_neg_integer, default: 2 + ], + readonly: [ + doc: "Sets the field to readonly. Also see the [panels](/guides/fields/readonly.md) guide.", + type: {:or, [:boolean, {:fun, 1}]} ] ] From 9053d367033d5ec17e032a463c7ee90101782b1d Mon Sep 17 00:00:00 2001 From: Phil-Bastian Berndt Date: Tue, 3 Dec 2024 10:14:21 +0100 Subject: [PATCH 19/26] allow functions for all prompts --- lib/backpex/fields/belongs_to.ex | 2 +- lib/backpex/fields/has_many.ex | 2 +- lib/backpex/fields/multi_select.ex | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/backpex/fields/belongs_to.ex b/lib/backpex/fields/belongs_to.ex index 00b52428..221d0df1 100644 --- a/lib/backpex/fields/belongs_to.ex +++ b/lib/backpex/fields/belongs_to.ex @@ -23,7 +23,7 @@ defmodule Backpex.Fields.BelongsTo do ], prompt: [ doc: "The text to be displayed when no option is selected or function that receives the assigns.", - type: :string + type: {:or, [:string, {:fun, 1}]} ] ] diff --git a/lib/backpex/fields/has_many.ex b/lib/backpex/fields/has_many.ex index 3bd3fdb7..5ef7282a 100644 --- a/lib/backpex/fields/has_many.ex +++ b/lib/backpex/fields/has_many.ex @@ -32,7 +32,7 @@ defmodule Backpex.Fields.HasMany do The default value is `"Select options..."`. """, - type: :string + type: {:or, [:string, {:fun, 1}]} ], not_found_text: [ doc: """ diff --git a/lib/backpex/fields/multi_select.ex b/lib/backpex/fields/multi_select.ex index 4d49e9ed..158ebd07 100644 --- a/lib/backpex/fields/multi_select.ex +++ b/lib/backpex/fields/multi_select.ex @@ -7,7 +7,7 @@ defmodule Backpex.Fields.MultiSelect do ], prompt: [ doc: "The text to be displayed when no option is selected or function that receives the assigns.", - type: :string + type: {:or, [:string, {:fun, 1}]} ], not_found_text: [ doc: """ From 7b941b6c698bfb66077c7eb81f1ad9f57f7178c2 Mon Sep 17 00:00:00 2001 From: Phil-Bastian Berndt Date: Tue, 3 Dec 2024 10:15:10 +0100 Subject: [PATCH 20/26] improve multi select options --- lib/backpex/fields/multi_select.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/backpex/fields/multi_select.ex b/lib/backpex/fields/multi_select.ex index 158ebd07..f66f03f6 100644 --- a/lib/backpex/fields/multi_select.ex +++ b/lib/backpex/fields/multi_select.ex @@ -2,7 +2,7 @@ defmodule Backpex.Fields.MultiSelect do @config_schema [ options: [ doc: "List of options or function that receives the assigns.", - type: {:or, [:keyword_list, {:fun, 1}]}, + type: {:or, [{:list, :any}, {:fun, 1}]}, required: true ], prompt: [ From 7fa23a6f061a9ea7616517c56dee2eeebb593a81 Mon Sep 17 00:00:00 2001 From: Phil-Bastian Berndt Date: Tue, 3 Dec 2024 10:21:30 +0100 Subject: [PATCH 21/26] fix placeholder --- lib/backpex/fields/number.ex | 3 +++ lib/backpex/fields/text.ex | 3 +++ lib/backpex/fields/textarea.ex | 2 ++ lib/backpex/fields/url.ex | 1 + 4 files changed, 9 insertions(+) diff --git a/lib/backpex/fields/number.ex b/lib/backpex/fields/number.ex index 1b48dcdf..77962201 100644 --- a/lib/backpex/fields/number.ex +++ b/lib/backpex/fields/number.ex @@ -50,6 +50,7 @@ defmodule Backpex.Fields.Number do Date: Tue, 3 Dec 2024 10:27:03 +0100 Subject: [PATCH 22/26] add upgrade guide for throttle option --- guides/upgrading/v0.9.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/guides/upgrading/v0.9.md b/guides/upgrading/v0.9.md index 8af86181..8e25eb54 100644 --- a/guides/upgrading/v0.9.md +++ b/guides/upgrading/v0.9.md @@ -98,6 +98,11 @@ In case your field has field-specific configuration options, you need to provide use Backpex.Field, config_schema: @config_schema ``` +## Removed string support on `throttle` field options + +The fields that allow the `throttle` options previously supported a string value (e.g. `"500"`). +Please change it to an integer value (e.g. `500`). + ## Resource Action and Item Action `init_change/1` is renamed The term `init_change` was confusing because the result is being used as the base schema / item for the changeset function. Therefore we renamed the function to `base_schema/1` for both Item Actions and Resource Actions. From 9e09a7cc321e71938692c9a9c19c175dd9a95309 Mon Sep 17 00:00:00 2001 From: Phil-Bastian Berndt Date: Tue, 3 Dec 2024 12:21:50 +0100 Subject: [PATCH 23/26] use validated fields on edit --- lib/backpex/live_resource.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/backpex/live_resource.ex b/lib/backpex/live_resource.ex index a9d3e06b..3aa949ed 100644 --- a/lib/backpex/live_resource.ex +++ b/lib/backpex/live_resource.ex @@ -625,7 +625,7 @@ defmodule Backpex.LiveResource do defp apply_action(socket, :edit) do %{live_resource: live_resource, singular_name: singular_name} = socket.assigns - fields = live_resource.fields |> filtered_fields_by_action(socket.assigns, :edit) + fields = live_resource.validated_fields() |> filtered_fields_by_action(socket.assigns, :edit) primary_value = URI.decode(socket.assigns.params["backpex_id"]) item = Resource.get!(primary_value, socket.assigns, live_resource) From aa9ddeadca071419f073bdcf999b7da701973164 Mon Sep 17 00:00:00 2001 From: Florian Arens <60519307+Flo0807@users.noreply.github.com> Date: Wed, 4 Dec 2024 08:40:59 +0100 Subject: [PATCH 24/26] Add `can?` option --- lib/backpex/field.ex | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/backpex/field.ex b/lib/backpex/field.ex index 7f0b6124..7eef91a1 100644 --- a/lib/backpex/field.ex +++ b/lib/backpex/field.ex @@ -46,7 +46,13 @@ defmodule Backpex.Field do type: :boolean ], visible: [ - doc: "Function to change the visibility of a field. Receives the assigns and has to return a boolean.", + doc: + "Function to change the visibility of a field for all views except index. Receives the assigns and has to return a boolean.", + type: {:fun, 1} + ], + can?: [ + doc: + "Function to change the visibility of a field for all views. Receives the assigns and has to return a boolean.", type: {:fun, 1} ], panel: [ From 00f34f7d706ae9539f25c9c691458e208040a609 Mon Sep 17 00:00:00 2001 From: Florian Arens <60519307+Flo0807@users.noreply.github.com> Date: Wed, 4 Dec 2024 10:36:07 +0100 Subject: [PATCH 25/26] Add field-specific options for time field --- lib/backpex/fields/time.ex | 40 ++++++++++++++++++++++++++++---------- 1 file changed, 30 insertions(+), 10 deletions(-) diff --git a/lib/backpex/fields/time.ex b/lib/backpex/fields/time.ex index 148918f7..ff34abc7 100644 --- a/lib/backpex/fields/time.ex +++ b/lib/backpex/fields/time.ex @@ -1,19 +1,39 @@ # credo:disable-for-this-file Credo.Check.Design.DuplicatedCode defmodule Backpex.Fields.Time do - @default_format "%I:%M %p" + @config_schema [ + format: [ + doc: """ + Format string which will be used to format the date time value or function that formats the date time. + + Can also be a function wich receives a `DateTime` and must return a string. + """, + type: {:or, [:string, {:fun, 1}]}, + default: "%I:%M %p" + ], + debounce: [ + doc: "Timeout value (in milliseconds), \"blur\" or function that receives the assigns.", + type: {:or, [:pos_integer, :string, {:fun, 1}]} + ], + throttle: [ + doc: "Timeout value (in milliseconds) or function that receives the assigns.", + type: {:or, [:pos_integer, {:fun, 1}]} + ], + readonly: [ + doc: "Sets the field to readonly. Also see the [panels](/guides/fields/readonly.md) guide.", + type: {:or, [:boolean, {:fun, 1}]} + ] + ] - # credo:disable-for-next-line Credo.Check.Readability.StrictModuleLayout @moduledoc """ A field for handling a time value. - ## Options + ## Field-specific options + + See `Backpex.Field` for general field options. - * `:format` - Format string which will be used to format the time value or function that formats the time. - Defaults to `#{@default_format}`. - * `:debounce` - Optional integer timeout value (in milliseconds), "blur" or function that receives the assigns. - * `:throttle` - Optional integer timeout value (in milliseconds) or function that receives the assigns. + #{NimbleOptions.docs(@config_schema)} - ## Example + ## Examples @impl Backpex.LiveResource def fields do @@ -26,11 +46,11 @@ defmodule Backpex.Fields.Time do ] end """ - use BackpexWeb, :field + use Backpex.Field, config_schema: @config_schema @impl Backpex.Field def render_value(assigns) do - format = Map.get(assigns.field_options, :format, @default_format) + format = assigns.field_options[:format] value = cond do From fa00a8f078737641cc47af03872074ea6a5eb74a Mon Sep 17 00:00:00 2001 From: Phil-Bastian Berndt Date: Wed, 4 Dec 2024 17:01:18 +0100 Subject: [PATCH 26/26] add debounce and throttle option to belongs_to and select --- lib/backpex/fields/belongs_to.ex | 8 ++++++++ lib/backpex/fields/select.ex | 8 ++++++++ 2 files changed, 16 insertions(+) diff --git a/lib/backpex/fields/belongs_to.ex b/lib/backpex/fields/belongs_to.ex index 221d0df1..f152e01a 100644 --- a/lib/backpex/fields/belongs_to.ex +++ b/lib/backpex/fields/belongs_to.ex @@ -24,6 +24,14 @@ defmodule Backpex.Fields.BelongsTo do prompt: [ doc: "The text to be displayed when no option is selected or function that receives the assigns.", type: {:or, [:string, {:fun, 1}]} + ], + debounce: [ + doc: "Timeout value (in milliseconds), \"blur\" or function that receives the assigns.", + type: {:or, [:pos_integer, :string, {:fun, 1}]} + ], + throttle: [ + doc: "Timeout value (in milliseconds) or function that receives the assigns.", + type: {:or, [:pos_integer, {:fun, 1}]} ] ] diff --git a/lib/backpex/fields/select.ex b/lib/backpex/fields/select.ex index 5ac96860..7b898805 100644 --- a/lib/backpex/fields/select.ex +++ b/lib/backpex/fields/select.ex @@ -8,6 +8,14 @@ defmodule Backpex.Fields.Select do prompt: [ doc: "The text to be displayed when no option is selected or function that receives the assigns.", type: {:or, [:string, {:fun, 1}]} + ], + debounce: [ + doc: "Timeout value (in milliseconds), \"blur\" or function that receives the assigns.", + type: {:or, [:pos_integer, :string, {:fun, 1}]} + ], + throttle: [ + doc: "Timeout value (in milliseconds) or function that receives the assigns.", + type: {:or, [:pos_integer, {:fun, 1}]} ] ]