View Source Backpex.LiveResource behaviour (Backpex v0.3.0)
LiveResource makes it easy to manage existing resources in your application.
It provides extensive configuration options in order to meet everyone's needs.
In connection with Backpex.Components you can build an individual admin dashboard
on top of your application in minutes.
Example
Before you start make sure Backpex is properly configured.
Backpex.LiveResource is the module that will generate the corresponding LiveViews for the resource you configured.
You are required to set some general options to tell Backpex where to find the resource and what
changesets should be used. In addition you have to provide names and a list of fields.
A minimal configuration looks something like this:
defmodule MyAppWeb.UserLive do
use Backpex.LiveResource,
layout: {MyAppWeb.LayoutView, :admin},
schema: MyApp.User,
repo: MyApp.Repo,
update_changeset: &MyApp.User.update_changeset/3,
create_changeset: &MyApp.User.create_changeset/3
@impl Backpex.LiveResource
def singular_name(), do: "User"
@impl Backpex.LiveResource
def plural_name(), do: "Users"
@impl Backpex.LiveResource
def fields do
[
username: %{
module: Backpex.Fields.Text,
label: "Username"
},
first_name: %{
module: Backpex.Fields.Text,
label: "First Name"
},
last_name: %{
module: Backpex.Fields.Text,
label: "Last Name"
},
]
endYou are also required to configure your router in order to serve the generated LiveViews:
defmodule MyAppWeb.Router
import Backpex.Router
scope "/admin", MyAppWeb do
pipe_through :browser
live_session :default, on_mount: Backpex.InitAssigns do
live_resources("/users", UserLive)
end
end
end
use Backpex.LiveResourceWhen you
use Backpex.LiveResource, theBackpex.LiveResourcemodule will set@behavior Backpex.LiveResource. Additionally it will create a LiveView based on the given configuration in order to create fully functional index, show, new and edit views for a resource. It will also insert fallback functions that can be overridden.
Define a resource
To explain configuration settings we created an event schema with a corresponding EventLive configuration file.
defmodule MyAppWeb.EventLive do
alias MyApp.Event
use Backpex.LiveResource,
layout: {MyAppWeb.LayoutView, :admin}, # Specify the layout you created in the step before
schema: Event, # Schema of the resource
repo: MyApp.Repo, # Ecto repository
update_changeset: &Event.update_changeset/3, # Changeset to be used for update queries
create_changeset: &Event.create_changeset/3, # Changeset to be used for create queries
pubsub: Demo.PubSub, # PubSub name of the project.
topic: "events", # The topic for PubSub
event_prefix: "event_", # The event prefix for Pubsub, to differentiate between events of different resources when subscribed to multiple resources
fluid?: true # Optional to define if your resource should be rendered full width. Depends on the your [layout configuration](installation.md)
# Singular name to displayed on the resource page
@impl Backpex.LiveResource
def singular_name(), do: "Event"
# Plural name to displayed on the resource page
@impl Backpex.LiveResource
def plural_name(), do: "Events"
# Field configurations
# Here can configure which fields of the schema should be displayed on your dashboard.
# Backpex provides certain field modules that may be used for displaying the field on index and form views.
# You may define you own module or overwrite the render functions in this configuration (`render` for index views
# and `render_form` for form views).
@impl Backpex.LiveResource
def fields do
[
# The title field of our event schema
title: %{
module: Backpex.Fields.Text,
label: "Title"
},
# The location field of our event schema. It should not be displayed on `:edit` view.
location: %{
module: Backpex.Fields.Text,
label: "Location",
except: [:edit]
},
# The location field of our event schema. We use the Backpex URL module in order to make the url clickable.
# This field is only displayed on `:index` view.
url: %{
module: Backpex.Fields.URL,
label: "Url",
only: [:index]
},
# The begins_at field of our event schema. We provide or own render function to display this field on index views.
# The value can be extracted from the assigns.
begins_at: %{
module: Backpex.Fields.Date,
label: "Begins At",
render: fn assigns ->
~H"""
<div class="text-red-500">
<%= @value %>
</div>
"""
end
},
# The ends_at field of our event schema. This field should not be sortable.
ends_at: %{
module: Backpex.Fields.Date,
label: "Ends at"
},
# The published field of our url schema. We use the boolean field to display a switch button on edit views.
published: %{
module: Backpex.Fields.Boolean,
label: "Published",
sortable: false
}
]
end
endTemplates
You are able to customize certain parts of Backpex. While you may use our app shell layout only you may also define functions to provide additional templates to be rendered on the resource LiveView or completely overwrite certain parts like the header or main content.
See render_resource_slot/3 for supported positions.
Example:
# in your resource configuration file
# to add content above main on index view
def render_resource_slot(assigns, :index, :before_main), do: ~H"Hello World!"Item Query
It is possible to manipulate the query when fetching resources for index, show and edit view by defining an item_query function.
In all queries we define a from query with a named binding to fetch all existing resources on index view or a specific resource on show / edit view.
After that, we call the item_query function. By default this returns the incoming query.
The item_query function makes it easy to add custom query expressions.
For example, you could filter posts by a published boolean on index view.
# in your resource configuration file
@impl Backpex.LiveResource
def item_query(query, :index, _assigns) do
query
|> where([post], post.published)
endIn this example we also made use of the named binding. It's always the name of the provided schema in snake_case.
It is recommended to build your item_query on top of the incoming query. Otherwise you will likely get binding errors.
Authorize Actions
Use can?(_assigns, _action, _item) function in you resource configuration to limit access to item actions
(Actions: :index, :new, :show, :edit, :delete, :your_item_action_key, :your_resource_action_key).
The function is not required and returns true by default.
The item is nil for any action that does not require an item to be performed (:index, :new, :your_resource_action_key).
Examples:
# _item is nil for any action that does not require an item to be performed
def can?(_assigns, :new, _item), do: false
def can?(_assigns, :my_item_action, item), do: item.role == :admin
def can?(assigns, :my_resource_action, nil), do: assigns.current_user == :adminNote that item actions are always displayed if they are defined. If you want to remove item actions completely, you must restrict access to them with
can?/3and remove the action with theitem_actions/1function.
Resource Actions
You may define actions for certain resources in order to integrate complex processes into Backpex.
Action routes are automatically generated when using the live_resources macro.
For example you could add an invite process to your user resource as shown in the following.
defmodule MyAppWeb.Admin.Actions.Invite do
use Backpex.ResourceAction
import Ecto.Changeset
@impl Backpex.ResourceAction
def label, do: "Invite"
@impl Backpex.ResourceAction
def title, do: "Invite user"
# you can reuse Backpex fields in the field definition
@impl Backpex.ResourceAction
def fields do
[
email: %{
module: Backpex.Fields.Text,
label: "Email",
type: :string
}
]
end
@required_fields ~w[email]a
@impl Backpex.ResourceAction
def changeset(change, attrs) do
change
|> cast(attrs, @required_fields)
|> validate_required(@required_fields)
|> validate_email(:email)
end
# your action to be performed
@impl Backpex.ResourceAction
def handle(_socket, params) do
# Send mail
# We suppose there was no error.
if true do
{:ok, "An email to #{params[:email]} was sent successfully."}
else
{:error, "An error occurred while sending an email to #{params[:email]}!"}
end
end
end# in your resource configuration file
# each action consists out of an unique id and the corresponding action module
@impl Backpex.LiveResource
def resource_actions() do
[
%{
module: MyWebApp.Admin.ResourceActions.Invite,
id: :invite
}
]
endOrdering
You may provide an init_order option to specify how the initial index page is being ordered.
# in your resource configuration file
use Backpex.LiveResource,
...,
init_order: %{by: :inserted_at, direction: :desc}
# RoutingRouting
You are required to configure your router in order to point to the resources created in before steps.
Make sure to use the Backpex.InitAssigns hook to ensure all Backpex assigns are applied to the LiveViews.
You have to use the Backpex.Router.live_resources/3 macro to generate routes for your resources.
# MyAppWeb.Router
import Backpex.Router
scope "/admin", MyAppWeb do
pipe_through :browser
live_session :default, on_mount: Backpex.InitAssigns do
live_resources("/events", EventLive)
endIn addition you have to use the Backpex.Router.backpex_routes macro. It will add some more routes at base scope. You can place this anywhere in your router.
We will mainly use this routes to insert a Backpex.CookieController. We need it in order to save some user related settings (e.g. which columns on index view you selected to be active).
# MyAppWeb.Router
import Backpex.Router
scope "/" do
pipe_through :browser
backpex_routes()
endSearching
You may flag fields as searchable. A search input will appear automatically on the resource index view.
# in your resource configuration file
@impl Backpex.LiveResource
def fields do
[
%{
...,
searchable: true
}
]
endFor a custom placeholder, you can use the elixir search_placeholder/0 callback.
# in your resource configuration file
@impl Backpex.LiveResource
def search_placeholder, do: "This will be shown in the search input."In addition to basic searching, Backpex allows you to perform full-text searches on resources (see Full-Text Search Guide).
Hooks
You may define hooks that are called after their respective action. Those hooks are on_item_created, on_item_updated and on_item_deleted.
These methods receive the socket and the corresponding item and are expected to return a socket.
# in your resource configuration file
@impl Backpex.LiveResource
def on_item_created(socket, item) do
# send an email on user creation
socket
endPubSub
PubSub settings are required in order to support live updates.
# in your resource configuration file
use Backpex.LiveResource,
...,
pubsub: Demo.PubSub, # PubSub name of the project.
topic: "events", # The topic for PubSub
event_prefix: "event_" # The event prefix for Pubsub, to differentiate between events of different resources when subscribed to multiple resourcesIn addition you may react to ...deleted, ...updated and ...created events via handle_info
# in your resource configuration file
@impl Phoenix.LiveView
def handle_info({"event_created", item}, socket) do
# make something in response to the event, for example show a toast to all users currently on the resource that an event has been created.
{:noreply, socket}
endNavigation
You may define a custom navigation path that is called after the item is saved. The method receives the socket, the live action and the corresponding item and is expected to return a route path.
# in your resource configuration file
@impl Backpex.LiveResource
def return_to(socket, assigns, _action, _item) do
# return to user index after saving
Routes.user_path(socket, :index)
endPanels
You are able to define panels to group certain fields together. Panels are displayed in the provided order.
The Backpex.LiveResource.panels/0 function has to return a keyword list with an identifier and label for each panel.
You can move fields into panels with the panel field configuration that has to return the identifier of the corresponding panel. Fields without a panel are displayed in the :default panel. The :default panel has no label.
Note that a panel is not displayed when there are no fields in it.
# in your resource configuration file
@impl Backpex.LiveResource
def panels do
[
contact: "Contact"
]
end
# in your fields list
@impl Backpex.LiveResource
def fields do
[
%{
...,
panel: :contact
}
]
endDefault values
It is possible to assign default values to fields.
# in your resource configuration file
@impl Backpex.LiveResource
def fields do
[
username: %{
default: fn _assigns -> "Default Username" end
}
]
endNote that default values are set when creating new resources only.
Alignment
You may align fields on index view. By default fields are aligned to the left.
We currently support the following alignments: :left, :center and :right.
# in your resource configuration file
@impl Backpex.LiveResource
def fields do
[
%{
...,
align: :center
}
]
endIn addition to field alignment, you can align the labels on form views (index, edit, resource_action) using the align_label field option.
We currently support the following label orientations: :top, :center and :bottom.
# in your resource configuration file
@impl Backpex.LiveResource
def fields do
[
%{
...,
align_label: :top
}
]
endFields Visibility
You are able to change visibility of fields based on certain conditions (assigns).
Imagine you want to implement a checkbox in order to toggle an input field (post likes). Initially, the input field should be visible when it has a certain value (post likes > 0).
# in your resource configuration file
@impl Backpex.LiveResource
def fields do
[
# show_likes is a virtual field in the post schema
show_likes: %{
module: Backpex.Fields.Boolean,
label: "Show likes",
# initialize the button based on the likes value
select: dynamic([post: p], fragment("? > 0", p.likes)),
},
likes: %{
module: Backpex.Fields.Number,
label: "Likes",
# display the field based on the `show_likes` value
# the value can be part of the changeset or item (when edit view is opened initially).
visible: fn
%{live_action: :new} = assigns ->
Map.get(assigns.changeset.changes, :show_likes)
%{live_action: :edit} = assigns ->
Map.get(assigns.changeset.changes, :show_likes, Map.get(assigns.item, :show_likes, false))
_assigns ->
true
end
}
]
endNote that hidden fields are not exempt from validation by Backpex itself and the visible function is not executed on
:index.
In addition to visible/1, we provide a can?1 function that you can use to determine the visibility of a field.
It can also be used on :index and takes the assigns as a parameter.
# in your resource configuration file
inserted_at: %{
module: Backpex.Fields.DateTime,
label: "Created At",
can?: fn
%{live_action: :show} = _assigns ->
true
_assigns ->
false
end
}Tooltips
We support tooltips via daisyUI.
Index Editable
A small number of fields support index editable. These fields can be edited inline on the index view.
You must enable index editable for a field.
# in your resource configuration file
def fields do
[
name: %{
module: Backpex.Fields.Text,
label: "Name",
index_editable: true
}
]
endCurrently supported by the following fields:
Backpex.Fields.BelongsToBackpex.Fields.DateBackpex.Fields.DateTimeBackpex.Fields.NumberBackpex.Fields.SelectBackpex.Fields.Text
Note you can add index editable support to your custom fields by defining the
render_index_form/1function and enabling index editable for your field.
Additional classes for index table rows
We provide the Backpex.LiveResource.index_row_class option to add additional classes to table rows
on the index view. This allows you, for example, to color the rows.
# in your resource configuration file
@impl Backpex.LiveResource
def index_row_class(assigns, item, selected, index), do: "bg-yellow-100"Note that we call the function twice. Once for the row on the
trelement and a second time for the item action overlay, because in most cases the overlay should have the same style applied. For this reason, Tailwind CSS modifiers such asevenandoddwill not always work as intended. Use the provided index instead. The index starts with 0 for the first item.
Summary
Callbacks
The function that can be used to restrict access to certain actions. It will be called before performing
an action and aborts when the function returns false.
A list of fields defining your resource. See Backpex.Field.
A optional keyword list of filters to be used on the index view.
An extra class to be added to table rows on the index view.
A list of item_actions that may be performed on (selected) items.
The function that can be used to inject an ecto query. The query will be used when resources are being fetched. This happens on index, edit
and show view. In most cases this function will be used to filter items on index view based on certain criteria, but it may also be used
to join other tables on edit or show view.
A list of metrics shown on the index view of your resource.
This function is executed when an item has been created.
This function is executed when an item has been deleted.
This function is executed when an item has been updated.
A list of panels to group certain fields together.
The plural name of the resource used for translations and titles.
The function that can be used to add content to certain positions on Backpex views. It may also be used to overwrite content.
A list of resource_actions that may be performed on the given resource.
This function navigates to the specified path when an item has been created or updated. Defaults to the previous resource path (index or edit).
Replaces the default placeholder for the index search.
The singular name of the resource used for translations and titles.
Functions
Uses LiveResource in the current module to make it a LiveResource.
Calculates the total amount of pages.
Calls the changeset function with the given change and target.
Checks whether user is allowed to perform provided action or not
Filters a field by a given action. It checks whether the field contains the only or except key and decides whether or not to keep the field.
Returns all filter options.
Returns filtered fields by a certain action.
Returns list of active filters.
Returns list of filter options from query options
List all items for current page with filters and search applied.
Maybe subscribes to pubsub.
Returns order options by params.
Checks whether a field is orderable or not.
Returns all orderable fields. A field is orderable by default.
Parses integer text representation map value of the given key. If the map does not contain the given key or parsing fails the default value is returned.
Returns pubsub settings based on configuration.
Returns all search options.
Returns all searchable fields. A field is not searchable by default.
Validates a page number.
Checks whether the given value is in a list of permitted values. Otherwise return default value.
Callbacks
The function that can be used to restrict access to certain actions. It will be called before performing
an action and aborts when the function returns false.
@callback fields() :: list()
A list of fields defining your resource. See Backpex.Field.
@callback filters() :: keyword()
A optional keyword list of filters to be used on the index view.
A optional keyword list of filters to be used on the index view.
@callback index_row_class( assigns :: map(), item :: map(), selected :: boolean(), index :: integer() ) :: binary() | nil
An extra class to be added to table rows on the index view.
A list of item_actions that may be performed on (selected) items.
@callback item_query(query :: Ecto.Query.t(), live_action :: atom(), assigns :: map()) :: Ecto.Query.t()
The function that can be used to inject an ecto query. The query will be used when resources are being fetched. This happens on index, edit
and show view. In most cases this function will be used to filter items on index view based on certain criteria, but it may also be used
to join other tables on edit or show view.
The function has to return an Ecto.Query. It is recommended to build your item_query on top of the incoming query. Otherwise you will likely get binding errors.
@callback metrics() :: keyword()
A list of metrics shown on the index view of your resource.
@callback on_item_created(socket :: Phoenix.LiveView.Socket.t(), item :: map()) :: Phoenix.LiveView.Socket.t()
This function is executed when an item has been created.
@callback on_item_deleted(socket :: Phoenix.LiveView.Socket.t(), item :: map()) :: Phoenix.LiveView.Socket.t()
This function is executed when an item has been deleted.
@callback on_item_updated(socket :: Phoenix.LiveView.Socket.t(), item :: map()) :: Phoenix.LiveView.Socket.t()
This function is executed when an item has been updated.
@callback panels() :: list()
A list of panels to group certain fields together.
@callback plural_name() :: binary()
The plural name of the resource used for translations and titles.
@callback render_resource_slot(assigns :: map(), action :: atom(), position :: atom()) :: %Phoenix.LiveView.Rendered{ caller: term(), dynamic: term(), fingerprint: term(), root: term(), static: term() }
The function that can be used to add content to certain positions on Backpex views. It may also be used to overwrite content.
The following actions are supported: :index, :show
The following positions are supported for the :index action: :page_title, :actions, :filters, :metrics and :main.
The following positions are supported for the :show action: :page_title and :main.
In addition to this, content can be inserted between the main positions via the following extra spots: :before_page_title, :before_actions, :before_filters, :before_metrics and :before_main.
@callback resource_actions() :: list()
A list of resource_actions that may be performed on the given resource.
@callback return_to( socket :: Phoenix.LiveView.Socket.t(), assigns :: map(), action :: atom(), item :: map() ) :: binary()
This function navigates to the specified path when an item has been created or updated. Defaults to the previous resource path (index or edit).
@callback search_placeholder() :: binary()
Replaces the default placeholder for the index search.
@callback singular_name() :: binary()
The singular name of the resource used for translations and titles.
Functions
Uses LiveResource in the current module to make it a LiveResource.
use Backpex.LiveResource,
layout: {MyAppWeb.LayoutView, :admin},
schema: MyApp.User,
repo: MyApp.Repo,
update_changeset: &MyApp.User.update_changeset/3,
create_changeset: &MyApp.User.create_changeset/3Options
:layout- Layout to be used by the LiveResource.:schema- Schema for the resource.:repo- Ecto repo that will be used to perform CRUD operations for the given schema.:update_changeset- Changeset to use when updating items. Additional metadata is passed as a keyword list via the third parameter.The list of metadata:
:assigns- the assigns:target- the name of theformtarget that triggered the changeset call. Default tonilif the call was not triggered by a form field.
:create_changeset- Changeset to use when creating items. Additional metadata is passed as a keyword list via the third parameter.The list of metadata:
:assigns- the assigns:target- the name of theformtarget that triggered the changeset call. Default tonilif the call was not triggered by a form field.
:pubsub- PubSub name of the project.:topic- The topic for PubSub.:event_prefix- The event prefix for Pubsub, to differentiate between events of different resources when subscribed to multiple resources.:per_page_options- The page size numbers you can choose from. Defaults to[15, 50, 100].:per_page_default- The default page size number. Defaults to the first element of theper_page_optionslist.:init_order- Order that will be used when no other order options are given. Defaults to%{by: :id, direction: :asc}.
Calculates the total amount of pages.
Examples
iex> Backpex.LiveResource.calculate_total_pages(1, 2)
1
iex> Backpex.LiveResource.calculate_total_pages(10, 10)
1
iex> Backpex.LiveResource.calculate_total_pages(20, 10)
2
iex> Backpex.LiveResource.calculate_total_pages(25, 6)
5
call_changeset_function(item, changeset_function, change, assigns, target \\ nil)
View SourceCalls the changeset function with the given change and target.
Checks whether user is allowed to perform provided action or not
Filters a field by a given action. It checks whether the field contains the only or except key and decides whether or not to keep the field.
Examples
iex> Backpex.LiveResource.filter_field_by_action(%{only: [:index]}, :index)
true
iex> Backpex.LiveResource.filter_field_by_action(%{only: [:edit]}, :index)
false
iex> Backpex.LiveResource.filter_field_by_action(%{except: [:edit]}, :index)
true
iex> Backpex.LiveResource.filter_field_by_action(%{except: [:index]}, :index)
false
Returns all filter options.
Returns filtered fields by a certain action.
Example
iex> Backpex.LiveResource.filtered_fields_by_action([field1: %{label: "Field1"}, field2: %{label: "Field2"}], %{}, :index)
[field1: %{label: "Field1"}, field2: %{label: "Field2"}]
iex> Backpex.LiveResource.filtered_fields_by_action([field1: %{label: "Field1", except: [:show]}, field2: %{label: "Field2"}], %{}, :show)
[field2: %{label: "Field2"}]
iex> Backpex.LiveResource.filtered_fields_by_action([field1: %{label: "Field1", only: [:index]}, field2: %{label: "Field2"}], %{}, :show)
[field2: %{label: "Field2"}]
Returns list of active filters.
Returns list of filter options from query options
get_valid_filters_from_params(params, valid_filters, empty_filter_key)
View SourceList all items for current page with filters and search applied.
Maybe subscribes to pubsub.
order_options_by_params(params, fields, init_order, permitted_order_directions)
View SourceReturns order options by params.
Examples
iex> Backpex.LiveResource.order_options_by_params(%{"order_by" => "field", "order_direction" => "asc"}, [field: %{}], %{by: :id, direction: :asc}, [:asc, :desc])
%{order_by: :field, order_direction: :asc}
iex> Backpex.LiveResource.order_options_by_params(%{}, [field: %{}], %{by: :id, direction: :desc}, [:asc, :desc])
%{order_by: :id, order_direction: :desc}
iex> Backpex.LiveResource.order_options_by_params(%{"order_by" => "field", "order_direction" => "asc"}, [field: %{orderable: false}], %{by: :id, direction: :asc}, [:asc, :desc])
%{order_by: :id, order_direction: :asc}
Checks whether a field is orderable or not.
Examples
iex> Backpex.LiveResource.orderable?({:name, %{orderable: true}})
true
iex> Backpex.LiveResource.orderable?({:name, %{orderable: false}})
false
iex> Backpex.LiveResource.orderable?({:name, %{}})
true
iex> Backpex.LiveResource.orderable?(nil)
false
Returns all orderable fields. A field is orderable by default.
Example
iex> Backpex.LiveResource.orderable_fields([field1: %{orderable: true}])
[:field1]
iex> Backpex.LiveResource.orderable_fields([field1: %{}])
[:field1]
iex> Backpex.LiveResource.orderable_fields([field1: %{orderable: false}])
[]
Parses integer text representation map value of the given key. If the map does not contain the given key or parsing fails the default value is returned.
Examples
iex> Backpex.LiveResource.parse_integer(%{number: "1"}, :number, 2)
1
iex> Backpex.LiveResource.parse_integer(%{number: "abc"}, :number, 1)
1
Returns pubsub settings based on configuration.
Returns all search options.
Returns all searchable fields. A field is not searchable by default.
Example
iex> Backpex.LiveResource.searchable_fields([field1: %{searchable: true}])
[:field1]
iex> Backpex.LiveResource.searchable_fields([field1: %{}])
[]
iex> Backpex.LiveResource.searchable_fields([field1: %{searchable: false}])
[]
Validates a page number.
Examples
iex> Backpex.LiveResource.validate_page(1, 5)
1
iex> Backpex.LiveResource.validate_page(-1, 5)
1
iex> Backpex.LiveResource.validate_page(6, 5)
5
Checks whether the given value is in a list of permitted values. Otherwise return default value.
Examples
iex> Backpex.LiveResource.value_in_permitted_or_default(3, [1, 2, 3], 5)
3
iex> Backpex.LiveResource.value_in_permitted_or_default(3, [1, 2], 5)
5