Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions demo/lib/demo_web/item_actions/user_soft_delete.ex
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,12 @@ defmodule DemoWeb.ItemActions.UserSoftDelete do
Backpex.Resource.update_all(item.posts, [set: [user_id: nil]], "updated", DemoWeb.PostLive)
end)

%{live_resource: live_resource, params: params} = socket.assigns

socket
|> clear_flash()
|> put_flash(:info, success_message(socket.assigns, items))
|> assign(:return_to, Router.get_path(socket, live_resource, params, :index))
rescue
error ->
Logger.error("An error occurred while deleting the resource: #{inspect(error)}")
Expand Down
5 changes: 3 additions & 2 deletions guides/actions/item-actions.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,12 +77,13 @@ See `Backpex.ItemAction` for a list of all available callbacks.

## Placement of Item Actions

Item actions can be placed in the resource table or at the top of it. You can specify the placement of the item action by using the `only` key.
Item actions can be placed in the index view on resource table, on top of it or in the show viw. 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
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 a icon at the top of the resource view, which triggers the Item Action for the corresponding element

The following example shows how to place the `show` item action on the index table rows only.

Expand Down
24 changes: 7 additions & 17 deletions lib/backpex/html/resource.ex
Original file line number Diff line number Diff line change
Expand Up @@ -643,7 +643,7 @@ defmodule Backpex.HTML.Resource do
<div :if={display_divider?(assigns)} class="border-base-300 my-0.5 border-r-2 border-solid" />

<button
:for={{key, action} <- index_item_actions(@item_actions)}
:for={{key, action} <- item_actions_for(:index, @item_actions)}
class="btn btn-sm btn-outline btn-primary"
disabled={action_disabled?(assigns, key, @selected_items)}
phx-click="item-action"
Expand Down Expand Up @@ -705,24 +705,22 @@ defmodule Backpex.HTML.Resource do
end

defp display_divider?(assigns) do
index_item_actions = index_item_actions(assigns.item_actions)
index_item_actions = item_actions_for(:index, assigns.item_actions)
resource_actions = resource_actions(assigns, assigns.resource_actions)

Enum.any?(index_item_actions) &&
(Enum.any?(resource_actions) || assigns.live_resource.can?(assigns, :new, nil))
end

defp index_item_actions(item_actions) do
def item_actions_for(place, item_actions) do
Enum.filter(item_actions, fn {_key, action} ->
action_on_index?(action)
action_enabled?(action, place)
end)
end

defp row_item_actions(item_actions) do
Enum.filter(item_actions, fn {_key, action} ->
action_on_row?(action)
end)
end
defp action_enabled?(%{only: only}, place), do: place in only
defp action_enabled?(%{except: except}, place), do: place in except
defp action_enabled?(_action, _place), do: true

defp action_disabled?(assigns, action_key, items) do
Enum.filter(items, fn item ->
Expand All @@ -731,14 +729,6 @@ defmodule Backpex.HTML.Resource do
|> Enum.empty?()
end

defp action_on_row?(%{only: only}), do: :row in only
defp action_on_row?(%{except: except}), do: :row not in except
defp action_on_row?(_action), do: true

defp action_on_index?(%{only: only}), do: :index in only
defp action_on_index?(%{except: except}), do: :index not in except
defp action_on_index?(_action), do: true

@doc """
Renders an info block to indicate that no items are found.
"""
Expand Down
6 changes: 3 additions & 3 deletions lib/backpex/html/resource/resource_index_table.html.heex
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<table class="table">
<thead class="bg-base-100 text-base-content uppercase">
<tr id="row-headers" phx-hook="BackpexStickyActions" class="border-b-2">
<th :if={Enum.any?(index_item_actions(@item_actions))}>
<th :if={Enum.any?(item_actions_for(:index, @item_actions))}>
<input
phx-click="toggle-item-selection"
type="checkbox"
Expand Down Expand Up @@ -36,7 +36,7 @@
class={index_row_class(assigns, item, selected?(@selected_items, item), index)}
phx-hook="BackpexStickyActions"
>
<td :if={Enum.any?(index_item_actions(@item_actions))} class="relative">
<td :if={Enum.any?(item_actions_for(:index, @item_actions))} class="relative">
<div :if={selected?(@selected_items, item)} class="bg-base-content absolute inset-y-0 left-0 w-0.5" />
<input
id={"select-input-#{LiveResource.primary_value(item, @live_resource)}"}
Expand Down Expand Up @@ -65,7 +65,7 @@
]}>
<div class={["flex items-center justify-end space-x-2"]}>
<button
:for={{key, action} <- row_item_actions(@item_actions)}
:for={{key, action} <- item_actions_for(:row, @item_actions)}
:if={@live_resource.can?(assigns, key, item)}
id={"item-action-#{key}-#{LiveResource.primary_value(item, @live_resource)}"}
type="button"
Expand Down
22 changes: 22 additions & 0 deletions lib/backpex/html/resource/resource_show.html.heex
Original file line number Diff line number Diff line change
@@ -1,4 +1,26 @@
<div>
<.modal
:if={@action_to_confirm}
id="action-confirm-modal"
title={@action_to_confirm.module.label(assigns, nil)}
on_cancel={JS.push("cancel-action-confirm")}
open={true}
close_label={Backpex.__("Close modal", @live_resource)}
>
<div class="px-5 py-3">
{@action_to_confirm.module.confirm(assigns)}
</div>
<div>
<.live_component
module={Backpex.FormComponent}
id={:item_action_modal}
live_resource={@live_resource}
action_type={:item}
{Map.drop(assigns, [:socket, :flash, :fields])}
/>
</div>
</.modal>
Comment on lines +2 to +22
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We might want to move this to another component as this is duplicated in resource_show.html.heex and resource_index.html.heex


