View Source Backpex.LiveResource behaviour (Backpex v0.1.1)
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/2,
create_changeset: &MyApp.User.create_changeset/2
@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"
},
]
end
You 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.LiveResource
When you
use Backpex.LiveResource
, theBackpex.LiveResource
module 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/2, # Changeset to be used for update queries
create_changeset: &Event.create_changeset/2, # 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
end
Templates
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)
end
In 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 == :admin
Note 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?/3
and remove the action with theitem_actions/1
function.
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
}
]
end
Ordering
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}
# Routing
Routing
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)
end
In 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()
end
Searching
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
}
]
end
For 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
end
PubSub
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 resources
In 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}
end
Navigation
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)
end
Panels
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
}
]
end
Default 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
}
]
end
Note 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
}
]
end
In 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
}
]
end
Fields 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
}
]
end
Note 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
}
]
end
Currently supported by the following fields:
Note you can add index editable support to your custom fields by defining the
render_index_form/1
function and enabling index editable for your field.
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.
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.
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/2,
create_changeset: &MyApp.User.create_changeset/2
Options
: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 that will be used when updating items. Optionally takes the target as the third parameter.:create_changeset
- Changeset that will be used when creating items. Optionally takes the target as the third parameter.: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_options
list.: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, 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