View Source Backpex.Field behaviour (Backpex v0.2.0)

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.

When creating your own field, you can use the field macro from the BackpexWeb module. It automatically implements the Backpex.Field behaviour and defines some aliases and imports.

The simplest version of a custom field would look like this:

use BackpexWeb, :field

@impl Backpex.Field
def render_value(assigns) do
  ~H"""
  <p>
    <%= HTML.pretty_value(@value) %>
  </p>
  """
end

@impl Backpex.Field
def render_form(assigns) do
  ~H"""
  <div>
    <Layout.field_container>
      <:label>
        <Layout.input_label text={@field_options[:label]} />
      </:label>
      <BackpexForm.field_input type="text" form={@form} field_name={@name} field_options={@field_options} />
    </Layout.field_container>
  </div>
  """
end

The render_value/1 function returns markup that is used to display a value on index and show views. The render_form/1 function returns markup that is used to render a form on edit and new views.

The list of fields in the resource configuration has to be a keyword list. The key has to be the name of the column. The value has to be a list of options represented as a map. At least you are required to provide the module of the field and a label as options. For extra information and options you may have to look into the corresponding field documentation.

Example

def fields do
  [
    rating: %{
      module: Backpex.Fields.Text,
      label: "Rating"
    }
  ]
end

Read-only fields

Fields can be configured to be read-only. In edit view, these fields are rendered with the additional HTML attributes readonly and disabled, ensuring that the user cannot interact with the field or change its value.

In index view, if read-only and index editable are set to true, forms will be rendered with the readonly HTML attribute.

Currently read-only configuration is possible for Backpex.Fields.Text and Backpex.Fields.Textarea on edit view.

On the index view, read-only is supported for all fields with the index editable option.

You can also add read-only functionality to a custom field. To do this, you need to define a render_form_readonly/1 function. This function must return markup to be used when read-only is enabled.

@impl Backpex.Field
def render_form_readonly(assigns) do
  ~H"""
  <div>
    <Layout.field_container>
      <:label>
        <Layout.input_label text={@field[:label]} />
      </:label>
      <BackpexForm.field_input
        type="text"
        form={@form}
        field_name={@name}
        field_options={@field_options}
        readonly
        disabled
      />
    </Layout.field_container>
  </div>
  """
end

When defining a custom field with index editable support, you need to handle the read-only state in the index editable markup. We pass a readonly value to index fields, which will be true or false depending on the read-only option of the field and the can?/3 function.

Fields can be set to read-only in the configuration map of the field by adding the readonly option. This key must contain either a boolean value or a function that returns a boolean value.

The function takes assigns as a parameter. This allows the field to be set as read-only programmatically, as the following example illustrates:

rating: %{
  module: Backpex.Fields.Text,
  label: "Rating",
  readonly: fn assigns ->
    assigns.current_user.role in [:employee]
  end
}

Custom Alias Configuration

Backpex automatically generates aliases for queries in your fields. However, if you try to add two BelongsTo fields of the same association, you will encounter an error indicating that the alias is already in use by another field. To resolve this issue, Backpex allows the assignment of custom aliases to fields, eliminating naming conflicts in queries.

To use a custom alias, especially with a :select query, define it within your field configuration. This allows you to reference associated data via the custom alias.

Example usage of a custom alias together with a :select:

second_category: %{
  module: Backpex.Fields.BelongsTo,
  label: "Second Category",
  display_field: :name,
  searchable: true,
  custom_alias: :second_category,
  select: dynamic([second_category: sc], sc.name)
},

Computed Fields

Sometimes you want to compute new fields based on existing fields.

Imagine there is a user table with first_name and last_name. Now, on your index view you want to add a column to display the full_name. You could create a generated column in you database, but there are several reasons for not adding generated columns for all computed fields you want to display in your application.

Backpex adds a way to support this.

There is a select option you may add to a field. This option has to return a dynamic. This query will then be executed to select fields when listing your resources. In addition this query will also be used to order / search this field.

Therefore you can display the full_name of your users by adding the following field to the resource configuration file.

full_name: %{
  module: Backpex.Fields.Text,
  label: "Full Name",
  searchable: true,
  except: [:edit],
  select: dynamic([user: u], fragment("concat(?, ' ', ?)", u.first_name, u.last_name))
}

We are using a database fragment to build the full_name based on the first_name and last_name of an user. Backpex will select this field when listing resources automatically. Ordering and searching works the same like on all other fields, because Backpex uses the query you provided in the dynamic in order / search queries, too.

We recommend to display this field on index and show view only.

Note: You are required to add a virtual field full_name to your user schema. Otherwise, Backpex is not able to select this field.

Computed fields also work in associations.

For example, you are able to add a select query to a BelongsTo field.

Imagine you want to display a list of posts with the corresponding authors (users). The user column should be a full_name computed by the first_name and last_name:

user: %{
  module: Backpex.Fields.BelongsTo,
  label: "Full Name",
  display_field: :full_name,
  select: dynamic([user: u], fragment("concat(?, ' ', ?)", u.first_name, u.last_name)),
  options_query: fn query, _assigns ->
    query |> select_merge([user], %{full_name: fragment("concat(?, ' ', ?)", user.first_name, user.last_name)})
  end
}

