From b797881a085050b738cb1538df5795a4d68b13b5 Mon Sep 17 00:00:00 2001 From: Florian Arens <60519307+Flo0807@users.noreply.github.com> Date: Thu, 6 Jun 2024 12:00:51 +0200 Subject: [PATCH 01/28] Update docs --- README.md | 44 ++++++++-------- guides/about/contribute-to-backpex.md | 1 + guides/about/what-is-backpex.md | 1 + guides/about/why-we-built-backpex.md | 1 + guides/actions/item-actions.md | 1 + guides/actions/resource-actions.md | 1 + guides/authorization/field-authorization.md | 1 + .../live-resource-authorization.md | 1 + guides/fields/alignment.md | 1 + guides/fields/custom-field.md | 1 + guides/fields/defaults.md | 1 + guides/fields/index-edit.md | 1 + guides/fields/readonly.md | 1 + guides/fields/visibility.md | 1 + guides/fields/what-is-a-field.md | 1 + guides/filter/boolean-filter.md | 1 + guides/filter/how-to-add-a-filter.md | 1 + guides/filter/multi-select-filter.md | 1 + guides/filter/range-filter.md | 1 + guides/filter/select-filter.md | 1 + .../installation.md | 0 guides/get_started/prerequisites.md | 1 + ...additional-classes-for-index-table-rows.md | 1 + guides/live_resource/hooks.md | 1 + guides/live_resource/item-query.md | 1 + guides/live_resource/navigation.md | 1 + guides/live_resource/ordering.md | 1 + guides/live_resource/panels.md | 1 + guides/live_resource/templates.md | 1 + .../live_resource/what-is-a-live-resource.md | 1 + guides/searching/basic-search.md | 1 + .../full-text-search.md} | 0 .../translations.md | 17 +++---- mix.exs | 50 ++++++++++++++++--- 34 files changed, 101 insertions(+), 39 deletions(-) create mode 100644 guides/about/contribute-to-backpex.md create mode 100644 guides/about/what-is-backpex.md create mode 100644 guides/about/why-we-built-backpex.md create mode 100644 guides/actions/item-actions.md create mode 100644 guides/actions/resource-actions.md create mode 100644 guides/authorization/field-authorization.md create mode 100644 guides/authorization/live-resource-authorization.md create mode 100644 guides/fields/alignment.md create mode 100644 guides/fields/custom-field.md create mode 100644 guides/fields/defaults.md create mode 100644 guides/fields/index-edit.md create mode 100644 guides/fields/readonly.md create mode 100644 guides/fields/visibility.md create mode 100644 guides/fields/what-is-a-field.md create mode 100644 guides/filter/boolean-filter.md create mode 100644 guides/filter/how-to-add-a-filter.md create mode 100644 guides/filter/multi-select-filter.md create mode 100644 guides/filter/range-filter.md create mode 100644 guides/filter/select-filter.md rename guides/{introduction => get_started}/installation.md (100%) create mode 100644 guides/get_started/prerequisites.md create mode 100644 guides/live_resource/additional-classes-for-index-table-rows.md create mode 100644 guides/live_resource/hooks.md create mode 100644 guides/live_resource/item-query.md create mode 100644 guides/live_resource/navigation.md create mode 100644 guides/live_resource/ordering.md create mode 100644 guides/live_resource/panels.md create mode 100644 guides/live_resource/templates.md create mode 100644 guides/live_resource/what-is-a-live-resource.md create mode 100644 guides/searching/basic-search.md rename guides/{advanced/full_text_search.md => searching/full-text-search.md} (100%) rename guides/{introduction => translations}/translations.md (83%) diff --git a/README.md b/README.md index 2ee8b8e8..5ef9aac4 100644 --- a/README.md +++ b/README.md @@ -1,38 +1,36 @@ -# Backpex +![Logo](https://github.com/naymspace/backpex/blob/develop/priv/static/images/logo.svg) [![CI](https://github.com/naymspace/backpex/actions/workflows/ci.yml/badge.svg)](https://github.com/naymspace/backpex/actions/workflows/ci.yml) +[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/naymspace/backpex/blob/develop/LICENSE.md) [![Hex](https://img.shields.io/hexpm/v/backpex.svg)](https://hex.pm/packages/backpex) [![Hex Docs](https://img.shields.io/badge/hex-docs-green)](https://hexdocs.pm/backpex) -Backpex is a simple admin dashboard that makes it easy to manage existing resources in your application. - -See our comprehensive [docs](https://hexdocs.pm/backpex) for more information. - -## Screenshot ([Live Demo](https://backpex.live/admin/users)) - -![Backpex Screenshot](priv/static/images/screenshot.png) +# Backpex -## Development +Welcome! Backpex is a highly customizable administration panel for Phoenix LiveView applications. Quickly create beautiful CRUD views for your existing data using configurable *LiveResources*. Backpex integrates seamlessly with your existing Phoenix application and provides a simple way to manage your resources. It is highly customizable and can be extended with your own layouts, views, field types, filters and more. -### Requirements +Visit our Live Demo at [backpex.live](https://backpex.live/admin/users)! -- [Docker](https://www.docker.com/) +![Backpex Screenshot](https://github.com/naymspace/backpex/blob/develop/priv/static/images/screenshot.png) -### Recommended Extensions +## Learn More -- [Editorconfig](http://editorconfig.org) -- [JavaScript Standard Style](https://github.com/standard/standard#are-there-text-editor-plugins) +- [Installation](guides/get_started/installation.md) +- [What is Backpex?](guides/about_backpex/what-is-backpex.md) +- [Why we built Backpex?](guides/about_backpex/why-we-built-backpex.md) +- [Contribute to Backpex](guides/about_backpex/contribute-to-backpex.md) -### Setup +## Key Features -- Clone the repository. -- In `demo` directory run `cp .env.example .env` and set values accordingly. - - Generate `SECRET_KEY_BASE` via `mix phx.gen.secret`. - - Generate `LIVE_VIEW_SIGNING_SALT` via `mix phx.gen.secret 32`. -- Run `docker compose up` (`yarn watch` is triggered automatically). +- **LiveResources**: Quickly create LiveResource modules for your database tables with fully customizable CRUD views. Bring your own layout or use our components. +- **Search and Filters**: Define searchable fields on your resources and add custom filters. Get instant results with the power of Phoenix LiveView. +- **Resource Actions**: Add your globally available custom actions (like user invitation or exports) with additional form fields to your LiveResources. +- **Authorization**: Handle authorization for all your CRUD and custom actions via simple pattern matching. Optionally integrate your own authorization library. +- **Field Types**: Many field types (e.g. Text, Number, Date, Upload) are supported out of the box. Easily create your own field type modules with custom logic. +- **Associations**: Handle HasOne, BelongsTo and HasMany(Through) associations with minimal configuration. Customize available options and rendered columns. +- **Metrics**: Easily add value metrics (like sums or averages) to your resources for a quick glance at your date. More metric types are in the making. -Now you can visit [`localhost:4000`](http://localhost:4000) from your browser. +## Installation -## License +See our comprehensive [installation guide](guides/get_started/installation.md) for more information on how to install and configure Backpex in your Phoenix application. -Backpex source code is licensed under the [MIT License](LICENSE.md). diff --git a/guides/about/contribute-to-backpex.md b/guides/about/contribute-to-backpex.md new file mode 100644 index 00000000..31bace49 --- /dev/null +++ b/guides/about/contribute-to-backpex.md @@ -0,0 +1 @@ +# Contribute to Backpex \ No newline at end of file diff --git a/guides/about/what-is-backpex.md b/guides/about/what-is-backpex.md new file mode 100644 index 00000000..53d068e6 --- /dev/null +++ b/guides/about/what-is-backpex.md @@ -0,0 +1 @@ +# What is Backpex? diff --git a/guides/about/why-we-built-backpex.md b/guides/about/why-we-built-backpex.md new file mode 100644 index 00000000..6fa3c46f --- /dev/null +++ b/guides/about/why-we-built-backpex.md @@ -0,0 +1 @@ +# Why we built Backpex? \ No newline at end of file diff --git a/guides/actions/item-actions.md b/guides/actions/item-actions.md new file mode 100644 index 00000000..9c749cfa --- /dev/null +++ b/guides/actions/item-actions.md @@ -0,0 +1 @@ +# Item Actions \ No newline at end of file diff --git a/guides/actions/resource-actions.md b/guides/actions/resource-actions.md new file mode 100644 index 00000000..be1a13a1 --- /dev/null +++ b/guides/actions/resource-actions.md @@ -0,0 +1 @@ +# Resource Actions \ No newline at end of file diff --git a/guides/authorization/field-authorization.md b/guides/authorization/field-authorization.md new file mode 100644 index 00000000..9c0c7a41 --- /dev/null +++ b/guides/authorization/field-authorization.md @@ -0,0 +1 @@ +# Field Authorization \ No newline at end of file diff --git a/guides/authorization/live-resource-authorization.md b/guides/authorization/live-resource-authorization.md new file mode 100644 index 00000000..43486111 --- /dev/null +++ b/guides/authorization/live-resource-authorization.md @@ -0,0 +1 @@ +# LiveResource Authorization \ No newline at end of file diff --git a/guides/fields/alignment.md b/guides/fields/alignment.md new file mode 100644 index 00000000..120efa3a --- /dev/null +++ b/guides/fields/alignment.md @@ -0,0 +1 @@ +# Alignment \ No newline at end of file diff --git a/guides/fields/custom-field.md b/guides/fields/custom-field.md new file mode 100644 index 00000000..04f24d32 --- /dev/null +++ b/guides/fields/custom-field.md @@ -0,0 +1 @@ +# Custom Field \ No newline at end of file diff --git a/guides/fields/defaults.md b/guides/fields/defaults.md new file mode 100644 index 00000000..6b175aeb --- /dev/null +++ b/guides/fields/defaults.md @@ -0,0 +1 @@ +# Defaults \ No newline at end of file diff --git a/guides/fields/index-edit.md b/guides/fields/index-edit.md new file mode 100644 index 00000000..5a714041 --- /dev/null +++ b/guides/fields/index-edit.md @@ -0,0 +1 @@ +# Index Edit \ No newline at end of file diff --git a/guides/fields/readonly.md b/guides/fields/readonly.md new file mode 100644 index 00000000..1f4fb3b0 --- /dev/null +++ b/guides/fields/readonly.md @@ -0,0 +1 @@ +# Readonly \ No newline at end of file diff --git a/guides/fields/visibility.md b/guides/fields/visibility.md new file mode 100644 index 00000000..cd0d6a1a --- /dev/null +++ b/guides/fields/visibility.md @@ -0,0 +1 @@ +# Visibility \ No newline at end of file diff --git a/guides/fields/what-is-a-field.md b/guides/fields/what-is-a-field.md new file mode 100644 index 00000000..301f10ea --- /dev/null +++ b/guides/fields/what-is-a-field.md @@ -0,0 +1 @@ +# What is a Field? \ No newline at end of file diff --git a/guides/filter/boolean-filter.md b/guides/filter/boolean-filter.md new file mode 100644 index 00000000..02caf67c --- /dev/null +++ b/guides/filter/boolean-filter.md @@ -0,0 +1 @@ +# Boolean Filter \ No newline at end of file diff --git a/guides/filter/how-to-add-a-filter.md b/guides/filter/how-to-add-a-filter.md new file mode 100644 index 00000000..660a52bb --- /dev/null +++ b/guides/filter/how-to-add-a-filter.md @@ -0,0 +1 @@ +# How to add a Filter? \ No newline at end of file diff --git a/guides/filter/multi-select-filter.md b/guides/filter/multi-select-filter.md new file mode 100644 index 00000000..9bf39dbd --- /dev/null +++ b/guides/filter/multi-select-filter.md @@ -0,0 +1 @@ +# Multi Select Filter \ No newline at end of file diff --git a/guides/filter/range-filter.md b/guides/filter/range-filter.md new file mode 100644 index 00000000..8cf856dd --- /dev/null +++ b/guides/filter/range-filter.md @@ -0,0 +1 @@ +# Range Filter \ No newline at end of file diff --git a/guides/filter/select-filter.md b/guides/filter/select-filter.md new file mode 100644 index 00000000..7450c7ad --- /dev/null +++ b/guides/filter/select-filter.md @@ -0,0 +1 @@ +# Select Filter \ No newline at end of file diff --git a/guides/introduction/installation.md b/guides/get_started/installation.md similarity index 100% rename from guides/introduction/installation.md rename to guides/get_started/installation.md diff --git a/guides/get_started/prerequisites.md b/guides/get_started/prerequisites.md new file mode 100644 index 00000000..ef222681 --- /dev/null +++ b/guides/get_started/prerequisites.md @@ -0,0 +1 @@ +# Prerequisites \ No newline at end of file diff --git a/guides/live_resource/additional-classes-for-index-table-rows.md b/guides/live_resource/additional-classes-for-index-table-rows.md new file mode 100644 index 00000000..5e676c54 --- /dev/null +++ b/guides/live_resource/additional-classes-for-index-table-rows.md @@ -0,0 +1 @@ +# Additional classes for index table rows \ No newline at end of file diff --git a/guides/live_resource/hooks.md b/guides/live_resource/hooks.md new file mode 100644 index 00000000..df33ddee --- /dev/null +++ b/guides/live_resource/hooks.md @@ -0,0 +1 @@ +# Hooks \ No newline at end of file diff --git a/guides/live_resource/item-query.md b/guides/live_resource/item-query.md new file mode 100644 index 00000000..0a081a93 --- /dev/null +++ b/guides/live_resource/item-query.md @@ -0,0 +1 @@ +# Item Query \ No newline at end of file diff --git a/guides/live_resource/navigation.md b/guides/live_resource/navigation.md new file mode 100644 index 00000000..69c0d5e8 --- /dev/null +++ b/guides/live_resource/navigation.md @@ -0,0 +1 @@ +# Navigation \ No newline at end of file diff --git a/guides/live_resource/ordering.md b/guides/live_resource/ordering.md new file mode 100644 index 00000000..49aa9ab1 --- /dev/null +++ b/guides/live_resource/ordering.md @@ -0,0 +1 @@ +# Ordering \ No newline at end of file diff --git a/guides/live_resource/panels.md b/guides/live_resource/panels.md new file mode 100644 index 00000000..76e4e9b2 --- /dev/null +++ b/guides/live_resource/panels.md @@ -0,0 +1 @@ +# Panels \ No newline at end of file diff --git a/guides/live_resource/templates.md b/guides/live_resource/templates.md new file mode 100644 index 00000000..fd5397ed --- /dev/null +++ b/guides/live_resource/templates.md @@ -0,0 +1 @@ +# Templates \ No newline at end of file diff --git a/guides/live_resource/what-is-a-live-resource.md b/guides/live_resource/what-is-a-live-resource.md new file mode 100644 index 00000000..36ce6a37 --- /dev/null +++ b/guides/live_resource/what-is-a-live-resource.md @@ -0,0 +1 @@ +# What is a LiveResource? \ No newline at end of file diff --git a/guides/searching/basic-search.md b/guides/searching/basic-search.md new file mode 100644 index 00000000..6aaea9fe --- /dev/null +++ b/guides/searching/basic-search.md @@ -0,0 +1 @@ +# Search \ No newline at end of file diff --git a/guides/advanced/full_text_search.md b/guides/searching/full-text-search.md similarity index 100% rename from guides/advanced/full_text_search.md rename to guides/searching/full-text-search.md diff --git a/guides/introduction/translations.md b/guides/translations/translations.md similarity index 83% rename from guides/introduction/translations.md rename to guides/translations/translations.md index 19f6cca4..16e141a3 100644 --- a/guides/introduction/translations.md +++ b/guides/translations/translations.md @@ -1,6 +1,6 @@ # Translations -You may configure translator functions in your application config: +You are able to translate all strings used by Backpex. To do so, you need to configure translator functions in your application config: ```elixir config :backpex, :translator_function, {MyAppWeb.Helpers, :translate_backpex} @@ -8,16 +8,13 @@ config :backpex, :translator_function, {MyAppWeb.Helpers, :translate_backpex} config :backpex, :error_translator_function, {MyAppWeb.ErrorHelpers, :translate_error} ``` -The first one is being used to translate general strings. The second one is being used to translate -(changeset) errors. +The first one is being used to translate general strings. The second one is being used to translate (changeset) errors. ## Using Gettext -If you want to use Gettext, the translator functions should look like this: +We recommend using Gettext for translations. If you want to use it, the translator functions should look like this: ```elixir -# MyAppWeb.Helpers - def translate_backpex({msg, opts}) do if count = opts[:count] do Gettext.dngettext(MyAppWeb.Gettext, "backpex", msg, msg, count, opts) @@ -26,8 +23,6 @@ def translate_backpex({msg, opts}) do end end -# MyAppWeb.ErrorHelpers - def translate_error({msg, opts}) do if count = opts[:count] do Gettext.dngettext(DemoWeb.Gettext, "errors", msg, msg, count, opts) @@ -37,9 +32,11 @@ def translate_error({msg, opts}) do end ``` -Store this example template as `priv/gettext/backpex.pot`. It contains all translations used by Backpex. +You can place the functions in a module of your choice. In this example, we use `MyAppWeb.Helpers` and `MyAppWeb.ErrorHelpers`. -``` +In addition, you need to create a Gettext template file in your application. You may use the following template. It contains all translations used by Backpex. + +```po ## This file is a PO Template file. msgid "" msgstr "" diff --git a/mix.exs b/mix.exs index 39ef2dbe..09265c84 100644 --- a/mix.exs +++ b/mix.exs @@ -68,7 +68,7 @@ defmodule Backpex.MixProject do defp docs() do [ - main: "Backpex.LiveResource", + main: "readme", logo: "priv/static/images/logo.svg", extras: extras(), extra_section: "GUIDES", @@ -84,17 +84,55 @@ defmodule Backpex.MixProject do defp extras do [ - "guides/introduction/installation.md", - "guides/introduction/translations.md", - "guides/advanced/full_text_search.md", + {"README.md", title: "Introduction"}, + "guides/about/what-is-backpex.md", + "guides/about/why-we-built-backpex.md", + "guides/about/contribute-to-backpex.md", + "guides/get_started/prerequisites.md", + "guides/get_started/installation.md", + "guides/live_resource/what-is-a-live-resource.md", + "guides/live_resource/templates.md", + "guides/live_resource/item-query.md", + "guides/live_resource/ordering.md", + "guides/live_resource/hooks.md", + "guides/live_resource/navigation.md", + "guides/live_resource/panels.md", + "guides/live_resource/additional-classes-for-index-table-rows.md", + "guides/fields/what-is-a-field.md", + "guides/fields/custom-field.md", + "guides/fields/alignment.md", + "guides/fields/visibility.md", + "guides/fields/defaults.md", + "guides/fields/readonly.md", + "guides/fields/index-edit.md", + "guides/filter/how-to-add-a-filter.md", + "guides/filter/boolean-filter.md", + "guides/filter/select-filter.md", + "guides/filter/multi-select-filter.md", + "guides/filter/range-filter.md", + "guides/actions/item-actions.md", + "guides/actions/resource-actions.md", + "guides/authorization/live-resource-authorization.md", + "guides/authorization/field-authorization.md", + "guides/searching/basic-search.md", + "guides/searching/full-text-search.md", + "guides/translations/translations.md", "guides/upgrading/v0.2.md" ] end defp groups_for_extras do [ - Introduction: ~r/guides\/introduction\/.?/, - Advanced: ~r/guides\/advanced\/.?/, + Introduction: ~r/README/, + About: ~r/guides\/about\/.?/, + "Get Started": ~r/guides\/get_started\/.?/, + LiveResource: ~r/guides\/live_resource\/.?/, + Fields: ~r/guides\/fields\/.?/, + Filter: ~r/guides\/filter\/.?/, + Actions: ~r/guides\/actions\/.?/, + Authorization: ~r/guides\/authorization\/.?/, + Searching: ~r/guides\/searching\/.?/, + Translations: ~r/guides\/translations\/.?/, "Upgrade Guides": ~r{guides/upgrading/.*} ] end From 6fcf606d219242286bdf58920de8dd022ae4fc3f Mon Sep 17 00:00:00 2001 From: Florian Arens <60519307+Flo0807@users.noreply.github.com> Date: Thu, 6 Jun 2024 12:02:17 +0200 Subject: [PATCH 02/28] Make logo smaller --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5ef9aac4..8a64a64d 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -![Logo](https://github.com/naymspace/backpex/blob/develop/priv/static/images/logo.svg) + [![CI](https://github.com/naymspace/backpex/actions/workflows/ci.yml/badge.svg)](https://github.com/naymspace/backpex/actions/workflows/ci.yml) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/naymspace/backpex/blob/develop/LICENSE.md) From 9cb603132c78b9b6e6e831b2b575515f4868f422 Mon Sep 17 00:00:00 2001 From: Florian Arens <60519307+Flo0807@users.noreply.github.com> Date: Thu, 6 Jun 2024 12:04:02 +0200 Subject: [PATCH 03/28] Update live demo link --- README.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 8a64a64d..5b12fcab 100644 --- a/README.md +++ b/README.md @@ -9,10 +9,13 @@ Welcome! Backpex is a highly customizable administration panel for Phoenix LiveView applications. Quickly create beautiful CRUD views for your existing data using configurable *LiveResources*. Backpex integrates seamlessly with your existing Phoenix application and provides a simple way to manage your resources. It is highly customizable and can be extended with your own layouts, views, field types, filters and more. -Visit our Live Demo at [backpex.live](https://backpex.live/admin/users)! - ![Backpex Screenshot](https://github.com/naymspace/backpex/blob/develop/priv/static/images/screenshot.png) +
+ Visit our Live Demo → +
+ + ## Learn More - [Installation](guides/get_started/installation.md) From 63543454110dd243f48f0fd43291f52b567ecffa Mon Sep 17 00:00:00 2001 From: Florian Arens <60519307+Flo0807@users.noreply.github.com> Date: Thu, 6 Jun 2024 14:08:11 +0200 Subject: [PATCH 04/28] Update guides --- README.md | 2 - guides/about/contribute-to-backpex.md | 51 +++++++- guides/about/what-is-backpex.md | 8 ++ guides/about/why-we-built-backpex.md | 8 +- guides/get_started/prerequisites.md | 26 +++- ...additional-classes-for-index-table-rows.md | 21 ++- guides/live_resource/hooks.md | 24 +++- guides/live_resource/item-query.md | 25 +++- guides/live_resource/navigation.md | 20 ++- guides/live_resource/ordering.md | 18 ++- guides/live_resource/panels.md | 44 ++++++- guides/live_resource/templates.md | 18 ++- guides/searching/basic-search.md | 39 +++++- guides/searching/full-text-search.md | 26 ++-- guides/translations/translations.md | 8 +- lib/backpex/live_resource.ex | 122 ------------------ mix.exs | 2 +- 17 files changed, 309 insertions(+), 153 deletions(-) diff --git a/README.md b/README.md index 5b12fcab..2fa94232 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,6 @@ Welcome! Backpex is a highly customizable administration panel for Phoenix LiveV Visit our Live Demo → - ## Learn More - [Installation](guides/get_started/installation.md) @@ -36,4 +35,3 @@ Welcome! Backpex is a highly customizable administration panel for Phoenix LiveV ## Installation See our comprehensive [installation guide](guides/get_started/installation.md) for more information on how to install and configure Backpex in your Phoenix application. - diff --git a/guides/about/contribute-to-backpex.md b/guides/about/contribute-to-backpex.md index 31bace49..93aa506d 100644 --- a/guides/about/contribute-to-backpex.md +++ b/guides/about/contribute-to-backpex.md @@ -1 +1,50 @@ -# Contribute to Backpex \ No newline at end of file +# Contribute to Backpex + +We are excited to have you contribute to Backpex! We are always looking for ways to improve the project and welcome any contributions in the form of bug reports, feature requests, documentation improvements, code contributions, and more. + +## What can I contribute? + +We are in the process of writing a roadmap to outline the features we plan to implement in the future. In the meantime, you can contribute to Backpex by: + +- Reporting bugs +- Requesting new features +- Improving the documentation +- Improving the demo application +- Fixing bugs + +## Fork the repository + +In order to contribute to Backpex, you need to fork the repository. You can do this by clicking the "Fork" button in the top right corner of the repository page at [https://github.com/naymspace/backpex](https://github.com/naymspace/backpex). + +## Clone the repository + +After forking the repository, you need to clone it to your local machine. You can do this by running the `git clone` command along with the URL of your forked repository. + +## Setting up your development environment + +You first need to create a `.env` file in the `demo` directory of the project with the following content: + +```bash +SECRET_KEY_BASE= +LIVE_VIEW_SIGNING_SALT= +``` + +For development purposes you can copy the values from the `demo/.env.example` file. + +You can then start the development environment by running the following command in the root directory of the project: + +```bash +docker compose up +``` + +Backpex comes with a demo application that you can use to test the features of the project. The command will start a PostgreSQL database and the demo application on [http://localhost:4000](http://localhost:4000). + +To insert some demo data into the database, you can run the following command: + +```bash +docker compose exec app mix ecto.seed +``` + +## Making changes + +After setting up your development environment, you can start making changes to the project. We recommend creating a new branch for your changes. After submitting your changes to your forked repository, you can create a pull request to the `develop` branch of the main repository. diff --git a/guides/about/what-is-backpex.md b/guides/about/what-is-backpex.md index 53d068e6..81c79a6a 100644 --- a/guides/about/what-is-backpex.md +++ b/guides/about/what-is-backpex.md @@ -1 +1,9 @@ # What is Backpex? + +Backpex is a highly customizable administration panel for Phoenix LiveView applications. It allows you to quickly create CRUD views of your existing data using configurable *LiveResources*. Backpex integrates seamlessly with your existing Phoenix LiveView application and provides an easy way to manage your resources. It is highly customizable and can be extended with your own layouts, views, field types, filters and more. + +Backpex is built on top of Phoenix LiveView and provides a rich set of features to manage your resources. With Backpex, you can set up an administration panel for your application in minutes, not hours. + +Whether you want to quickly scaffold CRUD views for your existing data or build a full-fledged administration panel, Backpex has you covered. + +TODO: add screenshots of key features here diff --git a/guides/about/why-we-built-backpex.md b/guides/about/why-we-built-backpex.md index 6fa3c46f..32912629 100644 --- a/guides/about/why-we-built-backpex.md +++ b/guides/about/why-we-built-backpex.md @@ -1 +1,7 @@ -# Why we built Backpex? \ No newline at end of file +# Why we built Backpex? + +After building several Phoenix applications, we realized that we were repeating ourselves when it came to building administration panels. We were writing the same CRUD views, search and filter functionality over and over again. We wanted a tool that would allow us to quickly scaffold these views and focus on building the core functionality of our applications. + +The tool we wanted had to be able to serve as a simple backend administration panel in one project, while being the core of the application in another. + +We looked at existing solutions, but found that none of them offered the flexibility and customization we were looking for. We decided to develop Backpex to solve this problem and provide a highly customizable administration panel for Phoenix LiveView applications. \ No newline at end of file diff --git a/guides/get_started/prerequisites.md b/guides/get_started/prerequisites.md index ef222681..486c3eb1 100644 --- a/guides/get_started/prerequisites.md +++ b/guides/get_started/prerequisites.md @@ -1 +1,25 @@ -# Prerequisites \ No newline at end of file +# 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. + +## 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](https://tailwindcss.com/docs/installation). 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](https://daisyui.com/docs/install/). + +> #### Important {: .info} +> +> Backpex currently only supports daisyUI light mode. Help us to support dark mode by contributing to the project. + +## Ecto + +Backpex currently depends on Ecto as the database layer. Make sure you have a running Ecto repository in your application. + +If you meet all these prerequisites, you are ready to install and configure Backpex in your Phoenix application. See our [installation guide](guides/get_started/installation.md) for more information on how to install and configure Backpex. \ No newline at end of file diff --git a/guides/live_resource/additional-classes-for-index-table-rows.md b/guides/live_resource/additional-classes-for-index-table-rows.md index 5e676c54..128a59dd 100644 --- a/guides/live_resource/additional-classes-for-index-table-rows.md +++ b/guides/live_resource/additional-classes-for-index-table-rows.md @@ -1 +1,20 @@ -# Additional classes for index table rows \ No newline at end of file +# Additional classes for index table rows + +You can add additional classes to table rows on the index view. This allows you, for example, to color the rows. + +## Configuration + +To add additional classes to table rows on the index view, you need to implement the [index_row_class/4](Backpex.LiveResource.html#c:index_row_class/4) callback in your resource configuration file. + +```elixir +# in your resource configuration file + +@impl Backpex.LiveResource +def index_row_class(assigns, item, selected, index), do: "bg-yellow-100" +``` + +The example above will add the `bg-yellow-100` class to all table rows on the index view. + +> #### Info {: .warning} +> +> Note that we call the function twice. Once for the row on the `tr` element and a second time for the item action overlay, because in most cases the overlay should have the same style applied. diff --git a/guides/live_resource/hooks.md b/guides/live_resource/hooks.md index df33ddee..dbb8525f 100644 --- a/guides/live_resource/hooks.md +++ b/guides/live_resource/hooks.md @@ -1 +1,23 @@ -# Hooks \ No newline at end of file +# Hooks + +You may define hooks that are called before their respective action. Those hooks are `on_item_created`, `on_item_updated` and `on_item_deleted`. + +> #### Info {: .info} +> +> Note that the hooks are called after the changes have been persisted to the database. + +## Configuration + +To add a hook to a resource, you need to implement the [on_item_created/2](Backpex.LiveResource.html#c:on_item_created/2), [on_item_updated/2](Backpex.LiveResource.html#c:on_item_updated/2) or [on_item_deleted/2](Backpex.LiveResource.html#c:on_item_deleted/2) callback in your resource configuration file. + +```elixir +# in your resource configuration file + +@impl Backpex.LiveResource +def on_item_created(socket, item) do + # do something + socket +end +``` + +The example above will call the `on_item_created` hook after an item has been created. diff --git a/guides/live_resource/item-query.md b/guides/live_resource/item-query.md index 0a081a93..d729ff14 100644 --- a/guides/live_resource/item-query.md +++ b/guides/live_resource/item-query.md @@ -1 +1,24 @@ -# Item Query \ No newline at end of file +# Item Query + +It is possible to manipulate the query when fetching resources for `index`, `show` and `edit` view. + +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 it returns the incoming query. + +The `item_query` function makes it easy to add custom query expressions. + +## Configuration + +To add a custom query to a resource, you need to implement the [item_query/3](Backpex.LiveResource.html#c:item_query/3) callback in your resource configuration file: + +```elixir +# in your resource configuration file + +@impl Backpex.LiveResource +def item_query(query, :index, _assigns) do +query +|> where([post], post.published) +end +``` + +The example above will filter all posts by a published boolean on `index` view. 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. \ No newline at end of file diff --git a/guides/live_resource/navigation.md b/guides/live_resource/navigation.md index 69c0d5e8..9e6b0dd1 100644 --- a/guides/live_resource/navigation.md +++ b/guides/live_resource/navigation.md @@ -1 +1,19 @@ -# Navigation \ No newline at end of file +# Navigation + + +By default Backpex redirects to the index page after creating or updating an item. You can customize this behavior. + +## Configuration + +To define a custom navigation path, you need to implement the [return_to/4](Backpex.LiveResource.html#c:return_to/4) callback in your resource configuration file: + +```elixir +# in your resource configuration file + +@impl Backpex.LiveResource +def return_to(socket, assigns, live_action, item) do + ~p"/home" +end +``` + +The example above will redirect to the `/home` path after saving an item. diff --git a/guides/live_resource/ordering.md b/guides/live_resource/ordering.md index 49aa9ab1..df31b368 100644 --- a/guides/live_resource/ordering.md +++ b/guides/live_resource/ordering.md @@ -1 +1,17 @@ -# Ordering \ No newline at end of file +# Ordering + +You can configure the ordering of the resource index page. By default, the resources are ordered by the `id` field in ascending order. + +## Configuration + +To configure the ordering of the resource index page, you need to provide an `init_order` option in the resource configuration file: + +```elixir +# in your resource configuration file + +use Backpex.LiveResource, + ..., + init_order: %{by: :inserted_at, direction: :desc} +``` + +The example above will order the resources by the `inserted_at` field in descending order. diff --git a/guides/live_resource/panels.md b/guides/live_resource/panels.md index 76e4e9b2..d0d16d35 100644 --- a/guides/live_resource/panels.md +++ b/guides/live_resource/panels.md @@ -1 +1,43 @@ -# Panels \ No newline at end of file +# Panels + +You can define panels to group certain fields together. Panels are displayed in the provided order. + +## Configuration + +To add panels to a resource, you need to implement the [panels/0](Backpex.LiveResource.html#c:panels/0) callback in your resource configuration file. It has to return a keyword list with an identifier and label for each panel. + +```elixir + +# in your resource configuration file +@impl Backpex.LiveResource +def panels do + [ + contact: "Contact" + ] +end +``` + +The example above will define a panel with the identifier `contact` and the label `Contact`. + +## Usage + +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. + +```elixir +# in your fields list +@impl Backpex.LiveResource +def fields do + [ + %{ + ..., + panel: :contact + } + ] +end +``` + +The example above will move the field into the `contact` panel. + +> #### Info {: .info} +> +> Note that a panel is not displayed when there are no fields in it. diff --git a/guides/live_resource/templates.md b/guides/live_resource/templates.md index fd5397ed..7295a8f1 100644 --- a/guides/live_resource/templates.md +++ b/guides/live_resource/templates.md @@ -1 +1,17 @@ -# Templates \ No newline at end of file +# Templates + +You can customize certain template parts of Backpex. While you can only use our app shell layout, you can 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](Backpex.LiveResource.html#c:render_resource_slot/3) for supported positions. + +## Configuration + +To add a custom template to a resource, you need to implement the [render_resource_slot/3](Backpex.LiveResource.html#c:render_resource_slot/3) callback in your resource configuration file. + +```elixir +# in your resource configuration file +@impl Backpex.LiveResource +def render_resource_slot(assigns, :index, :before_main), do: ~H"Hello World!" +``` + +The example above will render the string "Hello World!" before the main content of the index view. diff --git a/guides/searching/basic-search.md b/guides/searching/basic-search.md index 6aaea9fe..dcf4c8ab 100644 --- a/guides/searching/basic-search.md +++ b/guides/searching/basic-search.md @@ -1 +1,38 @@ -# Search \ No newline at end of file +# Search + +Backpex provides a simple search feature that allows you to search for records in your resources. You can search for records based on the values of the fields in your resources. + +> #### Info {: .info} +> +> Note that fields are searched using a case-insensitive `ilike` query. + +## Configuration + +To enable searching, you need to flag the fields you want to search on as `searchable`. + +```elixir +# in your resource configuration file +@impl Backpex.LiveResource +def fields do +[ + %{ + ..., + searchable: true + } +] +end +``` + +A search input will appear automatically on the resource index view. + +## Custom Placeholder + +You can provide a custom placeholder for the search input by implementing the `search_placeholder/0` callback in your resource configuration file. + +```elixir +# 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](full-text-search.md)). \ No newline at end of file diff --git a/guides/searching/full-text-search.md b/guides/searching/full-text-search.md index a85e8523..6bcd8b2d 100644 --- a/guides/searching/full-text-search.md +++ b/guides/searching/full-text-search.md @@ -1,6 +1,6 @@ # Full-Text Search -Backpex allows you to perform full-text searches on resources. It uses the built-in PostgreSQL full-text search functionality. +Backpex allows you to perform full-text searches on resources. It uses the built-in [PostgreSQL full-text search functionality](https://www.postgresql.org/docs/current/textsearch.html). ## Create a Generated Column @@ -20,18 +20,13 @@ ALTER TABLE film_reviews """) ``` -You can also concat multiple tsvectors in the generated column. This is useful if the table contains data in -different languages. We recommend that you specify the language when using the `to_tsvector` function. Otherwise -the default language will be used. +You can also concat multiple tsvectors in the generated column. This is useful if the table contains data in different languages. We recommend that you specify the language when using the `to_tsvector` function. Otherwise the default language will be used. ## Create an Index -To increase the speed of full-text searches, especially for resources with large amounts of data, you should create -an index on the generated column created in the previous step. +To increase the speed of full-text searches, especially for resources with large amounts of data, you should create an index on the generated column created in the previous step. -We strongly recommend that you use a GIN index, as it makes full-text searches really fast. -The disadvantage is that a GIN index takes up a lot of disk space, so if you are limited in disk space, -feel free to use a GiST index instead. +We strongly recommend that you use a GIN index, as it makes full-text searches really fast. The disadvantage is that a GIN index takes up a lot of disk space, so if you are limited in disk space, feel free to use a GiST index instead. ```elixir # in the database up migration @@ -40,10 +35,6 @@ execute(""" CREATE INDEX film_reviews_search_idx ON film_reviews USING GIN(generated_tsvector); """) ``` - -> #### Important {: .info} -> -> Note that you must explicitly define up and down migrations. Otherwise, the index cannot be dropped. ```elixir # in the database down migration @@ -55,8 +46,11 @@ DROP INDEX film_reviews_search_idx; drop table(:film_reviews) ``` -To enable full-text search, you need to specify the name of the generated column in the live resource of the -corresponding resource. +> #### Important {: .info} +> +> Note that you must explicitly define up and down migrations. Otherwise, the index cannot be dropped. + +To enable full-text search, you need to specify the name of the generated column in the live resource of the corresponding resource: ```elixir # in the live resource @@ -64,3 +58,5 @@ corresponding resource. use Backpex.LiveResource, full_text_search: :generated_tsvector ``` + +You can now perform full-text searches on the resource index view. \ No newline at end of file diff --git a/guides/translations/translations.md b/guides/translations/translations.md index 16e141a3..bef9d05c 100644 --- a/guides/translations/translations.md +++ b/guides/translations/translations.md @@ -1,6 +1,10 @@ # Translations -You are able to translate all strings used by Backpex. To do so, you need to configure translator functions in your application config: +You are able to translate all strings used by Backpex. This includes general strings like "New", "Edit", "Delete", etc., as well as error messages. + +## Configuration + +In order to translate strings, you need to configure two translator functions in your application config: ```elixir config :backpex, :translator_function, {MyAppWeb.Helpers, :translate_backpex} @@ -32,7 +36,7 @@ def translate_error({msg, opts}) do end ``` -You can place the functions in a module of your choice. In this example, we use `MyAppWeb.Helpers` and `MyAppWeb.ErrorHelpers`. +You can place the functions in a module of your choice. In this example, we use `MyAppWeb.Helpers` and `MyAppWeb.ErrorHelpers`. Don't forget to use the correct module in your config as well. In addition, you need to create a Gettext template file in your application. You may use the following template. It contains all translations used by Backpex. diff --git a/lib/backpex/live_resource.ex b/lib/backpex/live_resource.ex index 96090663..96559bbf 100644 --- a/lib/backpex/live_resource.ex +++ b/lib/backpex/live_resource.ex @@ -149,40 +149,6 @@ defmodule Backpex.LiveResource do 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](Backpex.LiveResource.html#c: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 @@ -282,8 +248,6 @@ defmodule Backpex.LiveResource do ..., 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. @@ -315,41 +279,6 @@ defmodule Backpex.LiveResource do 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](full_text_search.md)). - - ## 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. @@ -370,45 +299,6 @@ defmodule Backpex.LiveResource do {: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. @@ -541,18 +431,6 @@ defmodule Backpex.LiveResource do - `Backpex.Fields.Text` > 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. - - ## 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 `tr` element 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 as `even` and `odd` will not always work as intended. Use the provided index instead. The index starts with 0 for the first item. ''' alias Backpex.Resource diff --git a/mix.exs b/mix.exs index 09265c84..24664b5e 100644 --- a/mix.exs +++ b/mix.exs @@ -126,7 +126,7 @@ defmodule Backpex.MixProject do Introduction: ~r/README/, About: ~r/guides\/about\/.?/, "Get Started": ~r/guides\/get_started\/.?/, - LiveResource: ~r/guides\/live_resource\/.?/, + "Live Resource": ~r/guides\/live_resource\/.?/, Fields: ~r/guides\/fields\/.?/, Filter: ~r/guides\/filter\/.?/, Actions: ~r/guides\/actions\/.?/, From b6468450ebb748879ac1505e1b5bcc55e4d896cb Mon Sep 17 00:00:00 2001 From: Florian Arens <60519307+Flo0807@users.noreply.github.com> Date: Fri, 7 Jun 2024 11:13:29 +0200 Subject: [PATCH 05/28] Update docs --- guides/actions/item-actions.md | 193 ++++++++++++++- guides/actions/resource-actions.md | 91 ++++++- guides/authorization/field-authorization.md | 30 ++- .../live-resource-authorization.md | 54 ++++- guides/fields/alignment.md | 47 +++- guides/fields/computed-fields.md | 86 +++++++ guides/fields/custom-alias.md | 26 ++ guides/fields/custom-field.md | 1 - guides/fields/custom-fields.md | 1 + guides/fields/debounce-and-throttle.md | 94 ++++++++ guides/fields/defaults.md | 22 +- guides/fields/error-customization.md | 28 +++ guides/fields/index-edit.md | 36 ++- guides/fields/placeholder.md | 27 +++ guides/fields/readonly.md | 78 +++++- guides/fields/visibility.md | 137 ++++++++++- guides/fields/what-is-a-field.md | 77 +++++- guides/filter/custom-filter.md | 1 + guides/filter/filter-presets.md | 1 + guides/get_started/installation.md | 35 ++- guides/get_started/prerequisites.md | 2 +- .../live_resource/what-is-a-live-resource.md | 6 +- lib/backpex/field.ex | 137 ----------- lib/backpex/item_actions/item_action.ex | 114 +-------- lib/backpex/live_resource.ex | 228 ------------------ lib/backpex/resource_action.ex | 43 ---- mix.exs | 29 ++- 27 files changed, 1077 insertions(+), 547 deletions(-) create mode 100644 guides/fields/computed-fields.md create mode 100644 guides/fields/custom-alias.md delete mode 100644 guides/fields/custom-field.md create mode 100644 guides/fields/custom-fields.md create mode 100644 guides/fields/debounce-and-throttle.md create mode 100644 guides/fields/error-customization.md create mode 100644 guides/fields/placeholder.md create mode 100644 guides/filter/custom-filter.md create mode 100644 guides/filter/filter-presets.md diff --git a/guides/actions/item-actions.md b/guides/actions/item-actions.md index 9c749cfa..d2505b4d 100644 --- a/guides/actions/item-actions.md +++ b/guides/actions/item-actions.md @@ -1 +1,192 @@ -# Item Actions \ No newline at end of file +# Item Actions + +An item action defines an action (such as deleting a user) that can be performed on one or more items. Unlike resource actions, item actions are not automatically performed on all items in a resource. + +An item action could be something like deleting a user, or sending an email to a specific user. + +There are multiple ways to perform an Item Action: +- use the checkboxes in the first column of the resource table to select 1-n items and trigger the action later on +- use an icon in the last column of the resource table to perform the Item Action for one item +- use the corresponding icon in the show view to perform the Item Action for the corresponding item + +If you use the first method, you must trigger the item action using the button above the resource action. If you use the second or third method, the item action is triggered immediately. + +Backpex ships with a few built-in item actions, such as `delete`, `show`, and `edit`. + +## Configuration + +To add an item action to a resource, you need to implement the [`item_actions/1`](Backpex.LiveResource.html#c:item_actions/1) callback in your resource configuration module. The function should return a list of maps, where each map represents an item action. It takes the default item actions as an argument. This way you can add your custom item actions to the default ones or even replace them. + +Let's say we want to add a `show` item action to navigate to the show view of a user and replace all other default item actions. + +First, we need to add the item action to our resource configuration module. + +```elixir +# in your resource configuration file +@impl Backpex.LiveResource +def item_actions([_show, _edit, _delete]) do + [ + show: %{ + module: DemoWeb.ItemAction.Show + } + ] +end +``` + +In the above example, we only return the `show` item action. This way we replace the default `show`, `edit`, and `delete` item actions with our custom `show` item action. + +## Implementing an Item Action + +An item action is a module that uses the `Backpex.ItemAction` module. To get started, you can use the `BackpexWeb` module and provide the `:item_action` option. This will import the necessary functions and macros to define an item action. + +In the following example, we define an item action to navigate to the show view of a user. + +```elixir +defmodule DemoWeb.ItemAction.Show do + use BackpexWeb, :item_action + + @impl Backpex.ItemAction + def icon(assigns) do + ~H""" + + """ + end + + @impl Backpex.ItemAction + def label(_assigns), do: Backpex.translate("Show") + + @impl Backpex.ItemAction + def handle(socket, [item | _items], _change) do + path = Router.get_path(socket, socket.assigns.live_resource, socket.assigns.params, :show, item) + {:noreply, Phoenix.LiveView.push_patch(socket, to: path)} + end +end +``` + +Like in resource actions the `handle/3` function is called when the item action is triggered. The handle function receives the socket, the items that should be affected by the action, and the parameters that were submitted by the user. + +In the above example, we define an item action to navigate to the show view of a user. The `handle/3` function is used to navigate to the show view of the user. The `Router.get_path/5` function is used to generate the path to the show view of the user. + +See `Backpex.ItemAction` for a list of all available callbacks. + +## Placement of Item Actions + +Item actions can be placed in the resource table or in the show view. You can specify the placement of the item action by using the `only` key. + +The only key must provide a list and accepts the following options + +* `:row` - display an icon for each element in the table that can trigger the Item Action for the corresponding element +* `:index` - display a button at the top of the resource table, which triggers the Item Action for selected items +* `:show` - display an icon in the show view that triggers the Item Action for the corresponding item + +The following example shows how to place the `show` item action on the index table rows only. + +```elixir +# in your resource configuration file +@impl Backpex.LiveResource +def item_actions([_show, _edit, _delete]) do + [ + show: %{ + module: DemoWeb.ItemAction.Show, + only: [:row] + } + ] +end +``` + +## Advanced Item Action + +In the following example, we define an item action to soft delete users. The item action will also asked the user for a reason before the user can be deleted. + +First, wee need to add the item action to our resource configuration module. + +```elixir +# in your resource configuration file + +@impl Backpex.LiveResource +def item_actions([show, edit, _delete]) do + Enum.concat([show, edit], + soft_delete: %{module: DemoWeb.ItemAction.SoftDelete} + ) +end +``` + +In the above example, we add the `soft_delete` item action to the default item actions. We do not add the default `delete` item action to the list of item actions. This way we replace the default `delete` item action with our custom `soft_delete` item action. + +Next, we need to implement the item action module. + +```elixir +defmodule DemoWeb.ItemAction.SoftDelete do + use BackpexWeb, :item_action + + alias Backpex.Resource + + @impl Backpex.ItemAction + def icon(assigns) do + ~H""" + + """ + end + + @impl Backpex.ItemAction + def fields do + [ + reason: %{ + module: Backpex.Fields.Textarea, + label: "Reason", + type: :string + } + ] + end + + @required_fields ~w[reason]a + + @impl Backpex.ItemAction + def changeset(change, attrs) do + change + |> cast(attrs, @required_fields) + |> validate_required(@required_fields) + end + + @impl Backpex.ItemAction + def label(_assigns), do: Backpex.translate("Delete") + + @impl Backpex.ItemAction + def confirm_label(_assigns), do: Backpex.translate("Delete") + + @impl Backpex.ItemAction + def cancel_label(_assigns), do: Backpex.translate("Cancel") + + @impl Backpex.ItemAction + def handle(socket, items, params) do + datetime = DateTime.truncate(DateTime.utc_now(), :second) + + socket = + try do + {:ok, _items} = + Backpex.Resource.update_all( + socket.assigns, + items, + [set: [deleted_at: datetime, reason: Map.get(params, "reason")]], + "deleted" + ) + + socket + |> clear_flash() + |> put_flash(:info, "Item(s) successfully deleted.") + rescue + socket + |> clear_flash() + |> put_flash(:error, error) + end + + {:noreply, socket} + end +end +``` + +In the above example, we define an item action to soft delete users. The item action will also ask the user for a reason before the user can be deleted. The user needs to fill out the reason field before the item action can be performed. The reason field is defined in the `fields/0` function. The `changeset/2` function is used to validate the user input. + +The `handle/3` function is called when the item action is triggered. The handle function receives the socket, the items that should be affected by the action, and the parameters that were submitted by the user. + +By default an item action is triggered immediately when the user clicks on the corresponding icon in the resource table or in the show view, but an item actions also supports a confirmation dialog. To enable the confirmation dialog you need to implement the `confirm_label/1` function and return a string that will be displayed in the confirmation dialog. The confirmation dialog will be displayed when the user clicks on the icon in the resource table. \ No newline at end of file diff --git a/guides/actions/resource-actions.md b/guides/actions/resource-actions.md index be1a13a1..ddf0891d 100644 --- a/guides/actions/resource-actions.md +++ b/guides/actions/resource-actions.md @@ -1 +1,90 @@ -# Resource Actions \ No newline at end of file +# Resource Actions + +Resource actions are a way to define custom actions that can be performed on a whole resource. + +A resource action could be something like exporting a resource to a CSV file, or sending an email to all users in a resource. + +## Configuration + +You define resource actions by implementing the [`resource_actions/0`](Backpex.LiveResource.html#c:resource_actions/0) callback in your resource configuration module. + +Let's say you have a resource called `User` and you want to add a resource action to invite users to your application. + +First, you need to add the resource action to your resource configuration module. + +```elixir +# in your resource configuration file + +@impl Backpex.LiveResource +def resource_actions() do +[ + invite: %{ + module: MyWebApp.Admin.ResourceActions.Invite, + } +] +end +``` + +Each resource action is a map with at least the module key. The module key should point to the module that implements the resource action. The key in the keyword list is the unique `id` of the resource action. + +## Implementing a Resource Action + +A resource action is a module that uses the `Backpex.ResourceAction` module. + +```elixir +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 + + @impl Backpex.ResourceAction + def changeset(change, attrs) do + change + |> cast(attrs, [:email]) + |> validate_required([:email]) + |> validate_email(:email) + end + + @impl Backpex.ResourceAction + def handle(_socket, params) do + # Send mail + + # We suppose there was no error. + if true do + {:ok, "An invitation email to #{params[:email]} was sent successfully."} + else + {:error, "An error occurred while sending an invitation email to #{params[:email]}!"} + end + end +end +``` + +See `Backpex.ResourceAction` for a documentation of the callbacks. + +The [`handle/2`](Backpex.ResourceAction.html#c:handle/2) callback is called when the user submits the form to perform the action. In this example, we suppose there was no error sending the invitation email and return a success message. + +You can access the email entered by the user in the `params` argument. + +We validate the email address using the `validate_email/2` function provided by the `Ecto.Changeset` module. + +> #### Info {: .info} +> +> Each resource action has its own route. The route is defined by the `id` of the resource action. If you use the [`live_resource/3`](Backpex.Router.html#live_resources/3) macro, the route is automatically added to the live resource. \ No newline at end of file diff --git a/guides/authorization/field-authorization.md b/guides/authorization/field-authorization.md index 9c0c7a41..7ae0277d 100644 --- a/guides/authorization/field-authorization.md +++ b/guides/authorization/field-authorization.md @@ -1 +1,29 @@ -# Field Authorization \ No newline at end of file +# Field Authorization + +You can define authorization rules for your fields. + +## Configuration + +To define authorization rules for a field, you may use the `can?/1` callback for a field configuration. It takes the assigns and has to return a boolean value. + +```elixir +# in your resource configuration file +@impl Backpex.LiveResource +def fields do +[ + inserted_at: %{ + module: Backpex.Fields.DateTime, + label: "Created At", + can?: fn + %{live_action: :show} = _assigns -> + true + + _assigns -> + false + end + } +] +end +``` + +The above example will show the `inserted_at` field only in the show view. diff --git a/guides/authorization/live-resource-authorization.md b/guides/authorization/live-resource-authorization.md index 43486111..a496d7ff 100644 --- a/guides/authorization/live-resource-authorization.md +++ b/guides/authorization/live-resource-authorization.md @@ -1 +1,53 @@ -# LiveResource Authorization \ No newline at end of file +# LiveResource Authorization + +You are able to define authorization rules for your resources. The authorization rules are defined in the resource configuration file and are used to control access to certain actions. + +## Configuration + +To define authorization rules for a resource, you need to implement the [`can/3`](Backpex.LiveResource.html#c:can?/3) callback in the resource configuration file. + +```elixir +# in your resource configuration file +@impl Backpex.LiveResource +def can?(assigns, :show, item), do: false +def can?(assigns, action, item), do: true +``` + +The example above will deny access to the `show` action and allow access to all other actions. + +```elixir +# in your resource configuration file +@impl Backpex.LiveResource +def can?(assigns, :show, item) do + user = assigns.current_user + + item.user_id == user.id +end‚ + +def can?(assigns, action, item), do: true +``` + +The example above will deny access to the `show` action if the `user_id` of the item does not match the `id` of the current user. + +You can also use [`can/3`](Backpex.LiveResource.html#c:can?/3) to restrict access to item or resource actions. + +```elixir +# in your resource configuration file +@impl Backpex.LiveResource +def can?(_assigns, :my_item_action, item), do: item.role == :admin +def can?(assigns, action, item), do: true +``` + +The example above will deny access to the `my_item_action` action if the `role` of the item is not `:admin`. + +## Parameters + +The `can?` callback receives the following parameters: + +- `assigns` - the assigns of the LiveView +- `action` - the action that is being authorized (available actions are: `:index` , `:new`, `:show`, `:edit`, `:delete`, `:your_item_action_key`, `:your_resource_action_key`) +- `item` - the item that is being authorized + +## Return value + +The `can?` callback must return a boolean value. If the return value is `true`, the action is allowed. If the return value is `false`, the action is denied. \ No newline at end of file diff --git a/guides/fields/alignment.md b/guides/fields/alignment.md index 120efa3a..7a46b466 100644 --- a/guides/fields/alignment.md +++ b/guides/fields/alignment.md @@ -1 +1,46 @@ -# Alignment \ No newline at end of file +# Alignment + +It is possible to align the fields of a resource in the index view and the labels of the fields in the edit view. + +## Field Alignment (Index View) + +You can align the fields of a resource in the index view by setting the `align` option in the field configuration. + +The following alignments are supported: `:left`, `:center` and `:right` + +```elixir +# in your resource configuration file + +@impl Backpex.LiveResource +def fields do +[ + %{ + ..., + align: :center + } +] +end +``` + +The example above will align the field to the center in the index view. + +## Label Alignment (Edit View) + +You can align the labels of the fields in the edit view by setting the `label_align` option in the field configuration. + +The following label orientations are supported: `:top`, `:center` and `:bottom`. + +```elixir +# in your resource configuration file +@impl Backpex.LiveResource +def fields do +[ + %{ + ..., + align_label: :top + } +] +end +``` + +The example above will align the label to the top in the edit view. \ No newline at end of file diff --git a/guides/fields/computed-fields.md b/guides/fields/computed-fields.md new file mode 100644 index 00000000..d3c3d628 --- /dev/null +++ b/guides/fields/computed-fields.md @@ -0,0 +1,86 @@ +# Computed Fields + +In some cases you want to compute new fields based on existing fields. Backpex adds a way to support this. + +## Configuration + +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. + +```elixir +# in your resource configuration file + +@impl Backpex.LiveResource +def fields do +[ + total: %{ + module: Backpex.Fields.Integer, + label: "Total", + select: dynamic([post: p], fragment("likes + dislikes")), + } +] +end +``` + +The example above will compute the value of the total field based on the `likes` and `dislikes` fields. + +## Example + +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. + +You can display the `full_name` of your users by adding the following field to the resource configuration file. + +```elixir +# in your resource configuration file + +@impl Backpex.LiveResource +def fields do +[ + 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)) + } +] +end +``` + +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. + +> #### Important {: .info} +> +> 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 with Associations + +Computed fields also work with associations. + +For example, you are able to add a `select` query to a `Backpex.Field.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`: + +```elixir +# in your resource configuration file + +@impl Backpex.LiveResource +def fields do +[ + 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 + } +] +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. diff --git a/guides/fields/custom-alias.md b/guides/fields/custom-alias.md new file mode 100644 index 00000000..f9e4d28b --- /dev/null +++ b/guides/fields/custom-alias.md @@ -0,0 +1,26 @@ +# Custom Alias + +Backpex automatically generates aliases for queries in your fields. However, if you try to add two `Backpex.Field.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. + +## Configuration + +To use a custom alias, define the `custom_alias` key in your field configuration. The value of the `custom_alias` key must be a unique atom that is not already in use by another field. + + +```elixir +@impl Backpex.LiveResource +def fields do +[ + 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) + }, +] +end +``` + +The example above will assign the alias `:second_category` to the `second_category` field. This alias can now be used in queries without causing conflicts with other fields. \ No newline at end of file diff --git a/guides/fields/custom-field.md b/guides/fields/custom-field.md deleted file mode 100644 index 04f24d32..00000000 --- a/guides/fields/custom-field.md +++ /dev/null @@ -1 +0,0 @@ -# Custom Field \ No newline at end of file diff --git a/guides/fields/custom-fields.md b/guides/fields/custom-fields.md new file mode 100644 index 00000000..d2bdb2a1 --- /dev/null +++ b/guides/fields/custom-fields.md @@ -0,0 +1 @@ +# Custom Fields \ No newline at end of file diff --git a/guides/fields/debounce-and-throttle.md b/guides/fields/debounce-and-throttle.md new file mode 100644 index 00000000..552bf711 --- /dev/null +++ b/guides/fields/debounce-and-throttle.md @@ -0,0 +1,94 @@ +# Debounce and Throttle + +You can debounce and throttle the input of a field in the edit view. This is useful when you want to reduce the number of requests sent to the server. + +## Configuration + +To enable debounce or throttle for a field, you need to set the `debounce` or `throttle` option in the field configuration. + +- `debounce`: Has to return either an integer timeout value (in milliseconds), or "blur". When an integer is provided, emitting the event is delayed by the specified milliseconds. When "blur" is provided, emitting the event is delayed until the field is blurred by the user. +- `throttle`: Has to return an integer timeout value (in milliseconds). The event is emitted at most once every specified milliseconds. + +See Phoenix LiveView documentation for more information on [debouncing and throttling](https://hexdocs.pm/phoenix_live_view/bindings.html#rate-limiting-events-with-debounce-and-throttle). + +The options can be set to a function that receives the assigns and returns the debounce or throttle value or a static value. + +```elixir +# in your resource configuration file +@impl Backpex.LiveResource +def fields do +[ + username: %{ + module: Backpex.Fields.Text, + label: "Username", + debounce: 500 + } +] +end +``` + +The example above will debounce the input of the `username` field by 500 milliseconds. + +```elixir +# in your resource configuration file +@impl Backpex.LiveResource +def fields do +[ + username: %{ + module: Backpex.Fields.Text, + label: "Username", + debounce: fn _assigns -> 500 end + } +] +end +``` + +The example above will debounce the input of the `username` field by 500 milliseconds. + +```elixir +# in your resource configuration file +@impl Backpex.LiveResource +def fields do +[ + username: %{ + module: Backpex.Fields.Text, + label: "Username", + debounce: "blur" + } +] +end +``` + +The example above will debounce the input of the `username` field until the field is blurred by the user. + +```elixir +# in your resource configuration file +@impl Backpex.LiveResource +def fields do +[ + username: %{ + module: Backpex.Fields.Text, + label: "Username", + throttle: 500 + } +] +end +``` + +The example above will throttle the input of the `username` field by 500 milliseconds. + +```elixir +# in your resource configuration file +@impl Backpex.LiveResource +def fields do +[ + username: %{ + module: Backpex.Fields.Text, + label: "Username", + throttle: fn _assigns -> 500 end + } +] +end +``` + +The example above will throttle the input of the `username` field by 500 milliseconds. \ No newline at end of file diff --git a/guides/fields/defaults.md b/guides/fields/defaults.md index 6b175aeb..b084ebce 100644 --- a/guides/fields/defaults.md +++ b/guides/fields/defaults.md @@ -1 +1,21 @@ -# Defaults \ No newline at end of file +# Defaults + +You can assign default values to fields in your resource configuration file. This is useful when you want to provide a default value for a field that is not required. + +## Configuration + +To define a default value for a field, you need to set the `default` option in the field configuration. The `default` option has to return a function that receives the assigns and returns the default value. + +```elixir +# in your resource configuration file +@impl Backpex.LiveResource +def fields do +[ + username: %{ + default: fn _assigns -> "Default Username" end + } +] +end +``` + +The example above will assign the default value `"Default Username"` to the `username` field. diff --git a/guides/fields/error-customization.md b/guides/fields/error-customization.md new file mode 100644 index 00000000..dc60c04d --- /dev/null +++ b/guides/fields/error-customization.md @@ -0,0 +1,28 @@ +# Error Customization + +You can customize the error messages for each field in your resource configuration file. + +## Configuration + +To customize the error messages for a field, you need to define a `translate_error/1` function in the field configuration. The function receives the error tuple and must return a tuple with the message and metadata. + +```elixir +@impl Backpex.LiveResource +def fields do +[ + 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 + } +] +end +``` + +The example above will return the message `"has to be a number"` when the input is not a number. diff --git a/guides/fields/index-edit.md b/guides/fields/index-edit.md index 5a714041..d771d62a 100644 --- a/guides/fields/index-edit.md +++ b/guides/fields/index-edit.md @@ -1 +1,35 @@ -# Index Edit \ No newline at end of file +# Index Edit + +A small number of fields support index editable. These fields can be edited inline on the index view. + +## Configuration + +To enable index editable for a field, you need to set the `index_editable` option to `true` in the field configuration. + +```elixir +# in your resource configuration file +def fields do +[ + name: %{ + module: Backpex.Fields.Text, + label: "Name", + index_editable: true + } +] +end +``` + +The example above will enable index editable for the `name` text field. + +## Supported fields + +- `Backpex.Fields.BelongsTo` +- `Backpex.Fields.Date` +- `Backpex.Fields.DateTime` +- `Backpex.Fields.Number` +- `Backpex.Fields.Select` +- `Backpex.Fields.Text` + +## Custom index editable implementation + +You can add index editable support to your custom fields by defining the [render_index_form/1](Backpex.Field.html#c:render_index_form/1) function and enabling index editable for your field. \ No newline at end of file diff --git a/guides/fields/placeholder.md b/guides/fields/placeholder.md new file mode 100644 index 00000000..bb384875 --- /dev/null +++ b/guides/fields/placeholder.md @@ -0,0 +1,27 @@ +# Placeholder + +You can configure a placeholder for form fields. The placeholder will be displayed in the input field when the field is empty. + +> #### Important {: .info} +> +> Note that the option only works for input fields that are **not** type `textarea`, `select`, `toggle` or `checkbox`. +> +## Configuration + +To set a placeholder for a field, you need to set the `placeholder` option in the field configuration. The `placeholder` either has to be a string or a function that receives the assigns and returns the placeholder string. + +```elixir +# in your resource configuration file +@impl Backpex.LiveResource +def fields do +[ + username: %{ + module: Backpex.Fields.Text, + label: "Username", + placeholder: "Enter your username" + } +] +end +``` + +The example above will set the placeholder `"Enter your username"` for the `username` field. diff --git a/guides/fields/readonly.md b/guides/fields/readonly.md index 1f4fb3b0..aeb03a9b 100644 --- a/guides/fields/readonly.md +++ b/guides/fields/readonly.md @@ -1 +1,77 @@ -# Readonly \ No newline at end of file +# Readonly + +Fields can be configured to be readonly. 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 readonly and index editable is set to `true`, forms will be rendered with the `readonly` HTML attribute. + +## Supported fields + +On index view, read-only is supported for all fields with the index editable option (see [Index Edit](index-edit.md)). + +On edit view, read-only is supported for: +- `Backpex.Fields.Date` +- `Backpex.Fields.DateTime` +- `Backpex.Fields.Number` +- `Backpex.Fields.Text` +- `Backpex.Fields.Textarea` + +## Configuration + +To enable read-only for a field, you need to set the `readonly` option to `true` in the field configuration. This key must contain either a boolean value or a function that returns a boolean value. + +```elixir +# in your resource configuration file +def fields do +[ + rating: %{ + module: Backpex.Fields.Text, + label: "Rating", + readonly: fn assigns -> + assigns.current_user.role in [:employee] + end + } +] +end +``` + +```elixir +# in your resource configuration file +def fields do +[ + rating: %{ + module: Backpex.Fields.Text, + label: "Rating", + readonly: true + } +] +end +``` + +## Readonly for custom fields + +You can also add readonly functionality to a custom field. To do this, you need to define a [`render_form_readonly/1`](Backpex.Field.html#c:render_form_readonly/1) function. This function must return markup to be used when readonly is enabled. + +```elixir +@impl Backpex.Field +def render_form_readonly(assigns) do +~H""" +
+ + <:label> + + + + +
+""" +end +``` + +When defining a custom field with index editable support, you need to handle the readonly state in the index editable markup. There is a `readonly` value in the assigns, which will be `true` or `false`. diff --git a/guides/fields/visibility.md b/guides/fields/visibility.md index cd0d6a1a..a870da1f 100644 --- a/guides/fields/visibility.md +++ b/guides/fields/visibility.md @@ -1 +1,136 @@ -# Visibility \ No newline at end of file +# Visibility + +You can change the visibility of fields in certain views. + +## Visibility with `only` and `except` + +You can use the `only` and `except` options to define the views where a field should be visible. The `only` option will show the field only in the specified views, while the `except` option will show the field in all views except the specified ones. The options have to be a list of view names. + +The following values are supported: `:new`, `:edit`, `:show`, `:index` and `:resource_action`. + +```elixir +# in your resource configuration file +@impl Backpex.LiveResource +def fields do +[ + likes: %{ + module: Backpex.Fields.Number, + label: "Likes", + only: [:show, :edit] + } +] +end +``` + +The example above will show the `likes` field only in the show and edit views. + +```elixir +# in your resource configuration file +@impl Backpex.LiveResource +def fields do +[ + likes: %{ + module: Backpex.Fields.Number, + label: "Likes", + except: [:new] + } +] +end +``` + +The example above will show the `likes` field in all views except the new view. + +## Visibility with `visible` + +> #### Important {: .info} +> +> Note that the option `visible` is only available for the show and edit views. + +To change the visibility of a field, you can also set the `visible` option in the field configuration. The `visible` option has to return a function that receives the assigns and returns a boolean value. + +```elixir +# in your resource configuration file +@impl Backpex.LiveResource +def fields do +[ + likes: %{ + module: Backpex.Fields.Number, + label: "Likes", + visible: fn assigns -> + assigns.current_user.role in [:admin] + end + } +] +end +``` + +The example above will show the `likes` field only to users with the `admin` role. + + +> #### Warning {: .warning} +> +> Note that hidden fields are not exempt from validation by Backpex itself and the visible function is not executed on `:index`. + +## Visibility with `can?` + +In addition to the `visible` option, we provide a `can?` option that you can use to determine the visibility of a field. + +It can also be used on `:index`. It takes the `assigns` as a parameter and has to return a boolean value. + +```elixir +# in your resource configuration file +@impl Backpex.LiveResource +def fields do +[ + inserted_at: %{ + module: Backpex.Fields.DateTime, + label: "Created At", + can?: fn + %{live_action: :show} = _assigns -> + true + + _assigns -> + false + end + } +] +end +``` + +Also see the [field authorization](/guides/authorization/field-authorization.md) guide. + +## Advanced Example + +Imagine you want to implement a checkbox in order to toggle an input field (post likes). The input field should be visible when it has a certain value (post likes > 0). + +```elixir +# 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 +``` diff --git a/guides/fields/what-is-a-field.md b/guides/fields/what-is-a-field.md index 301f10ea..6bc9a95c 100644 --- a/guides/fields/what-is-a-field.md +++ b/guides/fields/what-is-a-field.md @@ -1 +1,76 @@ -# What is a Field? \ No newline at end of file +# What is a Field? + +Backpex fields are the building blocks of a resource. They define the data that will be displayed and manipulated in the resource views. Fields can be of different types, such as text, number, date, and select. Each field type has its own configuration options and behavior. Typically, you want to configure a field for each type of data you want to display in your resource. + +Backpex ships with a set of built-in field types, but you can also create custom fields to fit your specific needs. + +## Built-in Field Types + +Backpex provides the following built-in field types: + +- `Backpex.Fields.BelongsTo` +- `Backpex.Fields.Boolean` +- `Backpex.Fields.Currency` +- `Backpex.Fields.DateTime` +- `Backpex.Fields.Date` +- `Backpex.Fields.HasManyThrough` +- `Backpex.Fields.HasMany` +- `Backpex.Fields.InlineCRUD` +- `Backpex.Fields.ManyToMany` +- `Backpex.Fields.MultiSelect` +- `Backpex.Fields.Number` +- `Backpex.Fields.Select` +- `Backpex.Fields.Text` +- `Backpex.Fields.Textarea` +- `Backpex.Fields.Upload` +- `Backpex.Fields.URL` + +You can click on each field type to see its documentation and configuration options. + +## Configuration + +To define fields for a resource, you need to implement the [`fields/0`](Backpex.LiveResource.html#c:fields/0) callback in your resource module. This function must return a list of field configurations. + +```elixir +@impl Backpex.LiveResource +def fields do + [ + username: %{ + module: Backpex.Fields.Text, + label: "Username" + }, + email: %{ + module: Backpex.Fields.Email, + label: "Email" + } + ] +end +``` + +The example above will define two fields: `username` and `email`. Both fields use the built-in field types `Backpex.Fields.Text` and `Backpex.Fields.Email`, respectively. + +## Field Configuration + +Each field configuration must contain the following keys: + +- `module`: The module that implements the field behavior. +- `label`: The label that will be displayed for the field. + +In addition to these keys, you can configure each field with additional options specific to the field type. For example, a text field can have a `placeholder` option to set a placeholder text for the input field. + +```elixir +@impl Backpex.LiveResource +def fields do + [ + username: %{ + module: Backpex.Fields.Text, + label: "Username", + placeholder: "Enter your username" + } + ] +end +``` + +The example above will set the placeholder `"Enter your username"` for the `username` field. + +The following sections will cover general field options and how to create custom fields. diff --git a/guides/filter/custom-filter.md b/guides/filter/custom-filter.md new file mode 100644 index 00000000..4b1d2667 --- /dev/null +++ b/guides/filter/custom-filter.md @@ -0,0 +1 @@ +# Custom Filter \ No newline at end of file diff --git a/guides/filter/filter-presets.md b/guides/filter/filter-presets.md new file mode 100644 index 00000000..a1760d8c --- /dev/null +++ b/guides/filter/filter-presets.md @@ -0,0 +1 @@ +# Filter Presets \ No newline at end of file diff --git a/guides/get_started/installation.md b/guides/get_started/installation.md index efe6dbb3..a4c9c63d 100644 --- a/guides/get_started/installation.md +++ b/guides/get_started/installation.md @@ -1,15 +1,6 @@ # Installation -## Prerequisites - -Make sure you have a running phoenix application with [Tailwind CSS](https://tailwindcss.com/) and [daisyUI](https://daisyui.com/docs/install/) installed. - -> #### Important {: .info} -> -> Backpex currently only supports daisyUI light mode. - -Backpex currently depends on [Ecto](https://hexdocs.pm/ecto/Ecto.html). Therefore there has to be a running -ecto repository. +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. ## Add Backpex to list of dependencies @@ -19,22 +10,32 @@ In your `mix.exs`: defp deps do [ ... - {:backpex, path: "backpex_path"} + {:backpex, "~> 0.2.0"} ] end ``` ## Add Backpex files to tailwind content +Backpex uses Tailwind CSS and daisyUI. Make sure to add the Backpex files to your tailwind content to make sure the styles are applied. + In your `tailwind.config.js`: ```js -content: ['./path_to_deps/backpex/**/*.*ex'] +.., +content: [ + ..., + 'deps/backpex/**/*.*ex' +] ``` +> #### Info {: .info} +> +> The path to the Backpex files may vary depending on your project setup. + ## Setup formatter -We recommend to add Backpex to the list of dependencies in your `.formatter.exs`. +Backpex ships with a formatter configuration. To use it, add Backpex to the list of dependencies in your `.formatter.exs`. ```elixir # my_app/.formatter.exs @@ -90,3 +91,11 @@ If you do not want to put effort into creating your own layout, feel free to use ``` You can now create and configure the corresponding resources. + +## Create test resource + +## Setup routing + +## Configure LiveResource + +## Configure Routes diff --git a/guides/get_started/prerequisites.md b/guides/get_started/prerequisites.md index 486c3eb1..e2af32bd 100644 --- a/guides/get_started/prerequisites.md +++ b/guides/get_started/prerequisites.md @@ -14,7 +14,7 @@ Backpex uses Tailwind CSS for styling. Make sure you have Tailwind CSS installed Backpex is styled using daisyUI. Make sure you have daisyUI installed in your application. You can install daisyUI by following the [official installation guide](https://daisyui.com/docs/install/). -> #### Important {: .info} +> #### Warning {: .warning} > > Backpex currently only supports daisyUI light mode. Help us to support dark mode by contributing to the project. diff --git a/guides/live_resource/what-is-a-live-resource.md b/guides/live_resource/what-is-a-live-resource.md index 36ce6a37..eb97ef0c 100644 --- a/guides/live_resource/what-is-a-live-resource.md +++ b/guides/live_resource/what-is-a-live-resource.md @@ -1 +1,5 @@ -# What is a LiveResource? \ No newline at end of file +# What is a LiveResource? + +When refer to a *LiveResource* in Backpex, we are talking about a module that contains the configuration for a resource. This module is responsible for defining the resource's schema, the actions that can be performed on it, and the fields that will be rendered. + +In the documentation, we also talk about the resource configuration file. With this, we refer to the module that implements the `Backpex.LiveResource` behavior. This is a Backpex *LiveResource* module. diff --git a/lib/backpex/field.ex b/lib/backpex/field.ex index 76ff8ddf..944c3a5b 100644 --- a/lib/backpex/field.ex +++ b/lib/backpex/field.ex @@ -53,143 +53,6 @@ defmodule Backpex.Field do } ] 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""" -
- - <:label> - - - - -
- """ - 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 - } ''' import Phoenix.Component, only: [assign: 3] diff --git a/lib/backpex/item_actions/item_action.ex b/lib/backpex/item_actions/item_action.ex index eaf8daf7..4254d061 100644 --- a/lib/backpex/item_actions/item_action.ex +++ b/lib/backpex/item_actions/item_action.ex @@ -1,117 +1,7 @@ defmodule Backpex.ItemAction do - @moduledoc ~S''' + @moduledoc """ Behaviour implemented by all item actions. - - An Item Action defines an action (such as deleting a user) that can be performed on one or more items. - Unlike resource actions, item actions are not automatically performed on all items in a resource. - First, items must be selected. - - There are multiple ways to perform an Item Action: - - use the checkboxes in the first column of the resource table to select 1-n items and trigger the action later on - - use an icon in the last column of the resource table to perform the Item Action for one item - - use the corresponding icon in the show view to perform the Item Action for the corresponding item - - If you use the first method, you must trigger the item action using the button above the resource action. If you use the second or third method, the item action is triggered immediately. - - Therefore, you need to provide a label and an icon in your code. The label is displayed above the resource table and the icon - is displayed for each element in the table and in the show view. This gives the user multiple ways to trigger your item action. - - Optionally, there can be a confirmation modal. You can enable this by specifying a text to be displayed in the modal via the `confirm` function. You can also specify a changeset and a list of fields. The fields are displayed in the confirm modal as well. - For example, a field could be a reason for deleting a user. - - In the list of fields, each field needs a type. This is because we cannot infer a type from a schema. - - To add an item action to your resource you can use the `item_actions` function. This must be a keyword list. - The keyword defines the name of the action. In addition, each keyword must define a map as a value. This map must at least - provide the module for the item action with the `module` key. The "default" Item Actions (Delete, Edit and Show) are passed as - parameters to the item_actions and may be appended to your item actions. - - Furthermore, you can specify which ways of triggering the item action are enabled with the `only` key. - - The only key must provide a list and accepts the following options - - `:row` - display an icon for each element in the table that can trigger the Item Action for the corresponding element - - `:index` - display a button at the top of the resource table, which triggers the Item Action for selected items - - `:show` - display an icon in the show view that triggers the Item Action for the corresponding item - - The following example shows how to define a soft delete item action and replace it with the default delete Item Action. - - ## Example - - defmodule DemoWeb.ItemAction.SoftDelete do - use BackpexWeb, :item_action - - alias Backpex.Resource - - @impl Backpex.ItemAction - def icon(assigns) do - ~H""" - - """ - end - - @impl Backpex.ItemAction - def fields do - [ - reason: %{ - module: Backpex.Fields.Textarea, - label: "Reason", - type: :string - } - ] - end - - @required_fields ~w[reason]a - - @impl Backpex.ItemAction - def changeset(change, attrs) do - change - |> cast(attrs, @required_fields) - |> validate_required(@required_fields) - end - - @impl Backpex.ItemAction - def label(_assigns), do: Backpex.translate("Delete") - - @impl Backpex.ItemAction - def confirm_label(_assigns), do: Backpex.translate("Delete") - - @impl Backpex.ItemAction - def cancel_label(_assigns), do: Backpex.translate("Cancel") - - @impl Backpex.ItemAction - def handle(socket, items, params) do - datetime = DateTime.truncate(DateTime.utc_now(), :second) - socket = - try do - {:ok, _items} = - Backpex.Resource.update_all( - socket.assigns, - items, - [set: [deleted_at: datetime, reason: Map.get(params, "reason")]], - "deleted" - ) - - socket - |> clear_flash() - |> put_flash(:info, "Item(s) successfully deleted.") - rescue - socket - |> clear_flash() - |> put_flash(:error, error) - end - - {:noreply, socket} - end - - # in your resource configuration file - - @impl Backpex.LiveResource - def item_actions([show, edit, _delete]) do - Enum.concat([show, edit], - soft_delete: %{module: DemoWeb.ItemAction.SoftDelete} - ) - end - ''' + """ @doc """ Action icon diff --git a/lib/backpex/live_resource.ex b/lib/backpex/live_resource.ex index 96559bbf..5f3c88cd 100644 --- a/lib/backpex/live_resource.ex +++ b/lib/backpex/live_resource.ex @@ -149,105 +149,6 @@ defmodule Backpex.LiveResource do end end - ## 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 the `item_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. - - ```elixir - 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 - ``` - - ```elixir - # 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 You are required to configure your router in order to point to the resources created in before steps. @@ -299,138 +200,9 @@ defmodule Backpex.LiveResource do {:noreply, socket} 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](https://daisyui.com/components/tooltip/). - - ## 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: - - `Backpex.Fields.BelongsTo` - - `Backpex.Fields.Date` - - `Backpex.Fields.DateTime` - - `Backpex.Fields.Number` - - `Backpex.Fields.Select` - - `Backpex.Fields.Text` - - > 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. ''' alias Backpex.Resource diff --git a/lib/backpex/resource_action.ex b/lib/backpex/resource_action.ex index 1449bc87..21aa9b87 100644 --- a/lib/backpex/resource_action.ex +++ b/lib/backpex/resource_action.ex @@ -2,49 +2,6 @@ defmodule Backpex.ResourceAction do @moduledoc ~S''' Behaviour implemented by all resource action modules. - ## Example - - defmodule MyAppWeb.Actions.Invite do - use Backpex.ResourceAction - - import Ecto.Changeset - - @impl Backpex.ResourceAction - def title, do: "Invite user" - - @impl Backpex.ResourceAction - def label, do: "Invite" - - @impl Backpex.ResourceAction - def fields do - [ - email: %{ - module: Backpex.Fields.Text, - label: "Email", - type: :string - } - ] - end - - @impl Backpex.ResourceAction - def changeset(change, attrs) do - change - |> cast(attrs, [:email]) - |> validate_required([:email])) - end - - @impl Backpex.ResourceAction - def handle(_socket, params) do - # Send mail - - # Success - {:ok, "An email to #{params[:email]} was sent successfully."} - - # Failure - {:error, "An error occurred while sending an email to #{params[:email]}!"} - end - end - > #### `use Backpex.ResourceAction` {: .info} > > When you `use Backpex.ResourceAction`, the `Backpex.ResourceAction` module will set `@behavior Backpex.ResourceAction`. diff --git a/mix.exs b/mix.exs index 24664b5e..cf7f5d15 100644 --- a/mix.exs +++ b/mix.exs @@ -85,11 +85,17 @@ defmodule Backpex.MixProject do defp extras do [ {"README.md", title: "Introduction"}, + + # About "guides/about/what-is-backpex.md", "guides/about/why-we-built-backpex.md", "guides/about/contribute-to-backpex.md", + + # Get Started "guides/get_started/prerequisites.md", "guides/get_started/installation.md", + + # Live Resource "guides/live_resource/what-is-a-live-resource.md", "guides/live_resource/templates.md", "guides/live_resource/item-query.md", @@ -98,25 +104,46 @@ defmodule Backpex.MixProject do "guides/live_resource/navigation.md", "guides/live_resource/panels.md", "guides/live_resource/additional-classes-for-index-table-rows.md", + + # Fields "guides/fields/what-is-a-field.md", - "guides/fields/custom-field.md", + "guides/fields/custom-fields.md", "guides/fields/alignment.md", "guides/fields/visibility.md", "guides/fields/defaults.md", "guides/fields/readonly.md", + "guides/fields/custom-alias.md", + "guides/fields/placeholder.md", + "guides/fields/debounce-and-throttle.md", "guides/fields/index-edit.md", + "guides/fields/error-customization.md", + "guides/fields/computed-fields.md", + + # Filter "guides/filter/how-to-add-a-filter.md", "guides/filter/boolean-filter.md", "guides/filter/select-filter.md", "guides/filter/multi-select-filter.md", "guides/filter/range-filter.md", + "guides/filter/filter-presets.md", + "guides/filter/custom-filter.md", + + # Actions "guides/actions/item-actions.md", "guides/actions/resource-actions.md", + + # Authorization "guides/authorization/live-resource-authorization.md", "guides/authorization/field-authorization.md", + + # Searching "guides/searching/basic-search.md", "guides/searching/full-text-search.md", + + # Translations "guides/translations/translations.md", + + # Upgrade Guides "guides/upgrading/v0.2.md" ] end From 004dabd19e442ee2bfa11b6ce3aabcefb25d5aeb Mon Sep 17 00:00:00 2001 From: Florian Arens <60519307+Flo0807@users.noreply.github.com> Date: Thu, 13 Jun 2024 10:41:12 +0200 Subject: [PATCH 06/28] Update docs source_ref and source_url --- mix.exs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mix.exs b/mix.exs index e7c47252..ac179fa5 100644 --- a/mix.exs +++ b/mix.exs @@ -77,8 +77,8 @@ defmodule Backpex.MixProject do groups_for_functions: [ Components: &(&1[:type] == :component) ], - source_ref: "develop", - source_url_pattern: "#{@source_url}/blob/develop/%{path}#L%{line}" + source_ref: @version, + source_url: @source_url ] end From 70560b60dc5e29ef5da96e33fe5d80b87dc9d2bf Mon Sep 17 00:00:00 2001 From: Florian Arens <60519307+Flo0807@users.noreply.github.com> Date: Thu, 13 Jun 2024 10:51:22 +0200 Subject: [PATCH 07/28] Install deps during release step in ci --- .github/workflows/ci.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6af38345..5dd9c310 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -117,6 +117,12 @@ jobs: otp-version: ${{ steps.otp_version.outputs.version }} elixir-version: ${{ steps.elixir_version.outputs.version }} + - name: Install dependencies + run: | + mix local.hex --force --if-missing + mix local.rebar --force --if-missing + mix deps.get + - name: Publish package on hex.pm env: HEX_API_KEY: ${{ secrets.HEX_API_KEY }} From 5d54474cab5440074a4aad26c5c7df3e6f221e56 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 13 Jun 2024 17:16:51 +0000 Subject: [PATCH 08/28] Bump ecto_sql from 3.11.2 to 3.11.3 in /demo Bumps [ecto_sql](https://github.com/elixir-ecto/ecto_sql) from 3.11.2 to 3.11.3. - [Changelog](https://github.com/elixir-ecto/ecto_sql/blob/master/CHANGELOG.md) - [Commits](https://github.com/elixir-ecto/ecto_sql/compare/v3.11.2...v3.11.3) --- updated-dependencies: - dependency-name: ecto_sql dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- demo/mix.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/demo/mix.lock b/demo/mix.lock index 82ebc2a9..1e06bb67 100644 --- a/demo/mix.lock +++ b/demo/mix.lock @@ -11,7 +11,7 @@ "earmark_parser": {:hex, :earmark_parser, "1.4.39", "424642f8335b05bb9eb611aa1564c148a8ee35c9c8a8bba6e129d51a3e3c6769", [:mix], [], "hexpm", "06553a88d1f1846da9ef066b87b57c6f605552cfbe40d20bd8d59cc6bde41944"}, "ecto": {:hex, :ecto, "3.11.2", "e1d26be989db350a633667c5cda9c3d115ae779b66da567c68c80cfb26a8c9ee", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "3c38bca2c6f8d8023f2145326cc8a80100c3ffe4dcbd9842ff867f7fc6156c65"}, "ecto_psql_extras": {:hex, :ecto_psql_extras, "0.8.0", "440719cd74f09b3f01c84455707a2c3972b725c513808e68eb6c5b0ab82bf523", [:mix], [{:ecto_sql, "~> 3.7", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.16.0 or ~> 0.17.0 or ~> 0.18.0", [hex: :postgrex, repo: "hexpm", optional: false]}, {:table_rex, "~> 3.1.1 or ~> 4.0.0", [hex: :table_rex, repo: "hexpm", optional: false]}], "hexpm", "f1512812dc196bcb932a96c82e55f69b543dc125e9d39f5e3631a9c4ec65ef12"}, - "ecto_sql": {:hex, :ecto_sql, "3.11.2", "c7cc7f812af571e50b80294dc2e535821b3b795ce8008d07aa5f336591a185a8", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.11.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "73c07f995ac17dbf89d3cfaaf688fcefabcd18b7b004ac63b0dc4ef39499ed6b"}, + "ecto_sql": {:hex, :ecto_sql, "3.11.3", "4eb7348ff8101fbc4e6bbc5a4404a24fecbe73a3372d16569526b0cf34ebc195", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.11.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "e5f36e3d736b99c7fee3e631333b8394ade4bafe9d96d35669fca2d81c2be928"}, "esbuild": {:hex, :esbuild, "0.8.1", "0cbf919f0eccb136d2eeef0df49c4acf55336de864e63594adcea3814f3edf41", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "25fc876a67c13cb0a776e7b5d7974851556baeda2085296c14ab48555ea7560f"}, "ex_doc": {:hex, :ex_doc, "0.34.0", "ab95e0775db3df71d30cf8d78728dd9261c355c81382bcd4cefdc74610bef13e", [:mix], [{:earmark_parser, "~> 1.4.39", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "60734fb4c1353f270c3286df4a0d51e65a2c1d9fba66af3940847cc65a8066d7"}, "ex_machina": {:hex, :ex_machina, "2.7.0", "b792cc3127fd0680fecdb6299235b4727a4944a09ff0fa904cc639272cd92dc7", [:mix], [{:ecto, "~> 2.2 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_sql, "~> 3.0", [hex: :ecto_sql, repo: "hexpm", optional: true]}], "hexpm", "419aa7a39bde11894c87a615c4ecaa52d8f107bbdd81d810465186f783245bf8"}, From bc96f4d59d5e457999191f9360b78f9d44c12bdc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 13 Jun 2024 17:21:31 +0000 Subject: [PATCH 09/28] Bump ecto_sql from 3.11.2 to 3.11.3 Bumps [ecto_sql](https://github.com/elixir-ecto/ecto_sql) from 3.11.2 to 3.11.3. - [Changelog](https://github.com/elixir-ecto/ecto_sql/blob/master/CHANGELOG.md) - [Commits](https://github.com/elixir-ecto/ecto_sql/compare/v3.11.2...v3.11.3) --- updated-dependencies: - dependency-name: ecto_sql dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- mix.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.lock b/mix.lock index fd011112..beeef9a7 100644 --- a/mix.lock +++ b/mix.lock @@ -6,7 +6,7 @@ "decimal": {:hex, :decimal, "2.1.1", "5611dca5d4b2c3dd497dec8f68751f1f1a54755e8ed2a966c2633cf885973ad6", [:mix], [], "hexpm", "53cfe5f497ed0e7771ae1a475575603d77425099ba5faef9394932b35020ffcc"}, "earmark_parser": {:hex, :earmark_parser, "1.4.39", "424642f8335b05bb9eb611aa1564c148a8ee35c9c8a8bba6e129d51a3e3c6769", [:mix], [], "hexpm", "06553a88d1f1846da9ef066b87b57c6f605552cfbe40d20bd8d59cc6bde41944"}, "ecto": {:hex, :ecto, "3.11.2", "e1d26be989db350a633667c5cda9c3d115ae779b66da567c68c80cfb26a8c9ee", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "3c38bca2c6f8d8023f2145326cc8a80100c3ffe4dcbd9842ff867f7fc6156c65"}, - "ecto_sql": {:hex, :ecto_sql, "3.11.2", "c7cc7f812af571e50b80294dc2e535821b3b795ce8008d07aa5f336591a185a8", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.11.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "73c07f995ac17dbf89d3cfaaf688fcefabcd18b7b004ac63b0dc4ef39499ed6b"}, + "ecto_sql": {:hex, :ecto_sql, "3.11.3", "4eb7348ff8101fbc4e6bbc5a4404a24fecbe73a3372d16569526b0cf34ebc195", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.11.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "e5f36e3d736b99c7fee3e631333b8394ade4bafe9d96d35669fca2d81c2be928"}, "ex_doc": {:hex, :ex_doc, "0.34.0", "ab95e0775db3df71d30cf8d78728dd9261c355c81382bcd4cefdc74610bef13e", [:mix], [{:earmark_parser, "~> 1.4.39", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "60734fb4c1353f270c3286df4a0d51e65a2c1d9fba66af3940847cc65a8066d7"}, "expo": {:hex, :expo, "0.5.1", "249e826a897cac48f591deba863b26c16682b43711dd15ee86b92f25eafd96d9", [:mix], [], "hexpm", "68a4233b0658a3d12ee00d27d37d856b1ba48607e7ce20fd376958d0ba6ce92b"}, "file_system": {:hex, :file_system, "1.0.0", "b689cc7dcee665f774de94b5a832e578bd7963c8e637ef940cd44327db7de2cd", [:mix], [], "hexpm", "6752092d66aec5a10e662aefeed8ddb9531d79db0bc145bb8c40325ca1d8536d"}, From 3ca56b3bd742d18a7d295435b3bf68de4b245c89 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 14 Jun 2024 09:07:54 +0000 Subject: [PATCH 10/28] Bump braces from 3.0.2 to 3.0.3 in /demo Bumps [braces](https://github.com/micromatch/braces) from 3.0.2 to 3.0.3. - [Changelog](https://github.com/micromatch/braces/blob/master/CHANGELOG.md) - [Commits](https://github.com/micromatch/braces/compare/3.0.2...3.0.3) --- updated-dependencies: - dependency-name: braces dependency-type: indirect ... Signed-off-by: dependabot[bot] --- demo/yarn.lock | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/demo/yarn.lock b/demo/yarn.lock index 91e1c92e..b42d9a75 100644 --- a/demo/yarn.lock +++ b/demo/yarn.lock @@ -566,11 +566,11 @@ brace-expansion@^1.1.7: concat-map "0.0.1" braces@^3.0.2, braces@~3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" - integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== + version "3.0.3" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" + integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== dependencies: - fill-range "^7.0.1" + fill-range "^7.1.1" builtins@^5.0.1: version "5.0.1" @@ -1247,10 +1247,10 @@ file-entry-cache@^7.0.0: dependencies: flat-cache "^3.1.1" -fill-range@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" - integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== +fill-range@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" + integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== dependencies: to-regex-range "^5.0.1" From 6c39cc954e87437b71294542f475bb2233517525 Mon Sep 17 00:00:00 2001 From: Florian Arens <60519307+Flo0807@users.noreply.github.com> Date: Fri, 14 Jun 2024 13:06:39 +0200 Subject: [PATCH 11/28] Update readme --- README.md | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 2fa94232..886fc4a7 100644 --- a/README.md +++ b/README.md @@ -15,9 +15,14 @@ Welcome! Backpex is a highly customizable administration panel for Phoenix LiveV Visit our Live Demo → +## Installation & Documentation + +See our comprehensive [installation guide](guides/get_started/installation.md) for more information on how to install and configure Backpex in your Phoenix application. + +We also provide a detailed [documentation](https://hexdocs.pm/backpex) with guides on how to use Backpex and how to customize it to your needs. + ## Learn More -- [Installation](guides/get_started/installation.md) - [What is Backpex?](guides/about_backpex/what-is-backpex.md) - [Why we built Backpex?](guides/about_backpex/why-we-built-backpex.md) - [Contribute to Backpex](guides/about_backpex/contribute-to-backpex.md) @@ -32,6 +37,6 @@ Welcome! Backpex is a highly customizable administration panel for Phoenix LiveV - **Associations**: Handle HasOne, BelongsTo and HasMany(Through) associations with minimal configuration. Customize available options and rendered columns. - **Metrics**: Easily add value metrics (like sums or averages) to your resources for a quick glance at your date. More metric types are in the making. -## Installation +## License -See our comprehensive [installation guide](guides/get_started/installation.md) for more information on how to install and configure Backpex in your Phoenix application. +Backpex source code is licensed under the [MIT License](https://github.com/naymspace/backpex/blob/main/LICENSE.md). \ No newline at end of file From 5da73044b3a5c8eb37bfa911c6413ad8a2652eed Mon Sep 17 00:00:00 2001 From: Florian Arens <60519307+Flo0807@users.noreply.github.com> Date: Fri, 14 Jun 2024 13:06:47 +0200 Subject: [PATCH 12/28] Update guides --- guides/fields/custom-fields.md | 46 ++++++- guides/filter/boolean-filter.md | 1 - guides/filter/custom-filter.md | 68 +++++++++- guides/filter/filter-presets.md | 45 ++++++- guides/filter/how-to-add-a-filter.md | 75 ++++++++++- guides/filter/multi-select-filter.md | 1 - guides/filter/range-filter.md | 1 - guides/filter/select-filter.md | 1 - guides/filter/visibility-and-authorization.md | 21 +++ guides/filter/what-is-a-filter.md | 20 +++ lib/backpex/field.ex | 40 +----- lib/backpex/filters/boolean.ex | 8 +- lib/backpex/filters/filter.ex | 124 +----------------- lib/backpex/filters/multi_select.ex | 6 +- lib/backpex/filters/range.ex | 6 +- lib/backpex/filters/select.ex | 3 +- mix.exs | 8 +- 17 files changed, 289 insertions(+), 185 deletions(-) delete mode 100644 guides/filter/boolean-filter.md delete mode 100644 guides/filter/multi-select-filter.md delete mode 100644 guides/filter/range-filter.md delete mode 100644 guides/filter/select-filter.md create mode 100644 guides/filter/visibility-and-authorization.md create mode 100644 guides/filter/what-is-a-filter.md diff --git a/guides/fields/custom-fields.md b/guides/fields/custom-fields.md index d2bdb2a1..435dcf80 100644 --- a/guides/fields/custom-fields.md +++ b/guides/fields/custom-fields.md @@ -1 +1,45 @@ -# Custom Fields \ No newline at end of file +# Custom Fields + +Backpex ships with a set of default fields that can be used to create content types. See [Built-in Field Types](what-is-a-field.md#built-in-field-types) for a complete list of the default fields. In addition to the default fields, you can create custom fields for more advanced use cases. + +When creating your own custom field, you can use the `field` macro from the `BackpexWeb` module. It automatically implements the `Backpex.Field` behavior and defines some aliases and imports. + +> #### Information {: .info} +> +> A field has to be a [LiveComponent](https://hexdocs.pm/phoenix_live_view/Phoenix.LiveComponent.html). + +## Creating a Custom Field + +The simplest version of a custom field would look like this: + +```elixir +use BackpexWeb, :field + +@impl Backpex.Field +def render_value(assigns) do +~H""" +

