View Source Installation
The following guide will help you to install Backpex in your Phoenix application. We will guide you through the installation process and show you how to create a simple resource.
Prerequisites
Backpex integrates seamlessly with your existing Phoenix LiveView application, but there are a few prerequisites you need to meet before you can start using it.
Phoenix LiveView
Backpex is built on top of Phoenix LiveView, so you need to have Phoenix LiveView installed in your application. If you generate a new Phoenix application using the latest version of the mix phx.new
generator, Phoenix LiveView is included by default.
Alpine.js
Backpex uses Alpine.js for some interactivity. Make sure you have Alpine.js installed in your application.
You can install Alpine.js by installing it via npm:
cd assets && npm install alpinejs
Then, import Alpine.js in your app.js
file, start it and adjust your LiveView configuration:
import Alpine from "alpinejs";
window.Alpine = Alpine;
Alpine.start();
const liveSocket = new LiveSocket('/live', Socket, {
// add this
dom: {
onBeforeElUpdated (from, to) {
if (from._x_dataStack) {
window.Alpine.clone(from, to)
}
},
},
params: { _csrf_token: csrfToken },
})
Tailwind CSS
Backpex uses Tailwind CSS for styling. Make sure you have Tailwind CSS installed in your application. You can install Tailwind CSS by following the official installation guide. If you generate a new Phoenix application using the latest version of the mix phx.new
generator, Tailwind CSS is included by default.
daisyUI
Backpex is styled using daisyUI. Make sure you have daisyUI installed in your application. You can install daisyUI by following the official installation guide.
Ecto
Backpex currently depends on Ecto as the database layer. Make sure you have a running Ecto repository in your application.
Warning
Backpex requires a single primary key field in your database schema. Compound keys are not supported. We tested Backpex with UUID (binary_id), integer (bigserial) and string primary keys. Note that the primary key is used in the URL for Show and Edit Views, so make sure it is always URL-encoded or safe to use in a URL.
If you meet all these prerequisites, you are ready to install and configure Backpex in your Phoenix application. See our installation guide for more information on how to install and configure Backpex.
Add to list of dependencies
In your mix.exs
:
defp deps do
[
...
{:backpex, "~> 0.8.0"}
]
end
See the hex.pm page for the latest version.
Add files to Tailwind content
Backpex uses Tailwind CSS and daisyUI. Make sure to add the Backpex files to your tailwind content in order to include the Backpex styles.
In your tailwind.config.js
:
..,
content: [
...,
// add this line
'../deps/backpex/**/*.*ex'
]
Info
The path to the Backpex files may vary depending on your project setup.
Setup formatter
Backpex ships with a formatter configuration. To use it, add Backpex to the list of dependencies in your .formatter.exs
.
# my_app/.formatter.exs
[
import_deps: [:backpex]
]
Create an example resource
To make it more practical, we are going to create a simple resource that we will use in all our examples later in the installation guide. You can skip this step if you want to use your own resource or just follow the guide.
The example resource will be a Post
resource with the following fields:
title
(string)views
(integer)
Run the following commands:
mix phx.gen.schema Blog.Post blog_posts title:string views:integer
mix ecto.migrate
These commands will generate a Post
schema and a migration file. The migration file will create a blog_posts
table in your database.
You are now prepared to set up the Backpex layout and a LiveResource for the Post
resource.
Create layout
Backpex does not ship with a predefined layout by default to give you the freedom to create your own layout. Instead, it provides components that you can use to build your own layout. You can find all Backpex components in the lib/backpex/components
directory. Layout components are placed in the lib/backpex/components/layout
directory. To start quickly, Backpex provides an Backpex.HTML.Layout.app_shell/1
component. You can use this component to add an app shell layout to your application easily.
See the following example that uses the Backpex.HTML.Layout.app_shell/1
component and some other Backpex Layout components to create a simple layout:
<Backpex.HTML.Layout.app_shell fluid={@fluid?}>
<:topbar>
<Backpex.HTML.Layout.topbar_branding />
<Backpex.HTML.Layout.topbar_dropdown>
<:label>
<label tabindex="0" class="btn btn-square btn-ghost">
<.icon name="hero-user" class="h-8 w-8" />
</label>
</:label>
<li>
<.link navigate={~p"/"} class="flex justify-between text-red-600 hover:bg-gray-100">
<p>Logout</p>
<.icon name="hero-arrow-right-on-rectangle" class="h-5 w-5" />
</.link>
</li>
</Backpex.HTML.Layout.topbar_dropdown>
</:topbar>
<:sidebar>
<Backpex.HTML.Layout.sidebar_item current_url={@current_url} navigate={~p"/admin/posts"}>
<.icon name="hero-book-open" class="h-5 w-5" /> Posts
</Backpex.HTML.Layout.sidebar_item>
</:sidebar>
<Backpex.HTML.Layout.flash_messages flash={@flash} />
<%= @inner_content %>
</Backpex.HTML.Layout.app_shell>
Make sure to add the Backpex.HTML.Layout.flash_messages
component to display flash messages in your layout and do not forget to add the @inner_content
variable to render the content of the LiveView.
Place the layout file in your lib/myapp_web/templates/layout
directory. You can name it like you want, but we recommend to use admin.html.heex
. You can also use this layout as the only layout in your application if your application consists of only an admin interface.
We use the icon/1
component to render icons in the layout. This component is part of the core_components
module that ships with new Phoenix projects. See core_components.ex
. Feel free to use your own icon component or library.
Information
The
Backpex.HTML.Layout.app_shell/1
component accepts a booleanfluid
to determine if aLiveResource
should be rendered full width. There is afluid?
option you can configure in aLiveResource
. See the Fluid Layout documentation for more information.
Configure LiveResource
To create a LiveResource for the Post
resource, you need to create LiveResource module.
defmodule MyAppWeb.Live.PostLive do
use Backpex.LiveResource,
adapter_config: [
schema: MyApp.Blog.Post,
repo: MyApp.Repo,
update_changeset: &MyApp.Blog.Post.update_changeset/3,
create_changeset: &MyApp.Blog.Post.create_changeset/3,
item_query: &__MODULE__.item_query/3
],
layout: {MyAppWeb.Layouts, :admin},
pubsub: [
name: MyApp.PubSub,
topic: "posts",
event_prefix: "post_"
]
end
Backpex.LiveResource
is the module that will generate the corresponding LiveViews for the resource you configured. We provide a macro you have to use to configure the LiveResource. You are required to set some general options to tell Backpex where to find the resource and what changesets should be used. The above example shows the configuration for a Post
resource.
All options you can see in the above example are required:
- The
layout
option tells Backpex which layout to use for the LiveResource. In this case, we use the:admin
(admin.html.heex
) layout created in the previous step. - The
schema
option tells Backpex which schema to use for the resource. - The
repo
option tells Backpex which repo to use for the resource. - The
update_changeset
andcreate_changeset
options tell Backpex which changesets to use for updating and creating the resource. - The
pubsub
option tells Backpex which pubsub options to use for the resource (see the Listen to PubSub Events guide for more information).
In addition to the required options, you pass to the Backpex.LiveResource
macro, you are required to implement the following callback functions in the module:
singular_name/0
- This function should return the singular name of the resource.plural_name/0
- This function should return the plural name of the resource.fields/0
- This function should return a list of fields to display in the LiveResource.
After implementing the required callback functions, our PostLive
module looks like this:
defmodule MyAppWeb.Live.PostLive do
use Backpex.LiveResource,
adapter_config: [
schema: MyApp.Blog.Post,
repo: MyApp.Repo,
update_changeset: &MyApp.Blog.Post.update_changeset/3,
create_changeset: &MyApp.Blog.Post.create_changeset/3,
item_query: &__MODULE__.item_query/3
],
layout: {MyAppWeb.Layouts, :admin},
pubsub: [
name: MyApp.PubSub,
topic: "posts",
event_prefix: "post_"
]
@impl Backpex.LiveResource
def singular_name, do: "Post"
@impl Backpex.LiveResource
def plural_name, do: "Posts"
@impl Backpex.LiveResource
def fields do
[
title: %{
module: Backpex.Fields.Text,
label: "Title"
},
views: %{
module: Backpex.Fields.Number,
label: "Views"
}
]
end
end
The fields/0
function returns a list of fields to display in the LiveResource. See What is a Field? for more information.
Information
We recommend placing the LiveResource in the
lib/myapp_web/live
directory. You can name the module like you want, but in this case, we recommend usingpost_live.ex
.
Configure Routing
To make the LiveResource accessible in your application, you first need to configure your router (router.ex
).
Add Backpex Routes
Backpex needs to add a backpex_cookies
route to your router. This route is used to set the cookies needed for the Backpex LiveResource.
Backpex provides a macro you can use to add the required routes to your router. Make sure to import Backpex.Router
at the top of your router file or prefix the function calls.#
You have to do this step only once in your router file, so if you already added the backpex_routes/0
macro, you can skip this step.
# router.ex
import Backpex.Router
scope "/admin", MyAppWeb do
pipe_through :browser
# add this line
backpex_routes()
end
It does not matter where you place the backpex_routes/0
macro in your router file. You can insert it in every scope you want to, but we recommend placing it in the scope you want to use backpex in, e.g. /admin
.
Add Init Assigns and LiveSession
Backpex provides a Backpex.InitAssigns
module. This will attach the current_url
to the LiveView. Backpex needs it to highlight the current sidebar item in the layout. You can also use your own init assigns module if you want to attach more assigns to the LiveView, but make sure to add the current_url
to the assigns.
We use a live session to add the init assigns to all LiveViews in the /admin
scope.
# router.ex
import Backpex.Router
scope "/admin", MyAppWeb do
pipe_through :browser
backpex_routes()
# add this line
live_session :default, on_mount: Backpex.InitAssigns do
end
end
Add LiveResource routes
To make the LiveResource accessible in your application, you need to add routes for it. Backpex makes it easy to add the required routes to your router by providing the live_resources/3
macro.
# router.ex
import Backpex.Router
scope "/admin", MyAppWeb do
pipe_through :browser
backpex_routes()
live_session :default, on_mount: Backpex.InitAssigns do
# add this line
live_resources "/posts", PostLive
end
end
This macro will add the required routes for the PostLive
module. You can now access the PostLive
LiveResource at /admin/posts
.
Remove default background color
If you start with a new Phoenix project, you may have a default background color set for your body tag. This conflicts with the background color of the Backpex app_shell
.
So if you have this in your root.html.heex
.
<body class="bg-white">
</body>
You should remove the bg-white
class.
If you need this color on your body tag to style your application, consider using another root layout for Backpex (see put_root_layout/2
).
Set daisyUI theme
Backpex supports daisyUI themes. The following steps will guide you through setting up daisyUI themes in your application and optionally adding a theme selector to your layout.
1. Add the themes to your application.
First, you need to add the themes to your tailwind.config.js
file. You can add the themes to the daisyui
key in the configuration file. The following example shows how to add the light
, dark
, and cyberpunk
themes to your application.
// tailwind.config.js
module.exports = {
daisyui: {
themes: [
{
light: {
...require('daisyui/src/theming/themes').light,
primary: '#1d4ed8',
'primary-content': 'white',
secondary: '#f39325',
'secondary-content': 'white'
}
},
"dark",
"cyberpunk"
]
},
...
}
The full list of themes can be found at the daisyUI website.
2. Set the assign and the default daisyUI theme in your layout.
We fetch the theme from the assigns and set the data-theme
attribute on the html
tag. If no theme is set, we default to the light
theme.
# root.html.heex
<html data-theme={assigns[:theme] || "light"}>
...
</html>
If you just want to use a single theme, you can set the data-theme
attribute to the theme name. You can skip the next steps and are done with the theme setup.
# root.html.heex
<html data-theme="light">
...
</html>
3. Add Backpex.ThemeSelectorPlug
to the pipeline in the router
To add the saved theme to the assigns, you can add the Backpex.ThemeSelectorPlug
to the pipeline in your router. This plug will fetch the selected theme from the session and put it in the assigns.
# router.ex
pipeline :browser do
...
# Add this plug
plug Backpex.ThemeSelectorPlug
end
4. Add the theme selector component to the app shell
You can add a theme selector to your layout to allow users to change the theme. The following example shows how to add a theme selector to the admin.html.heex
layout. The list of themes should match the themes you added to your tailwind.config.js
file.
# admin.html.heex
<Backpex.HTML.Layout.app_shell fluid={@fluid?}>
<:topbar>
<Backpex.HTML.Layout.topbar_branding />
# Add this
<Backpex.HTML.Layout.theme_selector
socket={@socket}
themes={[
{"Light", "light"},
{"Dark", "dark"},
{"Cyberpunk", "cyberpunk"}
]}
/>
<Backpex.HTML.Layout.topbar_dropdown>
<:label>
<label tabindex="0" class="btn btn-square btn-ghost">
<.icon name="hero-user" class="h-8 w-8" />
</label>
</:label>
<li>
<.link navigate={~p"/"} class="flex justify-between text-red-600 hover:bg-gray-100">
<p>Logout</p>
<.icon name="hero-arrow-right-on-rectangle" class="h-5 w-5" />
</.link>
</li>
</Backpex.HTML.Layout.topbar_dropdown>
</:topbar>
<:sidebar>
<Backpex.HTML.Layout.sidebar_item current_url={@current_url} navigate={~p"/admin/posts"}>
<.icon name="hero-book-open" class="h-5 w-5" /> Posts
</Backpex.HTML.Layout.sidebar_item>
</:sidebar>
<Backpex.HTML.Layout.flash_messages flash={@flash} />
<%= @inner_content %>
</Backpex.HTML.Layout.app_shell>
5. Add a hook to persist the selected theme
To persist the selected theme, you can add a hook to your app.js
file. This hook will listen for the backpex:theme-change
event and store the selected theme in the session and in the local storage. The hook will also send a request to the server to store the selected theme in the session.
// app.js
// We want this to run as soon as possible to minimize
// flashes with the old theme in some situations
const storedTheme = window.localStorage.getItem('backpexTheme')
if (storedTheme != null) {
document.documentElement.setAttribute('data-theme', storedTheme)
}
const Hooks = {}
Hooks.BackpexThemeSelector = {
mounted () {
const form = document.querySelector('#backpex-theme-selector-form')
const storedTheme = window.localStorage.getItem('backpexTheme')
// Marking current theme as active
if (storedTheme != null) {
const activeThemeRadio = form.querySelector(
`input[name='theme-selector'][value='${storedTheme}']`
)
activeThemeRadio.checked = true
}
// Event listener that handles the theme changes and store
// the selected theme in the session and also in localStorage
window.addEventListener('backpex:theme-change', async (event) => {
const cookiePath = form.dataset.cookiePath
const selectedTheme = form.querySelector(
'input[name="theme-selector"]:checked'
)
if (selectedTheme) {
window.localStorage.setItem('backpexTheme', selectedTheme.value)
document.documentElement.setAttribute(
'data-theme',
selectedTheme.value
)
await fetch(cookiePath, {
body: `select_theme=${selectedTheme.value}`,
method: 'POST',
headers: {
'Content-type': 'application/x-www-form-urlencoded',
'x-csrf-token': csrfToken
}
})
}
})
}
}
let liveSocket = new LiveSocket("/live", Socket, {
hooks: Hooks,
dom: {
onBeforeElUpdated (from, to) {
if (from._x_dataStack) {
window.Alpine.clone(from, to);
}
},
},
params: { _csrf_token: csrfToken },
});
Remove @tailwindcss/forms
plugin
There is a conflict between the @tailwindcss/forms
plugin and daisyUI. You should remove the @tailwindcss/forms
plugin from your tailwind.config.js
to prevent styling issues.
// tailwind.config.js
module.exports = {
...
plugins: [
...
// remove this line
// require('@tailwindcss/forms'),
],
}
If your application depends on the @tailwindcss/forms
plugin, you can keep the plugin and change the strategy to 'class'
. This will prevent the plugin from conflicting with daisyUI. Note that you then have to add the form classes provided by the @tailwindcss/forms
plugin to your inputs manually.
Provide Tailwind Plugin for heroicons
Backpex uses the heroicons icon set. Backpex provides a Backpex.HTML.CoreComponents.icon/1
component, but you need to provide the icons and a Tailwind CSS plugin to generate the necessary styles to display them. If you generated your Phoenix project with the latest version of the mix phx.new
generator, you already have the dependency and plugin installed. If not, follow the steps below.
Track the heroicons GitHub repository
Track the heroicons GitHub repository with Mix:
def deps do
[
...
{:heroicons,
github: "tailwindlabs/heroicons",
tag: "v2.1.1",
sparse: "optimized",
app: false,
compile: false,
depth: 1}
]
end
This will add the heroicons repository as a dependency to your project. You can find the optimized SVG icons in the deps/heroicons
directory.
Add the Tailwind CSS plugin
Add the following plugin to your tailwind.config.js
to generate the necessary styles to display the icons.
// add fs and path to the top of the file
const fs = require('fs')
const path = require('path')
module.exports = {
...
plugins: [
...
// add this plugin
plugin(function ({ matchComponents, theme }) {
let iconsDir = path.join(__dirname, "../deps/heroicons/optimized")
let values = {}
let icons = [
["", "/24/outline"],
["-solid", "/24/solid"],
["-mini", "/20/solid"],
["-micro", "/16/solid"]
]
icons.forEach(([suffix, dir]) => {
fs.readdirSync(path.join(iconsDir, dir)).forEach(file => {
let name = path.basename(file, ".svg") + suffix
values[name] = { name, fullPath: path.join(iconsDir, dir, file) }
})
})
matchComponents({
"hero": ({ name, fullPath }) => {
let content = fs.readFileSync(fullPath).toString().replace(/\r?\n|\r/g, "")
let size = theme("spacing.6")
if (name.endsWith("-mini")) {
size = theme("spacing.5")
} else if (name.endsWith("-micro")) {
size = theme("spacing.4")
}
return {
[`--hero-${name}`]: `url('data:image/svg+xml;utf8,${content}')`,
"-webkit-mask": `var(--hero-${name})`,
"mask": `var(--hero-${name})`,
"mask-repeat": "no-repeat",
"background-color": "currentColor",
"vertical-align": "middle",
"display": "inline-block",
"width": size,
"height": size
}
}
}, { values })
})
],
}
This plugin will generate the necessary styles to display the heroicons in your application. You can now use the Backpex.HTML.CoreComponents.icon/1
component to render the icons in your application.
For example, to render the user
icon, you can use the following code:
<Backpex.HTML.CoreComponents.icon name="hero-user" class="h-5 w-5" />