{@live_resource.render_resource_slot(assigns, :show, :before_page_title)}
{@live_resource.render_resource_slot(assigns, :show, :page_title)}

Expand Down
6 changes: 4 additions & 2 deletions lib/backpex/item_actions/delete.ex
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,15 @@ defmodule Backpex.ItemActions.Delete do

@impl Backpex.ItemAction
def handle(socket, items, _data) do
{:ok, deleted_items} = Resource.delete_all(items, socket.assigns.live_resource)
%{live_resource: live_resource, params: params} = socket.assigns
{:ok, deleted_items} = Resource.delete_all(items, live_resource)

Enum.each(deleted_items, fn deleted_item -> socket.assigns.live_resource.on_item_deleted(socket, deleted_item) end)
Enum.each(deleted_items, fn deleted_item -> live_resource.on_item_deleted(socket, deleted_item) end)

socket
|> clear_flash()
|> put_flash(:info, success_message(socket.assigns, deleted_items))
|> assign(:return_to, Router.get_path(socket, live_resource, params, :index))
|> ok()
rescue
error ->
Expand Down
21 changes: 10 additions & 11 deletions lib/backpex/live_components/form_component.ex
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ defmodule Backpex.FormComponent do
end

def handle_event("validate", %{"change" => change, "_target" => target}, %{assigns: %{action_type: :item}} = socket) do
%{assigns: %{item: item, fields: fields} = assigns} = socket
%{assigns: %{form_item: item, fields: fields} = assigns} = socket

changeset_function = &assigns.action_to_confirm.module.changeset/3

Expand Down Expand Up @@ -107,7 +107,7 @@ defmodule Backpex.FormComponent do
def handle_event("validate", %{"change" => change, "_target" => target}, socket) do
%{
live_resource: live_resource,
item: item,
form_item: item,
fields: fields
} = socket.assigns

Expand Down Expand Up @@ -159,7 +159,7 @@ defmodule Backpex.FormComponent do
|> Map.get(:removed_uploads, [])
|> Keyword.update(upload_key, [file_key], fn existing -> [file_key | existing] end)

files = Upload.existing_file_paths(field, socket.assigns.item, Keyword.get(removed_uploads, upload_key, []))
files = Upload.existing_file_paths(field, socket.assigns.form_item, Keyword.get(removed_uploads, upload_key, []))
uploaded_files = Keyword.put(socket.assigns[:uploaded_files], upload_key, files)

socket
Expand Down Expand Up @@ -207,7 +207,7 @@ defmodule Backpex.FormComponent do
defp handle_save(socket, key, params, save_type \\ "save")

defp handle_save(socket, :new, params, save_type) do
%{assigns: %{live_resource: live_resource, item: item, live_action: live_action} = assigns} = socket
%{assigns: %{live_resource: live_resource, form_item: item, live_action: live_action} = assigns} = socket

opts = [
assocs: Map.get(assigns, :assocs, []),
Expand Down Expand Up @@ -251,7 +251,7 @@ defmodule Backpex.FormComponent do
defp handle_save(socket, :edit, params, save_type) do
%{
live_resource: live_resource,
item: item,
form_item: item,
fields: fields,
live_action: live_action
} = socket.assigns
Expand Down Expand Up @@ -301,7 +301,7 @@ defmodule Backpex.FormComponent do
%{
live_resource: live_resource,
resource_action: resource_action,
item: item,
form_item: item,
return_to: return_to,
fields: fields
} = assigns
Expand Down Expand Up @@ -353,8 +353,7 @@ defmodule Backpex.FormComponent do
live_resource: live_resource,
selected_items: selected_items,
action_to_confirm: action_to_confirm,
fields: fields,
return_to: return_to
fields: fields
} = assigns
} = socket

Expand All @@ -366,7 +365,7 @@ defmodule Backpex.FormComponent do

metadata = Resource.build_changeset_metadata(assigns)