+ <%= HTML.pretty_value(@value) %> +

+""" +end + +@impl Backpex.Field +def render_form(assigns) do +~H""" +
+ + <:label> + + + + +
+""" +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. + +See `Backpex.Field` for more information on the available callback functions. For example, you can implement `render_index_form/1` to make the field editable in the index view. diff --git a/guides/filter/boolean-filter.md b/guides/filter/boolean-filter.md deleted file mode 100644 index 02caf67c..00000000 --- a/guides/filter/boolean-filter.md +++ /dev/null @@ -1 +0,0 @@ -# Boolean Filter \ No newline at end of file diff --git a/guides/filter/custom-filter.md b/guides/filter/custom-filter.md index 4b1d2667..815ff27d 100644 --- a/guides/filter/custom-filter.md +++ b/guides/filter/custom-filter.md @@ -1 +1,67 @@ -# Custom Filter \ No newline at end of file +# Custom Filter + +Backpex ships with a set of default filters that can be used to filter the data. In addition to the default filters, you can create custom filters for more advanced use cases. + +## Creating a Custom Filter + +You can create a custom filter by using the `filter` macro from the `BackpexWeb` module.. It automatically implements the `Backpex.Filter` behavior and defines some aliases and imports. + +When creating a custom filter, you need to implement the following callbacks: `query/3`, `render/1` and `render_form/1`. The `query/3` function is used to filter the data based on the filter values. It receives the query, the attribute and the values of the filter and should return the filtered query. The `render/1` function returns the markup that is used to display the filter on the index view. The `render_form/1` function returns the markup that is used to render the filter form on the index view. + +Here is an example of a custom select filter: + +```elixir +defmodule MyApp.Filters.CustomSelectFilter do + use BackpexWeb, :filter + + @impl Backpex.Filter + def label, do: "Event status" + + @impl Backpex.Filter + def render(assigns) do + assigns = assign(assigns, :label, option_value_to_label(options(), assigns.value)) + + ~H""" + <%= @label %> + """ + end + + @impl Backpex.Filter + def render_form(assigns) do + ~H""" + <.form_field + type="select" + selected={selected(@value)} + options={my_options()} + form={@form} + field={@field} + label="" + /> + """ + end + + @impl Backpex.Filter + def query(query, attribute, value) do + where(query, [x], field(x, ^attribute) == ^value) + end + + defp option_value_to_label(options, value) do + Enum.find_value(options, fn {option_label, option_value} -> + if option_value == value, do: option_label + end) + end + + defp my_options, do: [ + {"Select an option...", nil}, + {"Open", :open}, + {"Close", :close}, + ] + + defp selected(""), do: nil + defp selected(value), do: value +end +``` + +In this example, we define a custom select filter that filters the data based on the event status. The `query/3` function filters the data based on the selected value. + +See `Backpex.Filter` for available callback functions. diff --git a/guides/filter/filter-presets.md b/guides/filter/filter-presets.md index a1760d8c..ea089f56 100644 --- a/guides/filter/filter-presets.md +++ b/guides/filter/filter-presets.md @@ -1 +1,44 @@ -# Filter Presets \ No newline at end of file +# Filter Presets + +Backpex allows you to define filter presets for your filters. Filter presets are used to define a set of filter configurations that can easily be applied to the data being displayed in the index view. Filter Presets consist of a name and a list of values that are used to filter the data. For example, you can define a filter preset for a date filter that filters the data based on the current month. Then the user can easily apply this filter by selecting the preset from the filter dropdown. + +## Defining a Filter Preset + +To define presets for your filters, you need to add a list of maps under the key of `:presets` to your filter in your LiveResource. + +Each of those maps has two keys: +1. `:label` – the name of the preset shown to the user +2. `:values` – a function with arity 0 that returns the values corresponding to your used filter + +See the example below for some preset examples. We add a preset for a date filter that filters the data based on the last 7 days and a preset for a select filter that filters the data based on the published status of an event. + +```elixir +@impl Backpex.LiveResource +def filters, do: [ + begins_at: %{ + module: MyAppWeb.Filters.DateRange, + label: "Begins At", + presets: [ + %{ + label: "Last 7 Days", + values: fn -> %{ + "start" => Date.add(Date.utc_today(), -7), + "end" => Date.utc_today() + } end + } + }, + published: %{ + module: MyAppWeb.Filters.EventPublished, + presets: [ + %{ + label: "Both", + values: fn -> [:published, :not_published] end + }, + %{ + label: "Only published", + values: fn -> [:published] end + } + ] + } +] +``` \ No newline at end of file diff --git a/guides/filter/how-to-add-a-filter.md b/guides/filter/how-to-add-a-filter.md index 660a52bb..eb3d1a79 100644 --- a/guides/filter/how-to-add-a-filter.md +++ b/guides/filter/how-to-add-a-filter.md @@ -1 +1,74 @@ -# How to add a Filter? \ No newline at end of file +# How to add a Filter? + +Adding a filter to your *LiveResource* is a two step process: + +## Defining a Filter module + +First, you need to define a filter module that implements one of the behaviors from the `Backpex.Filters` namespace., This may be one of the [built-in filters](what-is-a-filter.md#built-in-filters) or a [custom filter](custom-filter.md). + +We suggest to use a `MyAppWeb.Filters.` convention. + +If you want to add one of the built-in filters, you can click on the filter type in the list of [Built-in Filters](what-is-a-filter.md#built-in-filters) to see how to define a filter module for that filter type. + +For example, the following example shows how to define a filter module for a select filter that filters posts based on a category: + +```elixir +defmodule MyAppWeb.Filters.PostCategorySelect do + use Backpex.Filters.Select + + alias MyApp.Category + alias MyApp.Post + alias MyApp.Repo + + @impl Backpex.Filter + def label, do: "Category" + + @impl Backpex.Filters.Select + def prompt, do: "Select category ..." + + @impl Backpex.Filters.Select + def options do + query = + from p in Post, + join: c in Category, + on: p.category_id == c.id, + distinct: c.name, + select: {c.name, c.id} + + Repo.all(query) + end +end +``` + +## Adding the Filter to your LiveResource + +After you have defined the filter module, you need to add the filter to your *LiveResource*. + +To do this, you need to define the filter in the [filters/0](Backpex.LiveResource.html#c:filters/0) callback in your *LiveResource* module. + +Here is an example of how to add a filter to your *LiveResource*: + +```elixir +@impl Backpex.LiveResource +def filters, do: [ + category_id: %{ + module: MyAppWeb.Filters.PostCategorySelect, + } +] +``` + +In this example, we add a filter with the name `category_id` to the *LiveResource*. The filter uses the `MyAppWeb.Filters.PostCategorySelect` module we defined earlier. + +## Overwriting the Filter Label + +You can also overwrite the filter label defined in the filter label by adding a `label` key to the filter map: + +```elixir +@impl Backpex.LiveResource +def filters, do: [ + category_id: %{ + module: MyAppWeb.Filters.PostCategorySelect, + label: "Category" + } +] +``` diff --git a/guides/filter/multi-select-filter.md b/guides/filter/multi-select-filter.md deleted file mode 100644 index 9bf39dbd..00000000 --- a/guides/filter/multi-select-filter.md +++ /dev/null @@ -1 +0,0 @@ -# Multi Select Filter \ No newline at end of file diff --git a/guides/filter/range-filter.md b/guides/filter/range-filter.md deleted file mode 100644 index 8cf856dd..00000000 --- a/guides/filter/range-filter.md +++ /dev/null @@ -1 +0,0 @@ -# Range Filter \ No newline at end of file diff --git a/guides/filter/select-filter.md b/guides/filter/select-filter.md deleted file mode 100644 index 7450c7ad..00000000 --- a/guides/filter/select-filter.md +++ /dev/null @@ -1 +0,0 @@ -# Select Filter \ No newline at end of file diff --git a/guides/filter/visibility-and-authorization.md b/guides/filter/visibility-and-authorization.md new file mode 100644 index 00000000..6e6f4453 --- /dev/null +++ b/guides/filter/visibility-and-authorization.md @@ -0,0 +1,21 @@ +# Visibility and Authorization + +You can control whether a filter is visible or not by implementing the [can?/1](Backpex.Filter.html#c:can?/1) callback in your filter module. + +## Configuration + +The [can?/1](Backpex.Filter.html#c:can?/1) callback receives the assigns and has to return a boolean value. If the callback returns `true`, the filter will be visible. If it returns `false`, the filter will be hidden. If you don't implement the `can?/1` callback, the filter will be visible by default. + +Here is an example of how to hide a filter based on the user's role: + +```elixir +defmodule MyAppWeb.Filters.MyFilter do + use BackpexWeb, :filter + + @impl Backpex.Filter + def can?(assigns), do: assigns.current_user.role == :admin +end +``` + +In this example, the `MyFilter` filter will only be visible if the user's role is `admin`. + diff --git a/guides/filter/what-is-a-filter.md b/guides/filter/what-is-a-filter.md new file mode 100644 index 00000000..bdcec732 --- /dev/null +++ b/guides/filter/what-is-a-filter.md @@ -0,0 +1,20 @@ +# What is a Filter? + +Filters are used to filter the data that is being displayed in the (index) view. They are used to narrow down the data that is being displayed based on certain criteria. + +Backpex allows you to define filters for your *LiveResources*. Backpex handles the filtering of the data for you as well as the UI for the filters. You just need to define the filters and Backpex will take care of the rest. + +Backpex ships with a set of default filters that can be used to filter the data. In addition to the default filters, you can create custom filters for more advanced use cases. + +## Built-in Filters + +Backpex provides the following built-in filters: + +- `Backpex.Filters.Boolean` +- `Backpex.Filters.Range` +- `Backpex.Filters.Select` +- `Backpex.Filters.MultiSelect` + +You can click on each filter type to see its documentation and configuration options. + +We will go through how to define filters for a *LiveResource* in [the next section](how-to-add-a-filter.md). diff --git a/lib/backpex/field.ex b/lib/backpex/field.ex index 944c3a5b..5440dba5 100644 --- a/lib/backpex/field.ex +++ b/lib/backpex/field.ex @@ -3,45 +3,7 @@ defmodule Backpex.Field do @moduledoc ~S''' 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). - - 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""" -

