Skip to content

Commit

Permalink
Merge pull request #103 from tfwright/feat/action-args
Browse files Browse the repository at this point in the history
Add support for action arguments
  • Loading branch information
tfwright authored Feb 16, 2024
2 parents 11069a3 + 5966713 commit c6209e4
Show file tree
Hide file tree
Showing 10 changed files with 161 additions and 51 deletions.
21 changes: 10 additions & 11 deletions assets/css/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -431,7 +431,6 @@ input[type="submit"] {
.resource__table tfoot td {
@apply p-1;
@apply bottom-0;
@apply sticky;
}

.resource__table dd {
Expand Down Expand Up @@ -737,7 +736,7 @@ input[type="submit"] {
@apply ml-2;
}

.task__modal {
.modal {
@apply fixed;
@apply hidden;
@apply inset-0;
Expand All @@ -750,7 +749,7 @@ input[type="submit"] {
@apply m-0;
}

.task__modal > div {
.modal > div {
@apply relative;
@apply top-20;
@apply mx-auto;
Expand All @@ -763,40 +762,40 @@ input[type="submit"] {
@apply whitespace-normal;
}

.task__modal form {
.modal form {
@apply flex;
@apply flex-col;
@apply mt-3;
}

.task__modal label {
.modal label {
@apply flex;
@apply h-7;
@apply w-7;
@apply rounded-full;
@apply rounded-full;
@apply bg-gray-100;
@apply items-center;
@apply justify-center;
@apply mr-2;
}

.task__modal input {
.modal input {
@apply grow;
}

.task__modal pre {
.modal pre {
@apply inline;
@apply underline;
@apply decoration-dotted;
@apply underline-offset-4;
@apply decoration-2;
}

.task__modal form > div {
.modal form > div {
@apply flex;
@apply my-1;
}

.task__modal form > div:last-of-type {
.modal form > div:last-of-type {
@apply mb-3;
}
}
28 changes: 26 additions & 2 deletions assets/js/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,21 @@ Hooks.MapInput = {
Hooks.ViewPage = {
mounted() {
this.el.addEventListener("live_admin:action", e => {
this.pushEventTo(this.el, "action", {action: e.target.dataset.action, ids: this.selected});
if (e.target.tagName === "FORM") {
const params = [...new FormData(e.target)].reduce((params, [key,val]) => {
if (key === "args[]") {
return { ...params, args: [...params.args, val] }
} else {
return { ...params, [key] : val }
}
}, {args: []}) ;

e.target.reset();

this.pushEventTo(this.el, "action", params);
} else {
this.pushEventTo(this.el, "action", {name: e.target.dataset.action});
}
})
}
}
Expand All @@ -104,8 +118,18 @@ Hooks.IndexPage = {
this.el.addEventListener("live_admin:action", e => {
if (e.target.dataset.action === "delete") {
this.pushEventTo(this.el, "delete", {ids: this.selected});
} else if (e.target.tagName === "FORM") {
const params = [...new FormData(e.target)].reduce((params, [key,val]) => {
if (key === "args[]") {
return { ...params, args: [...params.args, val] }
} else {
return { ...params, [key] : val }
}
}, {args: []}) ;

this.pushEventTo(this.el, "action", {...params, ids: this.selected});
} else {
this.pushEventTo(this.el, "action", {action: e.target.dataset.action, ids: this.selected});
this.pushEventTo(this.el, "action", {name: e.target.dataset.action, ids: this.selected});
}
})

Expand Down
11 changes: 10 additions & 1 deletion dev/user_admin.ex
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,21 @@ defmodule DemoWeb.UserAdmin do
immutable_fields: [:encrypted_password, :inserted_at],
components: [new: DemoWeb.CreateUserForm],
label_with: :name,
actions: [:deactivate, :fake, :fake],
actions: [:deactivate, :set_password],
tasks: [:regenerate_passwords, :aggregate],
render_with: :render_field

use PhoenixHTMLHelpers

def set_password(user, _, new_password) do
user =
user
|> Ecto.Changeset.change(encrypted_password: Base.encode16(new_password))
|> Demo.Repo.update!()

{:ok, user}
end

def deactivate(user, _) do
user
|> Ecto.Changeset.change(active: false)
Expand Down
20 changes: 9 additions & 11 deletions dist/css/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -1171,8 +1171,6 @@ tfoot td {
.resource__table tfoot td {
padding: 0.25rem;
bottom: 0px;
position: -webkit-sticky;
position: sticky;
}

.resource__table dd {
Expand Down Expand Up @@ -1720,7 +1718,7 @@ input[type="submit"] {
margin-left: 0.5rem;
}

.task__modal {
.modal {
position: fixed;
display: none;
inset: 0px;
Expand All @@ -1733,7 +1731,7 @@ input[type="submit"] {
margin: 0px;
}

.task__modal > div {
.modal > div {
position: relative;
top: 5rem;
margin-left: auto;
Expand All @@ -1750,13 +1748,13 @@ input[type="submit"] {
white-space: normal;
}

.task__modal form {
.modal form {
display: flex;
flex-direction: column;
margin-top: 0.75rem;
}

.task__modal label {
.modal label {
display: flex;
height: 1.75rem;
width: 1.75rem;
Expand All @@ -1768,11 +1766,11 @@ input[type="submit"] {
margin-right: 0.5rem;
}

.task__modal input {
.modal input {
flex-grow: 1;
}

.task__modal pre {
.modal pre {
display: inline;
-webkit-text-decoration-line: underline;
text-decoration-line: underline;
Expand All @@ -1782,18 +1780,18 @@ input[type="submit"] {
text-decoration-thickness: 2px;
}

.task__modal form > div {
.modal form > div {
display: flex;
margin-top: 0.25rem;
margin-bottom: 0.25rem;
}

.task__modal form > div:last-of-type {
.modal form > div:last-of-type {
margin-bottom: 0.75rem;
}

.focus\:ring:focus {
--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width)) var(--tw-ring-color);
box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
}
}
30 changes: 27 additions & 3 deletions dist/js/app.js

Large diffs are not rendered by default.

70 changes: 70 additions & 0 deletions lib/live_admin/components.ex
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,76 @@ defmodule LiveAdmin.Components do
"""
end

def action_control(assigns) do
{m, f, []} =
assigns.resource
|> LiveAdmin.fetch_config(:actions, assigns.session)
|> Enum.find_value(fn
{action_name, mfa} -> action_name == assigns.action && mfa
action_name -> action_name == assigns.action && {assigns.resource, action_name, []}
end)

extra_arg_count =
:functions
|> m.__info__()
|> Enum.find_value(fn {name, arity} -> name == f && arity - 2 end)

assigns = assign(assigns, extra_arg_count: extra_arg_count)

~H"""
<button
class="resource__action--link"
data-action={@action}
phx-click={
if @extra_arg_count > 0,
do:
JS.show(
to: "##{@action}-action-modal",
transition: {"ease-in duration-300", "opacity-0", "opacity-100"}
),
else: JS.dispatch("live_admin:action")
}
data-confirm={if @extra_arg_count > 0, do: nil, else: "Are you sure?"}
>
<%= @action |> to_string() |> humanize() %>
</button>
<%= if @extra_arg_count > 0 do %>
<.action_modal id={"#{@action}-action-modal"}>
<pre><%= @action %></pre> action requires additional arguments:
<.form
for={Phoenix.Component.to_form(%{})}
phx-submit={JS.dispatch("live_admin:action") |> JS.hide(to: "##{@action}-action-modal")}
>
<input type="hidden" name="name" value={@action} />
<%= for num <- 1..@extra_arg_count do %>
<div>
<label><%= num %></label>
<input type="text" name="args[]" />
</div>
<% end %>
<input type="submit" value="Execute" />
</.form>
</.action_modal>
<% end %>
"""
end

defp action_modal(assigns) do
~H"""
<div
id={@id}
class="modal"
phx-capture-click={
JS.hide(to: "##{@id}", transition: {"ease-out duration-300", "opacity-100", "opacity-0"})
}
>
<div>
<%= render_slot(@inner_block) %>
</div>
</div>
"""
end

defp list(assigns) do
~H"""
<div>
Expand Down
2 changes: 1 addition & 1 deletion lib/live_admin/components/container.ex
Original file line number Diff line number Diff line change
Expand Up @@ -411,7 +411,7 @@ defmodule LiveAdmin.Components.Container do
~H"""
<div
id={@id}
class="task__modal"
class="modal"
phx-capture-click={
JS.hide(to: "##{@id}", transition: {"ease-out duration-300", "opacity-100", "opacity-0"})
}
Expand Down
13 changes: 3 additions & 10 deletions lib/live_admin/components/resource/index.ex
Original file line number Diff line number Diff line change
Expand Up @@ -233,14 +233,7 @@ defmodule LiveAdmin.Components.Container.Index do
items={LiveAdmin.fetch_config(@resource, :actions, @config)}
disabled={Enum.empty?(LiveAdmin.fetch_config(@resource, :actions, @config))}
>
<button
class="resource__action--link"
data-action={action}
phx-click={JS.dispatch("live_admin:action")}
data-confirm="Are you sure?"
>
<%= action |> to_string() |> humanize() %>
</button>
<.action_control action={action} session={@session} resource={@resource} />
</.dropdown>
</div>
</td>
Expand Down Expand Up @@ -343,7 +336,7 @@ defmodule LiveAdmin.Components.Container.Index do
@impl true
def handle_event(
"action",
%{"action" => action, "ids" => ids},
params = %{"name" => action, "ids" => ids},
socket = %{assigns: %{resource: resource, prefix: prefix, repo: repo}}
) do
records = Resource.all(ids, resource, prefix, repo)
Expand All @@ -360,7 +353,7 @@ defmodule LiveAdmin.Components.Container.Index do
action_name -> to_string(action_name) == action && {resource, action_name, []}
end)

apply(m, f, [record, socket.assigns.session])
apply(m, f, [record, socket.assigns.session] ++ Map.get(params, "args", []))
end)
end)
|> Task.await_many()
Expand Down
15 changes: 4 additions & 11 deletions lib/live_admin/components/resource/view.ex
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ defmodule LiveAdmin.Components.Container.View do
@impl true
def render(assigns) do
~H"""
<div id="view-page" class="view__container" phx-hook="IndexPage" phx-target={@myself}>
<div id="view-page" class="view__container" phx-hook="ViewPage" phx-target={@myself}>
<div class="resource__table">
<dl>
<%= for {field, type, _} <- Resource.fields(@resource, @config) do %>
Expand Down Expand Up @@ -84,14 +84,7 @@ defmodule LiveAdmin.Components.Container.View do
items={LiveAdmin.fetch_config(@resource, :actions, @config)}
disabled={Enum.empty?(LiveAdmin.fetch_config(@resource, :actions, @config))}
>
<button
class="resource__action--link"
data-action={action}
phx-click={JS.dispatch("live_admin:action")}
data-confirm="Are you sure?"
>
<%= action |> to_string() |> humanize() %>
</button>
<.action_control action={action} session={@session} resource={@resource} />
</.dropdown>
</div>
</div>
Expand Down Expand Up @@ -136,7 +129,7 @@ defmodule LiveAdmin.Components.Container.View do
@impl true
def handle_event(
"action",
params = %{"action" => action},
params = %{"name" => action},
socket = %{assigns: %{resource: resource, prefix: prefix, repo: repo, session: session}}
) do
record =
Expand All @@ -154,7 +147,7 @@ defmodule LiveAdmin.Components.Container.View do
end)

socket =
case apply(m, f, [record, socket.assigns.session]) do
case apply(m, f, [record, socket.assigns.session] ++ Map.get(params, "args", [])) do
{:ok, record} ->
socket
|> push_event("success", %{
Expand Down
2 changes: 1 addition & 1 deletion test/live_admin/components/container_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ defmodule LiveAdmin.Components.ContainerTest do
test "runs configured action on selected records", %{view: view} do
view
|> element("#list")
|> render_hook("action", %{action: "user_action", ids: []})
|> render_hook("action", %{name: "user_action", ids: []})

assert_redirected(view, "/user")
end
Expand Down

0 comments on commit c6209e4

Please sign in to comment.