We recommend to add a select_merge to the options_query where you select the same field. Otherwise, displaying the same values in the select form on edit page will not work.

Do not forget to add the virtual field full_name to your user schema in this example, too.

Error Customisation

Each field can define a translate_error/1 function to use custom error messages. The function is called for each error and must return a tuple with a message and metadata.

For example, if you want to indicate that an integer input must be a number:

number: %{
  module: Backpex.Fields.Number,
  label: "Number",
  translate_error: fn
    {_msg, [type: :integer, validation: :cast] = metadata} = _error ->
      {"has to be a number", metadata}

    error ->
      error
  end
}

Summary

Callbacks

This function will be called in the FormComponent and may be used to assign uploads.

Determines whether the field is an association or not.

The field to be displayed on index views. In most cases this is the name / key configured in the corresponding field definition. In fields with associations this value often differs from the name / key. The function will receive the field definition.

Will be used on edit views to render a form for the value of the provided item. This has to be a heex template.

Used to render the readonly version of the field.

Used to render form on index to support index editable.

Will be used on index and show views to render a value from the provided item. This has to be a heex template.

The schema to be used in queries. In most cases this is the schema defined in the resource configuration. In fields with associations this is the schema of the corresponding relation. The function will receive the field definition and the schema defined in the resource configuration.

Defines the search condition. Defaults to an ilike condition with text comparison. The function has to return a query wrapped into a Ecto.Query.dynamic/2 which is then passed into a Ecto.Query.where/3.

Functions

Defines Backpex.Field behaviour and provides default implementations.

Returns a map of types from a list of fields used for the Ecto changeset.

Defines debounce timeout value.

Handles index editable.

Checks whether index editable is enabled or not.

Defines placeholder value.

Determines whether the field should be rendered as readonly version.

Defines throttle timeout value.

Callbacks

Link to this callback

assign_uploads(field, socket)

View Source
@callback assign_uploads(field :: tuple(), socket :: Phoenix.LiveView.Socket.t()) ::
  Phoenix.LiveView.Socket.t()

This function will be called in the FormComponent and may be used to assign uploads.

@callback association?(field :: tuple()) :: boolean()

Determines whether the field is an association or not.

@callback display_field(field :: tuple()) :: atom()

The field to be displayed on index views. In most cases this is the name / key configured in the corresponding field definition. In fields with associations this value often differs from the name / key. The function will receive the field definition.

@callback render_form(assigns :: map()) :: %Phoenix.LiveView.Rendered{
  caller: term(),
  dynamic: term(),
  fingerprint: term(),
  root: term(),
  static: term()
}

Will be used on edit views to render a form for the value of the provided item. This has to be a heex template.

Link to this callback

render_form_readonly(assigns)

View Source (optional)
@callback render_form_readonly(assigns :: map()) :: %Phoenix.LiveView.Rendered{
  caller: term(),
  dynamic: term(),
  fingerprint: term(),
  root: term(),
  static: term()
}

Used to render the readonly version of the field.

Link to this callback

render_index_form(assigns)

View Source (optional)
@callback render_index_form(assigns :: map()) :: %Phoenix.LiveView.Rendered{
  caller: term(),
  dynamic: term(),
  fingerprint: term(),
  root: term(),
  static: term()
}

Used to render form on index to support index editable.

@callback render_value(assigns :: map()) :: %Phoenix.LiveView.Rendered{
  caller: term(),
  dynamic: term(),
  fingerprint: term(),
  root: term(),
  static: term()
}

Will be used on index and show views to render a value from the provided item. This has to be a heex template.

@callback schema(field :: tuple(), schema :: atom()) :: atom()

The schema to be used in queries. In most cases this is the schema defined in the resource configuration. In fields with associations this is the schema of the corresponding relation. The function will receive the field definition and the schema defined in the resource configuration.

Link to this callback

search_condition(schema_name, field_name, search_string)

View Source
@callback search_condition(
  schema_name :: binary(),
  field_name :: binary(),
  search_string :: binary()
) :: Ecto.Query.dynamic_expr()

Defines the search condition. Defaults to an ilike condition with text comparison. The function has to return a query wrapped into a Ecto.Query.dynamic/2 which is then passed into a Ecto.Query.where/3.

Example

Imagine the underlying database type of the field is an integer. Before text comparison in an ilike condition you have to cast the integer to text.

The function could return the following query to make the field searchable.

dynamic(
  [{^schema_name, schema_name}],
  ilike(fragment("CAST(? AS TEXT)", schema_name |> field(^field_name)), ^search_string)
)

Functions

Defines Backpex.Field behaviour and provides default implementations.

Link to this function

align_label(field_options, assigns, default \\ :center)

View Source

Gets alignment option for label.

Returns a map of types from a list of fields used for the Ecto changeset.

Defines debounce timeout value.

Link to this function

handle_index_editable(socket, change)

View Source

Handles index editable.

Link to this function

index_editable_enabled?(field_options, assigns, default \\ false)

View Source

Checks whether index editable is enabled or not.

Link to this function

placeholder(arg1, assigns)

View Source

Defines placeholder value.

Link to this function

readonly?(arg1, assigns)

View Source

Determines whether the field should be rendered as readonly version.

Defines throttle timeout value.