- <%= HTML.pretty_value(@value) %> -

- """ - end - - @impl Backpex.Field - def render_form(assigns) do - ~H""" -
- - <:label> - - - - -
- """ - 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. + 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). ### Example diff --git a/lib/backpex/filters/boolean.ex b/lib/backpex/filters/boolean.ex index d1fa6ac5..bc7a3dfa 100644 --- a/lib/backpex/filters/boolean.ex +++ b/lib/backpex/filters/boolean.ex @@ -1,13 +1,15 @@ defmodule Backpex.Filters.Boolean do @moduledoc """ - The Boolean Filter renders one checkbox per given option, hence multiple options can apply at the same time. + The boolean filter renders one checkbox per given option, hence multiple options can apply at the same time. Instead of implementing a `query` callback, you need to define predicates for each option leveraging [`Ecto.Query.dynamic/2`](https://hexdocs.pm/ecto/Ecto.Query.html#dynamic/2). - > Please note that only query elements will work as a predicate that also work in an [`Ecto.Query.where/3`](https://hexdocs.pm/ecto/Ecto.Query.html#where/3). + > #### Warning {: .warning} + > + > Note that only query elements will work as a predicate that also work in an [`Ecto.Query.where/3`](https://hexdocs.pm/ecto/Ecto.Query.html#where/3). If none is selected, the filter does not change the query. If multiple options are selected they are logically reduced via `orWhere`. - A really basic example is the following filter for a boolean column `:published`: + See the following example for an implementation of a boolean filter for a published field. defmodule MyAppWeb.Filters.EventPublished do use Backpex.Filters.Boolean diff --git a/lib/backpex/filters/filter.ex b/lib/backpex/filters/filter.ex index fc9f5b33..03517865 100644 --- a/lib/backpex/filters/filter.ex +++ b/lib/backpex/filters/filter.ex @@ -1,128 +1,8 @@ defmodule Backpex.Filter do - @moduledoc ~S''' + @moduledoc """ The base behaviour for all filters. Injects also basic layout, form and delete button for a filters rendering. + """ - Enabling filters on a resources index view is a two step process: - - 1. Add modules implementing one of the behaviors from the `Backpex.Filters` namespace to your project. - - We suggest to use a `MyAppWeb.Filters.` convention. - - We implemented the `__using__` macro for the filters, injecting code like imports (e.g. `Ecto.Query`) and functions (e.g. `render/1`) into your module. - 2. Implement the `filters/0` callback on your resource. - - The latter just returns a keyword list with the field names as keys and the filter module as values. - - # in your resource configuration file - @impl Backpex.LiveResource - def filters, do: [ - status: %{ - module: MyAppWeb.Filters.EventStatusSelect - }, - begins_at: %{ - module: MyAppWeb.Filters.DateRange - }, - published: %{ - module: MyAppWeb.Filters.EventPublished - } - ] - - ## Custom filters - - Instead of using the pre-defined filters you can also define custom filters by using `Backpex.Filter` and implementing at least `query/3`, `render/1` and `render_form/1`. - - For example purposes let's define a custom select filter: - - defmodule MyApp.Filters.CustomSelectFilter do - use BackpexWeb, :filter - - @impl Backpex.Filter - def label, do: "Event status" - - @impl Backpex.Filter - def render(assigns) do - assigns = assign(assigns, :label, option_value_to_label(options(), assigns.value)) - - ~H""" - <%= @label %> - """ - end - - @impl Backpex.Filter - def render_form(assigns) do - ~H""" - <.form_field - type="select" - selected={selected(@value)} - options={my_options()} - form={@form} - field={@field} - label="" - /> - """ - end - - @impl Backpex.Filter - def query(query, attribute, value) do - where(query, [x], field(x, ^attribute) == ^value) - end - - defp option_value_to_label(options, value) do - Enum.find_value(options, fn {option_label, option_value} -> - if option_value == value, do: option_label - end) - end - - defp my_options, do: [ - {"Select an option...", nil}, - {"Open", :open}, - {"Close", :close}, - ] - - defp selected(""), do: nil - defp selected(value), do: value - end - - ## Filter presets - - To define presets for your filters, you need to add a list of maps under the key of `:presets` to your filter in your LiveResource. - - Each of those maps has two keys: - 1. `:label` – simply the String shown to the user - 2. `:values` – a function with arity 0 that returns the values corresponding to your used filter - - > See the example below for `:values` return values for the default range and boolean filter - - - @impl Backpex.LiveResource - def filters, do: [ - begins_at: %{ - module: MyAppWeb.Filters.DateRange, - label: "Begins At", - presets: [ - %{ - label: "Last 7 Days", - values: fn -> %{ - "start" => Date.add(Date.utc_today(), -7), - "end" => Date.utc_today() - } - end - } - }, - published: %{ - module: MyAppWeb.Filters.EventPublished, - presets: [ - %{ - label: "Both", - values: fn -> [:published, :not_published] end - }, - %{ - label: "Only published", - values: fn -> [:published] end - } - ] - } - ] - - ''' @doc """ Defines whether the filter can be used or not. """ diff --git a/lib/backpex/filters/multi_select.ex b/lib/backpex/filters/multi_select.ex index 5bc2cf09..9727f4e5 100644 --- a/lib/backpex/filters/multi_select.ex +++ b/lib/backpex/filters/multi_select.ex @@ -1,7 +1,6 @@ defmodule Backpex.Filters.MultiSelect do @moduledoc """ - The multi select filter behaviour. Renders a multi select box for the implemented `options/0` callback. - `prompt/0` defines the label for the multi select box. + The multi select filter renders checkboxes for a given list of options, hence allowing the user to select multiple values. See the following example for an implementation of a multi select user filter. @@ -23,8 +22,7 @@ defmodule Backpex.Filters.MultiSelect do > #### `use Backpex.Filters.MultiSelect` {: .info} > - > When you `use Backpex.Filters.MultiSelect`, the `Backpex.Filters.MultiSelect` module will set `@behavior Backpex.Filters.Select`. - > In addition it will add a `render` and `render_form` function in order to display the corresponding filter. + > When you `use Backpex.Filters.MultiSelect`, the `Backpex.Filters.MultiSelect` module will set `@behavior Backpex.Filters.Select`. In addition it will add a `render` and `render_form` function in order to display the corresponding filter. """ use BackpexWeb, :filter diff --git a/lib/backpex/filters/range.ex b/lib/backpex/filters/range.ex index 168a8cc1..3479c5ee 100644 --- a/lib/backpex/filters/range.ex +++ b/lib/backpex/filters/range.ex @@ -1,8 +1,8 @@ defmodule Backpex.Filters.Range do @moduledoc """ - Range filters render two input fields of the same type. Backpex offers the `:date`, `:datetime` and the `number` type. + The range filter renders two input fields of the same type. Backpex offers the `:date`, `:datetime` and the `number` type. - A basic implementation of a date range filter would look like this: + See the following example for an implementation of a date range filter. defmodule MyAppWeb.Filters.DateRange do use Backpex.Filters.Range @@ -14,6 +14,8 @@ defmodule Backpex.Filters.Range do def label, do: "Date Range (begins at)" end + > #### Information {: .info} + > > Note that the query function is already implemented via `Backpex.Filters.Range`. > #### `use Backpex.Filters.Range` {: .info} diff --git a/lib/backpex/filters/select.ex b/lib/backpex/filters/select.ex index 234e3241..d0d7aa78 100644 --- a/lib/backpex/filters/select.ex +++ b/lib/backpex/filters/select.ex @@ -1,7 +1,6 @@ defmodule Backpex.Filters.Select do @moduledoc """ - The select filter behaviour. Renders a select box for the implemented `options/0` and `prompt/0` callbacks. - `prompt/0` defines the key for the `nil` value added as first option. + Tbe select filter renders a select box for the implemented `options/0` and `prompt/0` callbacks. The `prompt/0` callback defines the key for the `nil` value added as first option. See the following example for an implementation of an event status filter. diff --git a/mix.exs b/mix.exs index cf7f5d15..2ab5d11b 100644 --- a/mix.exs +++ b/mix.exs @@ -120,13 +120,11 @@ defmodule Backpex.MixProject do "guides/fields/computed-fields.md", # Filter + "guides/filter/what-is-a-filter.md", "guides/filter/how-to-add-a-filter.md", - "guides/filter/boolean-filter.md", - "guides/filter/select-filter.md", - "guides/filter/multi-select-filter.md", - "guides/filter/range-filter.md", "guides/filter/filter-presets.md", "guides/filter/custom-filter.md", + "guides/filter/visibility-and-authorization.md", # Actions "guides/actions/item-actions.md", @@ -168,7 +166,7 @@ defmodule Backpex.MixProject do [ Components: ~r/Backpex\.HTML.?/, Fields: ~r/Backpex\.Field.?/, - "Item Actions": ~r/Backpex\.ItemAction.?/, + Actions: ~r/Backpex\.(ItemAction|ResourceAction).?/, Filters: ~r/Backpex\.Filter.?/, Metrics: ~r/Backpex\.Metric.?/ ] From 3f3e948a9158827b0e1946da744485edffea0748 Mon Sep 17 00:00:00 2001 From: Florian Arens <60519307+Flo0807@users.noreply.github.com> Date: Fri, 14 Jun 2024 13:08:06 +0200 Subject: [PATCH 13/28] Update readme --- README.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 886fc4a7..bbc84b20 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,16 @@ Welcome! Backpex is a highly customizable administration panel for Phoenix LiveV Visit our Live Demo → +## Key Features + +- **LiveResources**: Quickly create LiveResource modules for your database tables with fully customizable CRUD views. Bring your own layout or use our components. +- **Search and Filters**: Define searchable fields on your resources and add custom filters. Get instant results with the power of Phoenix LiveView. +- **Resource Actions**: Add your globally available custom actions (like user invitation or exports) with additional form fields to your LiveResources. +- **Authorization**: Handle authorization for all your CRUD and custom actions via simple pattern matching. Optionally integrate your own authorization library. +- **Field Types**: Many field types (e.g. Text, Number, Date, Upload) are supported out of the box. Easily create your own field type modules with custom logic. +- **Associations**: Handle HasOne, BelongsTo and HasMany(Through) associations with minimal configuration. Customize available options and rendered columns. +- **Metrics**: Easily add value metrics (like sums or averages) to your resources for a quick glance at your date. More metric types are in the making. + ## Installation & Documentation See our comprehensive [installation guide](guides/get_started/installation.md) for more information on how to install and configure Backpex in your Phoenix application. @@ -27,16 +37,6 @@ We also provide a detailed [documentation](https://hexdocs.pm/backpex) with guid - [Why we built Backpex?](guides/about_backpex/why-we-built-backpex.md) - [Contribute to Backpex](guides/about_backpex/contribute-to-backpex.md) -## Key Features - -- **LiveResources**: Quickly create LiveResource modules for your database tables with fully customizable CRUD views. Bring your own layout or use our components. -- **Search and Filters**: Define searchable fields on your resources and add custom filters. Get instant results with the power of Phoenix LiveView. -- **Resource Actions**: Add your globally available custom actions (like user invitation or exports) with additional form fields to your LiveResources. -- **Authorization**: Handle authorization for all your CRUD and custom actions via simple pattern matching. Optionally integrate your own authorization library. -- **Field Types**: Many field types (e.g. Text, Number, Date, Upload) are supported out of the box. Easily create your own field type modules with custom logic. -- **Associations**: Handle HasOne, BelongsTo and HasMany(Through) associations with minimal configuration. Customize available options and rendered columns. -- **Metrics**: Easily add value metrics (like sums or averages) to your resources for a quick glance at your date. More metric types are in the making. - ## License Backpex source code is licensed under the [MIT License](https://github.com/naymspace/backpex/blob/main/LICENSE.md). \ No newline at end of file From 5b0b54a593c329dc1875992a6280ead4acac0d39 Mon Sep 17 00:00:00 2001 From: Florian Arens <60519307+Flo0807@users.noreply.github.com> Date: Fri, 14 Jun 2024 15:40:54 +0200 Subject: [PATCH 14/28] Update guides --- guides/get_started/installation.md | 221 +++++++++++++++--- guides/get_started/prerequisites.md | 4 + guides/live_resource/fluid-layout.md | 18 ++ .../live_resource/listen-to-pubsub-events.md | 41 ++++ lib/backpex/live_resource.ex | 197 +--------------- mix.exs | 2 + 6 files changed, 257 insertions(+), 226 deletions(-) create mode 100644 guides/live_resource/fluid-layout.md create mode 100644 guides/live_resource/listen-to-pubsub-events.md diff --git a/guides/get_started/installation.md b/guides/get_started/installation.md index a4c9c63d..47e85a2f 100644 --- a/guides/get_started/installation.md +++ b/guides/get_started/installation.md @@ -2,7 +2,7 @@ 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. -## Add Backpex to list of dependencies +## Add to list of dependencies In your `mix.exs`: @@ -10,14 +10,16 @@ In your `mix.exs`: defp deps do [ ... - {:backpex, "~> 0.2.0"} + {:backpex, "~> 0.3.0"} ] end ``` -## Add Backpex files to tailwind content +See the [hex.pm page](https://hex.pm/packages/backpex) for the latest version. -Backpex uses Tailwind CSS and daisyUI. Make sure to add the Backpex files to your tailwind content to make sure the styles are applied. +## 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`: @@ -25,6 +27,7 @@ In your `tailwind.config.js`: .., content: [ ..., + // add this line 'deps/backpex/**/*.*ex' ] ``` @@ -44,58 +47,216 @@ Backpex ships with a formatter configuration. To use it, add Backpex to the list ] ``` -## Create layout +## 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. -Backpex does not ship a predefined layout by default. Instead it exposes components you -may use to build your own layout. You could also partly use Backpex components. For example -you are able to define your own sidebar navigation in our app shell component. +The example resource will be a `Post` resource with the following fields: -Place the layout file in your `lib/myapp_web/templates/layout` directory. +- `title` (string) +- `views` (integer) -You can find all Backpex components in the `lib/backpex/components` directory. +Run the following commands: + +```bash +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 -If you do not want to put effort into creating your own layout, feel free to use this layout (`admin.html.heex`) as a starting point: +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. -> The `Backpex.HTML.Layout.app_shell` component accepts a boolean `fluid` to determine if a `LiveResource` should be rendered full width. Our default is the definition of the `fluid?` option in a `LiveResource`, but feel free to change this behavior in your layout. +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: ```elixir <:topbar> - + + <:label> - + -
- <.link navigate="/" class="my-1 flex justify-between px-4 py-2 text-sm text-red-600 hover:bg-gray-100"> +
  • + <.link navigate={~p"/"} class="flex justify-between text-red-600 hover:bg-gray-100">

    Logout

    - + <.icon name="hero-arrow-right-on-rectangle" class="h-5 w-5" /> -
  • +
    <:sidebar> - - Your Resource + + <.icon name="hero-book-open" class="h-5 w-5" /> Posts - - <:label>Your Sidebar Section - - Your nested Resource - - <%= @inner_content %>
    ``` -You can now create and configure the corresponding resources. +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. -## Create test resource +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`](https://github.com/phoenixframework/phoenix/blob/main/priv/templates/phx.gen.live/core_components.ex). Feel free to use your own icon component or library. -## Setup routing +> #### Information {: .info} +> +> The `Backpex.HTML.Layout.app_shell/1` component accepts a boolean `fluid` to determine if a `LiveResource` should be rendered full width. There is a `fluid?` option you can configure in a `LiveResource`. See the [Fluid Layout documentation](live_resource/fluid-layout.md) for more information. ## Configure LiveResource -## Configure Routes +To create a LiveResource for the `Post` resource, you need to create LiveResource module. + +```elixir +defmodule MyAppWeb.Live.PostLive do + use Backpex.LiveResource, + layout: {MyAppWeb.Layouts, :admin}, + schema: MyApp.Blog.Post, + repo: MyApp.Repo, + update_changeset: &MyApp.Blog.Post.update_changeset/3, + create_changeset: &MyApp.Blog.Post.create_changeset/3, + pubsub: 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` and `create_changeset` options tell Backpex which changesets to use for updating and creating the resource. +- The `pubsub` option tells Backpex which pubsub to use for the resource (see the [Listen to PubSub Events](live_resource/listen-to-pubsub-events.md) guide for more information). +- The `topic` option tells Backpex which topic to use for the resource when broadcasting events. +- The `event_prefix` option tells Backpex which event prefix to use for the resource when broadcasting events. + +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`](Backpex.LiveResource.html#c:singular_name/0) - This function should return the singular name of the resource. +- [`plural_name/0`](Backpex.LiveResource.html#c:plural_name/0) - This function should return the plural name of the resource. +- [`fields/0`](Backpex.LiveResource.html#c: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: + +```elixir +defmodule MyAppWeb.Live.PostLive do + use Backpex.LiveResource, + layout: {MyAppWeb.Layouts, :admin}, + schema: MyApp.Blog.Post, + repo: MyApp.Repo, + update_changeset: &MyApp.Blog.Post.update_changeset/3, + create_changeset: &MyApp.Blog.Post.create_changeset/3, + pubsub: 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: "First Name" + } + ] +end +``` + +The `fields/0` function returns a list of fields to display in the LiveResource. See [What is a Field?](fields/what-is-a-field.md) for more information. + +> #### Information {: .info} +> +> 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 using `post_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`](Backpex.Router.html#backpex_routes/0) macro, you can skip this step. + +```elixir +# router.ex + +import Backpex.Router + +scope "/admin", DemoWeb do + pipe_through :browser + + # add this line + backpex_routes() +end +``` + +It does not matter where you place the [`backpex_routes/0`](Backpex.Router.html#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. + +```elixir +# router.ex + +import Backpex.Router + +scope "/admin", DemoWeb 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`](Backpex.Router.html#live_resources/3) macro. + +```elixir +# router.ex + +import Backpex.Router + +scope "/admin", DemoWeb 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`. diff --git a/guides/get_started/prerequisites.md b/guides/get_started/prerequisites.md index e2af32bd..6aa619c4 100644 --- a/guides/get_started/prerequisites.md +++ b/guides/get_started/prerequisites.md @@ -22,4 +22,8 @@ Backpex is styled using daisyUI. Make sure you have daisyUI installed in your ap Backpex currently depends on Ecto as the database layer. Make sure you have a running Ecto repository in your application. +> #### Warning {: .warning} +> +> Backpex currently only supports UUID (binary_id) primary keys. Help us to support other primary key types by contributing to the project. + If you meet all these prerequisites, you are ready to install and configure Backpex in your Phoenix application. See our [installation guide](guides/get_started/installation.md) for more information on how to install and configure Backpex. \ No newline at end of file diff --git a/guides/live_resource/fluid-layout.md b/guides/live_resource/fluid-layout.md new file mode 100644 index 00000000..a54c7670 --- /dev/null +++ b/guides/live_resource/fluid-layout.md @@ -0,0 +1,18 @@ +# Fluid Layout + +Backpex provides a way to create a fluid layout. A fluid layout is a layout that fills the entire width of the screen. This layout is useful for applications that need to display a lot of content on the screen. + +> #### Information {: .info} +> +> The `fluid?` options requires you to pass the `fluid?` assign to the `Backpex.HTML.Layout.app_shell/1` component in your layout file. See the [Create layout](get_started/installation.md#create-layout) documentation for more information. + +## Configure LiveResource + +To create a fluid layout, you need to set the `fluid?` option in a `LiveResource` to `true`. + +```elixir +# in your LiveResource module +defmodule MyAppWeb.Live.UserLive do + use Backpex.LiveResource, fluid?: true +end +``` \ No newline at end of file diff --git a/guides/live_resource/listen-to-pubsub-events.md b/guides/live_resource/listen-to-pubsub-events.md new file mode 100644 index 00000000..3fe91184 --- /dev/null +++ b/guides/live_resource/listen-to-pubsub-events.md @@ -0,0 +1,41 @@ +# Listen to PubSub Events + +As mentioned in the [installation guide](get_started/installation.md) you need to configure PubSub for your *LiveResources*. Backpex will use the configuration to publish `deleted`, `updated` and `created` events. Backpex will listen to these events and update the UI accordingly. Sometimes you may want to listen to these events and perform some custom actions. For example you want to show a toast to all users currently on the resource that a post has been created. + +### Configuration + +You can listen for Backpex PubSub events by implementing the Phoenix.LiveView [`handle_info/2`](Phoenix.LiveView.html#c:handle_info/2) callback in your *LiveResource* module. + +We assume you configured PubSub for your Posts *LiveResource* like this: + +```elixir +use Backpex.LiveResource, + ..., + pubsub: Demo.PubSub + topic: "posts", + event_prefix: "post_" +``` + +You could implement the following `handle_info/2` callbacks in your *LiveResource*: + +```elixir +# in your resource configuration file + +@impl Phoenix.LiveView +def handle_info({"post_created", item}, socket) do + # make something in response to the event + {:noreply, socket} +end + +@impl Phoenix.LiveView +def handle_info({"post_updated", item}, socket) do + # make something in response to the event + {:noreply, socket} +end + +@impl Phoenix.LiveView +def handle_info({"post_deleted", item}, socket) do + # make something in response to the event + {:noreply, socket} +end +``` diff --git a/lib/backpex/live_resource.ex b/lib/backpex/live_resource.ex index 82b4f703..5ffdb391 100644 --- a/lib/backpex/live_resource.ex +++ b/lib/backpex/live_resource.ex @@ -3,206 +3,11 @@ defmodule Backpex.LiveResource do @moduledoc ~S''' - 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" - }, - ] - 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 + A 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. > #### `use Backpex.LiveResource` {: .info} > > When you `use Backpex.LiveResource`, the `Backpex.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/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""" -
    - <%= @value %> -
    - """ - 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 - - ## 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 - - ## 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 - - ## Tooltips - - We support tooltips via [daisyUI](https://daisyui.com/components/tooltip/). ''' alias Backpex.Resource diff --git a/mix.exs b/mix.exs index 98151ce5..fc99a776 100644 --- a/mix.exs +++ b/mix.exs @@ -103,6 +103,8 @@ defmodule Backpex.MixProject do "guides/live_resource/hooks.md", "guides/live_resource/navigation.md", "guides/live_resource/panels.md", + "guides/live_resource/fluid-layout.md", + "guides/live_resource/listen-to-pubsub-events.md", "guides/live_resource/additional-classes-for-index-table-rows.md", # Fields From 941bcad8358abfe8c2462ca4c023f5f7d31cc35f Mon Sep 17 00:00:00 2001 From: Florian Arens <60519307+Flo0807@users.noreply.github.com> Date: Fri, 14 Jun 2024 15:55:30 +0200 Subject: [PATCH 15/28] Fix typo in upload docs --- lib/backpex/fields/upload.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/backpex/fields/upload.ex b/lib/backpex/fields/upload.ex index b4b4c723..e175c7ed 100644 --- a/lib/backpex/fields/upload.ex +++ b/lib/backpex/fields/upload.ex @@ -256,7 +256,7 @@ defmodule Backpex.Fields.Upload do def change do alter table(:products) do - dd(:images, {:array, :string}) + add(:images, {:array, :string}) end end end From 22531e24e321b1e024fad1124672ba68f00e40fe Mon Sep 17 00:00:00 2001 From: Florian Arens <60519307+Flo0807@users.noreply.github.com> Date: Fri, 14 Jun 2024 18:43:35 +0200 Subject: [PATCH 16/28] Fix form error hint is only shown after second submit --- lib/backpex/live_components/form_component.ex | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/lib/backpex/live_components/form_component.ex b/lib/backpex/live_components/form_component.ex index 5755a914..bb131695 100644 --- a/lib/backpex/live_components/form_component.ex +++ b/lib/backpex/live_components/form_component.ex @@ -18,6 +18,7 @@ defmodule Backpex.FormComponent do socket |> assign(assigns) |> assign_new(:action_type, fn -> nil end) + |> assign_new(:show_form_errors, fn -> false end) |> update_assigns() |> assign_form() @@ -26,14 +27,12 @@ defmodule Backpex.FormComponent do defp update_assigns(%{assigns: %{action_type: :item}} = socket) do socket - |> assign_new(:show_form_errors, fn -> false end) |> assign_fields() |> assign_changeset() end defp update_assigns(%{assigns: assigns} = socket) do socket - |> assign(:show_form_errors, assigns.live_action == :edit) |> apply_action(assigns.live_action) |> maybe_assign_uploads() end @@ -104,7 +103,10 @@ defmodule Backpex.FormComponent do send(self(), {:update_changeset, changeset}) - socket = assign(socket, :form, form) + socket = + socket + |> assign(:form, form) + |> assign(:show_form_errors, false) {:noreply, socket} end @@ -125,12 +127,17 @@ defmodule Backpex.FormComponent do send(self(), {:update_changeset, changeset}) - socket = assign(socket, :form, form) + socket = + socket + |> assign(:form, form) + |> assign(:show_form_errors, false) {:noreply, socket} end def handle_event("validate", _params, socket) do + socket = assign(socket, :show_form_errors, false) + {:noreply, socket} end @@ -230,6 +237,7 @@ defmodule Backpex.FormComponent do socket = socket + |> assign(:show_form_errors, false) |> clear_flash() |> put_flash(:info, info_msg) |> push_navigate(to: return_to) @@ -281,6 +289,7 @@ defmodule Backpex.FormComponent do socket = socket + |> assign(:show_form_errors, false) |> clear_flash() |> put_flash(:info, info_msg) |> push_navigate(to: return_to) @@ -323,6 +332,7 @@ defmodule Backpex.FormComponent do socket = socket + |> assign(:show_form_errors, false) |> put_flash_message(result) |> push_redirect(to: return_to) @@ -365,6 +375,7 @@ defmodule Backpex.FormComponent do {message, socket} = socket + |> assign(:show_form_errors, false) |> assign(selected_items: []) |> assign(select_all: false) |> action_to_confirm.module.handle(selected_items, params) From 239fffbdf0f4f93dbd32c377f55bfb2293020e43 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 14 Jun 2024 17:10:46 +0000 Subject: [PATCH 17/28] Bump bandit from 1.5.3 to 1.5.4 in /demo Bumps [bandit](https://github.com/mtrudel/bandit) from 1.5.3 to 1.5.4. - [Changelog](https://github.com/mtrudel/bandit/blob/main/CHANGELOG.md) - [Commits](https://github.com/mtrudel/bandit/compare/1.5.3...1.5.4) --- updated-dependencies: - dependency-name: bandit dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- demo/mix.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/demo/mix.lock b/demo/mix.lock index 1e06bb67..80f6f8fb 100644 --- a/demo/mix.lock +++ b/demo/mix.lock @@ -1,5 +1,5 @@ %{ - "bandit": {:hex, :bandit, "1.5.3", "c7ee44871da696371a5674dd2c2062e974a18cd787732efcf50cc70b98c78fdc", [:mix], [{:hpax, "~> 0.1.1", [hex: :hpax, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:thousand_island, "~> 1.0", [hex: :thousand_island, repo: "hexpm", optional: false]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "3812ed5e48c1f1e3109edb5c463c6f8aaf25ecfac2826606be3e5237550116ef"}, + "bandit": {:hex, :bandit, "1.5.4", "8e56e7cfc06f3c57995be0d9bf4e45b972d8732f5c7e96ef8ec0735f52079527", [:mix], [{:hpax, "~> 0.2.0", [hex: :hpax, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:thousand_island, "~> 1.0", [hex: :thousand_island, repo: "hexpm", optional: false]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "04c2b38874769af67fe7f10034f606ad6dda1d8f80c4d7a0c616b347584d5aff"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "castore": {:hex, :castore, "1.0.7", "b651241514e5f6956028147fe6637f7ac13802537e895a724f90bf3e36ddd1dd", [:mix], [], "hexpm", "da7785a4b0d2a021cd1292a60875a784b6caef71e76bf4917bdee1f390455cf5"}, "certifi": {:hex, :certifi, "2.12.0", "2d1cca2ec95f59643862af91f001478c9863c2ac9cb6e2f89780bfd8de987329", [:rebar3], [], "hexpm", "ee68d85df22e554040cdb4be100f33873ac6051387baf6a8f6ce82272340ff1c"}, @@ -23,7 +23,7 @@ "gettext": {:hex, :gettext, "0.24.0", "6f4d90ac5f3111673cbefc4ebee96fe5f37a114861ab8c7b7d5b30a1108ce6d8", [:mix], [{:expo, "~> 0.5.1", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "bdf75cdfcbe9e4622dd18e034b227d77dd17f0f133853a1c73b97b3d6c770e8b"}, "hackney": {:hex, :hackney, "1.20.1", "8d97aec62ddddd757d128bfd1df6c5861093419f8f7a4223823537bad5d064e2", [:rebar3], [{:certifi, "~>2.12.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~>6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~>1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.4.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~>1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "fe9094e5f1a2a2c0a7d10918fee36bfec0ec2a979994cff8cfe8058cd9af38e3"}, "heroicons": {:hex, :heroicons, "0.5.5", "c2bcb05a90f010df246a5a2a2b54cac15483b5de137b2ef0bead77fcdf06e21a", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:phoenix_live_view, ">= 0.18.2", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}], "hexpm", "2f4bf929440fecd5191ba9f40e5009b0f75dc993d765c0e4d068fcb7026d6da1"}, - "hpax": {:hex, :hpax, "0.1.2", "09a75600d9d8bbd064cdd741f21fc06fc1f4cf3d0fcc335e5aa19be1a7235c84", [:mix], [], "hexpm", "2c87843d5a23f5f16748ebe77969880e29809580efdaccd615cd3bed628a8c13"}, + "hpax": {:hex, :hpax, "0.2.0", "5a58219adcb75977b2edce5eb22051de9362f08236220c9e859a47111c194ff5", [:mix], [], "hexpm", "bea06558cdae85bed075e6c036993d43cd54d447f76d8190a8db0dc5893fa2f1"}, "idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"}, "jason": {:hex, :jason, "1.4.1", "af1504e35f629ddcdd6addb3513c3853991f694921b1b9368b0bd32beb9f1b63", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fbb01ecdfd565b56261302f7e1fcc27c4fb8f32d56eab74db621fc154604a7a1"}, "libcluster": {:hex, :libcluster, "3.3.3", "a4f17721a19004cfc4467268e17cff8b1f951befe428975dd4f6f7b84d927fe0", [:mix], [{:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "7c0a2275a0bb83c07acd17dab3c3bfb4897b145106750eeccc62d302e3bdfee5"}, From 61fec5d40e0488e2083bfd8a3191d4e4a383961f Mon Sep 17 00:00:00 2001 From: Simon Hansen Date: Sat, 15 Jun 2024 12:13:39 +0200 Subject: [PATCH 18/28] bump version --- guides/get_started/installation.md | 6 +++--- guides/upgrading/v0.3.md | 2 +- mix.exs | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/guides/get_started/installation.md b/guides/get_started/installation.md index 47e85a2f..d9332aae 100644 --- a/guides/get_started/installation.md +++ b/guides/get_started/installation.md @@ -10,7 +10,7 @@ In your `mix.exs`: defp deps do [ ... - {:backpex, "~> 0.3.0"} + {:backpex, "~> 0.3.1"} ] end ``` @@ -109,7 +109,7 @@ Place the layout file in your `lib/myapp_web/templates/layout` directory. You ca 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`](https://github.com/phoenixframework/phoenix/blob/main/priv/templates/phx.gen.live/core_components.ex). Feel free to use your own icon component or library. > #### Information {: .info} -> +> > The `Backpex.HTML.Layout.app_shell/1` component accepts a boolean `fluid` to determine if a `LiveResource` should be rendered full width. There is a `fluid?` option you can configure in a `LiveResource`. See the [Fluid Layout documentation](live_resource/fluid-layout.md) for more information. ## Configure LiveResource @@ -186,7 +186,7 @@ end The `fields/0` function returns a list of fields to display in the LiveResource. See [What is a Field?](fields/what-is-a-field.md) for more information. > #### Information {: .info} -> +> > 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 using `post_live.ex`. ## Configure Routing diff --git a/guides/upgrading/v0.3.md b/guides/upgrading/v0.3.md index 2faf39e0..4f273f1f 100644 --- a/guides/upgrading/v0.3.md +++ b/guides/upgrading/v0.3.md @@ -7,7 +7,7 @@ Update Backpex to the latest version: ```elixir defp deps do [ - {:backpex, "~> 0.3.0"} + {:backpex, "~> 0.3.1"} ] end ``` diff --git a/mix.exs b/mix.exs index fc99a776..dd1d1350 100644 --- a/mix.exs +++ b/mix.exs @@ -1,7 +1,7 @@ defmodule Backpex.MixProject do use Mix.Project - @version "0.3.0" + @version "0.3.1" @source_url "https://github.com/naymspace/backpex" def project do From 58108ca2433eadc90aa350187657300b2ecbabc1 Mon Sep 17 00:00:00 2001 From: Graham Preston Date: Sat, 15 Jun 2024 13:01:59 -0400 Subject: [PATCH 19/28] Fix Learn More links in README --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index bbc84b20..c312ea09 100644 --- a/README.md +++ b/README.md @@ -33,9 +33,9 @@ We also provide a detailed [documentation](https://hexdocs.pm/backpex) with guid ## Learn More -- [What is Backpex?](guides/about_backpex/what-is-backpex.md) -- [Why we built Backpex?](guides/about_backpex/why-we-built-backpex.md) -- [Contribute to Backpex](guides/about_backpex/contribute-to-backpex.md) +- [What is Backpex?](guides/about/what-is-backpex.md) +- [Why we built Backpex?](guides/about/why-we-built-backpex.md) +- [Contribute to Backpex](guides/about/contribute-to-backpex.md) ## License From 57d042ca86331fe13b13eafd5f23fe1bb11c7bf3 Mon Sep 17 00:00:00 2001 From: Florian Arens <60519307+Flo0807@users.noreply.github.com> Date: Sat, 15 Jun 2024 21:37:55 +0200 Subject: [PATCH 20/28] Add documentation pull requests to release notes --- .github/release.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/release.yml b/.github/release.yml index 74afca3b..10096479 100644 --- a/.github/release.yml +++ b/.github/release.yml @@ -15,6 +15,9 @@ changelog: - title: Bug fixes labels: - bug + - title: Documentation + labels: + - documentation - title: Dependency updates labels: - dependencies From ccf63f813a147106973b2827db4502e9e2a9be51 Mon Sep 17 00:00:00 2001 From: Florian Arens <60519307+Flo0807@users.noreply.github.com> Date: Sat, 15 Jun 2024 21:42:38 +0200 Subject: [PATCH 21/28] Rename label of a field in the installation guide --- guides/get_started/installation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guides/get_started/installation.md b/guides/get_started/installation.md index d9332aae..0547905b 100644 --- a/guides/get_started/installation.md +++ b/guides/get_started/installation.md @@ -177,7 +177,7 @@ defmodule MyAppWeb.Live.PostLive do }, views: %{ module: Backpex.Fields.Number, - label: "First Name" + label: "Views" } ] end From 063d47f7841cc1e1073e211d261187ac16fc8f72 Mon Sep 17 00:00:00 2001 From: Florian Arens <60519307+Flo0807@users.noreply.github.com> Date: Sat, 15 Jun 2024 21:46:49 +0200 Subject: [PATCH 22/28] Improve config example in translations guide --- guides/translations/translations.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/guides/translations/translations.md b/guides/translations/translations.md index bef9d05c..39bf0d17 100644 --- a/guides/translations/translations.md +++ b/guides/translations/translations.md @@ -7,9 +7,9 @@ You are able to translate all strings used by Backpex. This includes general str In order to translate strings, you need to configure two translator functions in your application config: ```elixir -config :backpex, :translator_function, {MyAppWeb.Helpers, :translate_backpex} - -config :backpex, :error_translator_function, {MyAppWeb.ErrorHelpers, :translate_error} +config :backpex, + translator_function: {MyAppWeb.CoreComponents, :translate_backpex}, + error_translator_function: {MyAppWeb.CoreComponents, :translate_error} ``` The first one is being used to translate general strings. The second one is being used to translate (changeset) errors. @@ -36,7 +36,7 @@ def translate_error({msg, opts}) do end ``` -You can place the functions in a module of your choice. In this example, we use `MyAppWeb.Helpers` and `MyAppWeb.ErrorHelpers`. Don't forget to use the correct module in your config as well. +You can place the functions in a module of your choice. In this example, we use `MyAppWeb.CoreComponents`. Don't forget to use the correct module in your config as well. In addition, you need to create a Gettext template file in your application. You may use the following template. It contains all translations used by Backpex. From 9bd6bc1aede3a8e61d181144ceb07dc7e06d54ae Mon Sep 17 00:00:00 2001 From: Florian Arens <60519307+Flo0807@users.noreply.github.com> Date: Sat, 15 Jun 2024 21:53:42 +0200 Subject: [PATCH 23/28] Fix images are not displayed in docs --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index c312ea09..bc7835a2 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ - + [![CI](https://github.com/naymspace/backpex/actions/workflows/ci.yml/badge.svg)](https://github.com/naymspace/backpex/actions/workflows/ci.yml) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/naymspace/backpex/blob/develop/LICENSE.md) @@ -9,7 +9,7 @@ Welcome! Backpex is a highly customizable administration panel for Phoenix LiveView applications. Quickly create beautiful CRUD views for your existing data using configurable *LiveResources*. Backpex integrates seamlessly with your existing Phoenix application and provides a simple way to manage your resources. It is highly customizable and can be extended with your own layouts, views, field types, filters and more. -![Backpex Screenshot](https://github.com/naymspace/backpex/blob/develop/priv/static/images/screenshot.png) +![Backpex Screenshot](https://github.com/naymspace/backpex/blob/develop/priv/static/images/screenshot.png?raw=true)
    Visit our Live Demo → From b5fa82e93fda160ce65316ab4270c363fcd0f328 Mon Sep 17 00:00:00 2001 From: Florian Arens <60519307+Flo0807@users.noreply.github.com> Date: Fri, 12 Apr 2024 15:01:16 +0200 Subject: [PATCH 24/28] Refactor sentry meta tag --- demo/lib/demo_web/components/core_components.ex | 13 ++++++------- demo/lib/demo_web/components/layouts/root.html.heex | 2 +- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/demo/lib/demo_web/components/core_components.ex b/demo/lib/demo_web/components/core_components.ex index e2b2f6e4..e175b6aa 100644 --- a/demo/lib/demo_web/components/core_components.ex +++ b/demo/lib/demo_web/components/core_components.ex @@ -4,8 +4,6 @@ defmodule DemoWeb.CoreComponents do """ use Phoenix.Component - import Phoenix.HTML.Tag - @doc """ Renders the analytics snippet. """ @@ -26,11 +24,12 @@ defmodule DemoWeb.CoreComponents do @doc """ Builds sentry meta tag. """ - def sentry_meta_tag do - case Application.get_env(:sentry, :dsn) do - nil -> nil - dsn -> tag(:meta, name: "sentry-dsn", content: dsn) - end + def sentry_meta_tag(assigns) do + assigns = assign(assigns, :dsn, Application.get_env(:sentry, :dsn)) + + ~H""" + + """ end @doc """ diff --git a/demo/lib/demo_web/components/layouts/root.html.heex b/demo/lib/demo_web/components/layouts/root.html.heex index f5d3006d..cda605a9 100644 --- a/demo/lib/demo_web/components/layouts/root.html.heex +++ b/demo/lib/demo_web/components/layouts/root.html.heex @@ -4,7 +4,7 @@ - <%= sentry_meta_tag() %> + <.sentry_meta_tag /> <.live_title suffix=" · Backpex"> <%= assigns[:page_title] || "Phoenix Admin Panel build with PETAL" %> From 001c5dba5de3e298dba74aab4847bf33173b5fb2 Mon Sep 17 00:00:00 2001 From: Florian Arens <60519307+Flo0807@users.noreply.github.com> Date: Mon, 17 Jun 2024 10:04:50 +0200 Subject: [PATCH 25/28] Update what is backpex guide --- guides/about/what-is-backpex.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guides/about/what-is-backpex.md b/guides/about/what-is-backpex.md index 81c79a6a..a854168c 100644 --- a/guides/about/what-is-backpex.md +++ b/guides/about/what-is-backpex.md @@ -2,7 +2,7 @@ Backpex is a highly customizable administration panel for Phoenix LiveView applications. It allows you to quickly create CRUD views of your existing data using configurable *LiveResources*. Backpex integrates seamlessly with your existing Phoenix LiveView application and provides an easy way to manage your resources. It is highly customizable and can be extended with your own layouts, views, field types, filters and more. -Backpex is built on top of Phoenix LiveView and provides a rich set of features to manage your resources. With Backpex, you can set up an administration panel for your application in minutes, not hours. +Backpex is built on top of Phoenix LiveView and provides a rich set of features to manage your resources. With Backpex, you can set up an administration panel for your application in hours, not days. Whether you want to quickly scaffold CRUD views for your existing data or build a full-fledged administration panel, Backpex has you covered. From f600933bffae8a89179a5e2cdc1a98d6a8d5d3cc Mon Sep 17 00:00:00 2001 From: Florian Arens <60519307+Flo0807@users.noreply.github.com> Date: Tue, 18 Jun 2024 08:30:25 +0200 Subject: [PATCH 26/28] Update contributing guide --- guides/about/contribute-to-backpex.md | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/guides/about/contribute-to-backpex.md b/guides/about/contribute-to-backpex.md index 93aa506d..899db9e7 100644 --- a/guides/about/contribute-to-backpex.md +++ b/guides/about/contribute-to-backpex.md @@ -4,13 +4,23 @@ We are excited to have you contribute to Backpex! We are always looking for ways ## What can I contribute? -We are in the process of writing a roadmap to outline the features we plan to implement in the future. In the meantime, you can contribute to Backpex by: +We provide a roadmap and a list of issues that you can work on to contribute to Backpex. -- Reporting bugs -- Requesting new features +- Roadmap: https://github.com/orgs/naymspace/projects/2 +- Issues: https://github.com/naymspace/backpex/issues + +Especially, issues labeled with `good-first-issue` are a good starting point for new contributors. + +We also use GitHub's discussion feature to discuss new ideas and features. + +- Discussions: https://github.com/naymspace/backpex/discussions + +If you don't find an issue that you want to work on, you can always contribute to Backpex by: + +- Reporting bugs (create an issue) +- Requesting new features (use the discussions) - Improving the documentation - Improving the demo application -- Fixing bugs ## Fork the repository From a9130e027ae14eb39ab217bbc2dc8c703edd70d3 Mon Sep 17 00:00:00 2001 From: Florian Arens <60519307+Flo0807@users.noreply.github.com> Date: Tue, 18 Jun 2024 08:50:06 +0200 Subject: [PATCH 27/28] Link issue list with good-first-issue label --- guides/about/contribute-to-backpex.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guides/about/contribute-to-backpex.md b/guides/about/contribute-to-backpex.md index 899db9e7..b7138957 100644 --- a/guides/about/contribute-to-backpex.md +++ b/guides/about/contribute-to-backpex.md @@ -9,7 +9,7 @@ We provide a roadmap and a list of issues that you can work on to contribute to - Roadmap: https://github.com/orgs/naymspace/projects/2 - Issues: https://github.com/naymspace/backpex/issues -Especially, issues labeled with `good-first-issue` are a good starting point for new contributors. +Especially, [issues labeled with `good-first-issue`](https://github.com/naymspace/backpex/labels/good-first-issue) are a good starting point for new contributors. We also use GitHub's discussion feature to discuss new ideas and features. From d9803464543c386b7c9745de090a4926e64bbf2d Mon Sep 17 00:00:00 2001 From: Florian Arens <60519307+Flo0807@users.noreply.github.com> Date: Tue, 18 Jun 2024 10:51:09 +0200 Subject: [PATCH 28/28] Bump version in mix.exs --- mix.exs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.exs b/mix.exs index dd1d1350..3dd6ac95 100644 --- a/mix.exs +++ b/mix.exs @@ -1,7 +1,7 @@ defmodule Backpex.MixProject do use Mix.Project - @version "0.3.1" + @version "0.3.2" @source_url "https://github.com/naymspace/backpex" def project do