assigns.item
assigns.form_item
|> changeset_function.(params, metadata)
|> Map.put(:action, :insert)
|> Ecto.Changeset.apply_action(:insert)
Expand All @@ -381,7 +380,7 @@ defmodule Backpex.FormComponent do
|> assign(:show_form_errors, false)
|> assign(:selected_items, [])
|> assign(:select_all, false)
|> push_patch(to: return_to)
|> push_navigate(to: socket.assigns.return_to)
|> noreply()
else
{:error, changeset} ->
Expand Down Expand Up @@ -450,7 +449,7 @@ defmodule Backpex.FormComponent do
uploaded_entries = uploaded_entries(socket, upload_key)
removed_entries = Keyword.get(socket.assigns.removed_uploads, upload_key, [])

change = put_upload_change.(socket, acc, socket.assigns.item, uploaded_entries, removed_entries, action)
change = put_upload_change.(socket, acc, socket.assigns.form_item, uploaded_entries, removed_entries, action)

upload_used_input_data = Map.get(change, "#{to_string(name)}_used_input")
used_input? = upload_used_input_data != "false"
Expand Down
28 changes: 15 additions & 13 deletions lib/backpex/live_resource.ex
Original file line number Diff line number Diff line change
Expand Up @@ -407,19 +407,21 @@ defmodule Backpex.LiveResource do
~H"""
<.main_title class="flex items-center justify-between">
{@page_title}
<.link
:if={@live_resource.can?(assigns, :edit, @item)}
id={"#{@live_resource.singular_name()}-edit-link"}
phx-hook="BackpexTooltip"
data-tooltip={Backpex.__("Edit", @live_resource)}
aria-label={Backpex.__("Edit", @live_resource)}
patch={Router.get_path(@socket, @live_resource, @params, :edit, @item)}
>
<Backpex.HTML.CoreComponents.icon
name="hero-pencil-square"
class="h-6 w-6 cursor-pointer transition duration-75 hover:text-primary hover:scale-110"
/>
</.link>
<div class={["flex items-center justify-end space-x-2"]}>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
<div class={["flex items-center justify-end space-x-2"]}>
<div class="flex items-center justify-end space-x-2">

<button
:for={{key, action} <- item_actions_for(:show, @item_actions)}
:if={@live_resource.can?(assigns, key, @item)}
id={"item-action-#{key}-#{LiveResource.primary_value(@item, @live_resource)}"}
type="button"
phx-click="item-action"
phx-value-action-key={key}
aria-label={action.module.label(assigns, @item)}
phx-hook="BackpexTooltip"
data-tooltip={action.module.label(assigns, @item)}
>
{action.module.icon(assigns, @item)}
</button>
</div>
Comment on lines +410 to +424
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess, we need another div for the tooltip that has the button inside.

It looks like there is too much space between the text and the tooltip:

image

</.main_title>
"""
end
Expand Down
2 changes: 0 additions & 2 deletions lib/backpex/live_resource/form.ex
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ defmodule Backpex.LiveResource.Form do
@moduledoc false
use BackpexWeb, :html

import Phoenix.Component

alias Backpex.LiveResource
alias Backpex.Resource

Expand Down
10 changes: 4 additions & 6 deletions lib/backpex/live_resource/index.ex
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ defmodule Backpex.LiveResource.Index do
@moduledoc false
use BackpexWeb, :html

import Phoenix.Component

alias Backpex.Adapters.Ecto, as: EctoAdapter
alias Backpex.LiveResource
alias Backpex.Resource
Expand Down Expand Up @@ -220,7 +218,7 @@ defmodule Backpex.LiveResource.Index do

def handle_event("cancel-action-confirm", _params, socket) do
socket
|> assign(:item, nil)
|> assign(:form_item, nil)
|> assign(:changeset, nil)
|> assign(:action_to_confirm, nil)
|> noreply()
Expand Down Expand Up @@ -269,7 +267,7 @@ defmodule Backpex.LiveResource.Index do
changeset = changeset_function.(base_schema, %{}, metadata)

socket
|> assign(:item, base_schema)
|> assign(:form_item, base_schema)
|> assign(:changeset, changeset)
else
assign(socket, :changeset, %{})
Expand Down Expand Up @@ -387,7 +385,7 @@ defmodule Backpex.LiveResource.Index do
socket
|> assign(:page_title, socket.assigns.live_resource.plural_name())
|> apply_index()
|> assign(:item, nil)
|> assign(:form_item, nil)
end

defp apply_action(socket, :resource_action) do
Expand All @@ -409,7 +407,7 @@ defmodule Backpex.LiveResource.Index do
|> assign(:page_title, ResourceAction.name(action, :title))
|> assign(:resource_action, action)
|> assign(:resource_action_id, id)
|> assign(:item, item)
|> assign(:form_item, item)
|> apply_index()
|> assign(:changeset_function, changeset_function)
|> assign_changeset(changeset_function, item, action.module.fields(), :resource_action)
Expand Down
Loading
Loading