Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor filter modules and make them reusable #113

Merged
merged 18 commits into from
Mar 20, 2024
Merged
Show file tree
Hide file tree
Changes from 11 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
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ jobs:
- name: lint:dialyzer
working-directory: /opt/app/demo
run: |
yarn lint:dialyzer
mix dialyzer --force-check

- name: lint:credo
working-directory: /opt/app/demo
Expand Down
135 changes: 67 additions & 68 deletions demo/lib/demo_web/item_actions/duplicate_tag.ex
Original file line number Diff line number Diff line change
@@ -1,70 +1,69 @@
defmodule DemoWeb.ItemActions.DuplicateTag do
@moduledoc false

use BackpexWeb, :item_action

alias Demo.Repo

@impl Backpex.ItemAction
def icon(assigns) do
~H"""
<Heroicons.document_duplicate class="h-5 w-5 cursor-pointer transition duration-75 hover:scale-110 hover:text-green-600" />
"""
end

@impl Backpex.ItemAction
def fields do
[
name: %{
module: Backpex.Fields.Text,
label: "Name",
searchable: true,
placeholder: "Tag name"
}
]
end

@impl Backpex.ItemAction
def label(_assigns), do: "Duplicate"

@impl Backpex.ItemAction
def confirm(_assigns), do: "Please complete the form to duplicate the item."

@impl Backpex.ItemAction
def confirm_label(_assigns), do: "Duplicate"

@impl Backpex.ItemAction
def cancel_label(_assigns), do: "Cancel"

@impl Backpex.ItemAction
def changeset(item, change, metadata) do
Demo.Tag.create_changeset(item, change, metadata)
end

@impl Backpex.ItemAction
def init_change(assigns) do
[item | _other] = assigns.selected_items

item
end

@impl Backpex.ItemAction
def handle(socket, _items, params) do
result =
%Demo.Tag{}
|> Demo.Tag.create_changeset(params, [target: nil, assigns: socket.assigns])
|> Repo.insert()

socket =
case result do
{:ok, _created} ->
put_flash(socket, :info, "Item has been duplicated.")

_error ->
put_flash(socket, :error, "Error while duplicating item.")
end


{:noreply, socket}
end
@moduledoc false

use BackpexWeb, :item_action

alias Demo.Repo

@impl Backpex.ItemAction
def icon(assigns) do
~H"""
<Heroicons.document_duplicate class="h-5 w-5 cursor-pointer transition duration-75 hover:scale-110 hover:text-green-600" />
"""
end

@impl Backpex.ItemAction
def fields do
[
name: %{
module: Backpex.Fields.Text,
label: "Name",
searchable: true,
placeholder: "Tag name"
}
]
end

@impl Backpex.ItemAction
def label(_assigns), do: "Duplicate"

@impl Backpex.ItemAction
def confirm(_assigns), do: "Please complete the form to duplicate the item."

@impl Backpex.ItemAction
def confirm_label(_assigns), do: "Duplicate"

@impl Backpex.ItemAction
def cancel_label(_assigns), do: "Cancel"

@impl Backpex.ItemAction
def changeset(item, change, metadata) do
Demo.Tag.create_changeset(item, change, metadata)
end

@impl Backpex.ItemAction
def init_change(assigns) do
[item | _other] = assigns.selected_items

item
end

@impl Backpex.ItemAction
def handle(socket, _items, params) do
result =
%Demo.Tag{}
|> Demo.Tag.create_changeset(params, target: nil, assigns: socket.assigns)
|> Repo.insert()

socket =
case result do
{:ok, _created} ->
put_flash(socket, :info, "Item has been duplicated.")

_error ->
put_flash(socket, :error, "Error while duplicating item.")
end

{:noreply, socket}
end
end
152 changes: 91 additions & 61 deletions lib/backpex/filters/boolean.ex
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ defmodule Backpex.Filters.Boolean do
> In addition it will add a `render` and `render_form` function in order to display the corresponding filter.
> It will also implement the `Backpex.Filter.query` function to define a boolean query.
"""
use Phoenix.Component, global_prefixes: ~w(x-)
Flo0807 marked this conversation as resolved.
Show resolved Hide resolved
import Ecto.Query, warn: false

@doc """
The list of options for the select filter.
Expand All @@ -48,87 +50,115 @@ defmodule Backpex.Filters.Boolean do
quote do
use BackpexWeb, :filter

alias Backpex.Filters.Boolean, as: BooleanFilter

@behaviour Backpex.Filters.Boolean

@impl Backpex.Filter
def query(query, _attribute, []), do: query

def query(query, attribute, value) do
Enum.reduce(value, nil, fn
v, nil ->
Map.get(predicates(), v)

v, p ->
dynamic(^p or ^Map.get(predicates(), v))
end)
|> maybe_query(query)
BooleanFilter.query(query, options(), attribute, value)
end

defp maybe_query(nil, query), do: query
defp maybe_query(predicates, query), do: where(query, ^predicates)

@impl Backpex.Filter
def render(var!(assigns)) do
var!(assigns) =
var!(assigns)
|> assign(:label, option_value_to_label(options(), var!(assigns).value))
var!(assigns) = assign(var!(assigns), :options, options())

~H"""
<%= @label %>
<BooleanFilter.render options={@options} value={@value} />
Flo0807 marked this conversation as resolved.
Show resolved Hide resolved
"""
end

defp option_value_to_label(options, values) do
Enum.map(values, fn key -> find_option_label(options, key) end)
|> Enum.intersperse(", ")
end

defp find_option_label(options, key) do
Enum.find_value(options, fn option ->
if option.key == key, do: option.label
end) || ""
end

@impl Backpex.Filter
def render_form(var!(assigns) = assigns) do
checked = if is_nil(assigns.value), do: [], else: assigns.value
options = Enum.map(options(), fn %{label: l, key: k} -> {l, k} end)

var!(assigns) =
var!(assigns)
|> assign(:checked, checked)
|> assign(:options, options)
var!(assigns) = assign(var!(assigns), :options, options())

~H"""
<div class="mt-2 flex flex-col space-y-2">
<%= Phoenix.HTML.Form.hidden_input(@form, @field, name: Phoenix.HTML.Form.input_name(@form, @field), value: "") %>
<%= for {label, key} <- @options do %>
<label class="flex cursor-pointer items-center gap-x-2">
<%= Phoenix.HTML.Form.checkbox(
@form,
@field,
name: Phoenix.HTML.Form.input_name(@form, @field) <> "[]",
class: "checkbox checkbox-sm checkbox-primary",
checked: to_string(key) in @checked,
checked_value: key,
unchecked_value: "",
hidden_input: false
) %>
<span class="label-text">
<%= label %>
</span>
</label>
<% end %>
</div>
<BooleanFilter.render_form form={@form} field={@field} value={@value} options={@options} />
"""
end

defp predicates do
Enum.map(options(), fn %{predicate: p, key: k} -> {k, p} end)
|> Enum.into(%{})
end

defoverridable query: 3
defoverridable query: 3, render: 1, render_form: 1
end
end

attr :value, :any, required: true
attr :options, :list, required: true

def render(assigns) do
assigns = assign(assigns, :label, option_value_to_label(assigns.options, assigns.value))

~H"""
<%= @label %>
"""
end

attr :form, :any, required: true
attr :field, :atom, required: true
attr :value, :any, required: true
attr :options, :list, required: true

def render_form(assigns) do
checked = if is_nil(assigns.value), do: [], else: assigns.value
options = Enum.map(assigns.options, fn %{label: l, key: k} -> {l, k} end)

assigns =
assigns
|> assign(:checked, checked)
|> assign(:options, options)

~H"""
<div class="mt-2 flex flex-col space-y-2">
<%= Phoenix.HTML.Form.hidden_input(@form, @field, name: Phoenix.HTML.Form.input_name(@form, @field), value: "") %>
<%= for {label, key} <- @options do %>
<label class="flex cursor-pointer items-center gap-x-2">
<%= Phoenix.HTML.Form.checkbox(
@form,
@field,
name: Phoenix.HTML.Form.input_name(@form, @field) <> "[]",
class: "checkbox checkbox-sm checkbox-primary",
checked: to_string(key) in @checked,
checked_value: key,
unchecked_value: "",
hidden_input: false
) %>
<span class="label-text">
<%= label %>
</span>
</label>
<% end %>
</div>
"""
end

def query(query, _options, _attribute, []), do: query

def query(query, options, _attribute, value) do
Enum.reduce(value, nil, fn
v, nil ->
Map.get(predicates(options), v)

v, p ->
dynamic(^p or ^Map.get(predicates(options), v))
end)
|> maybe_query(query)
end

def maybe_query(nil, query), do: query
def maybe_query(predicates, query), do: where(query, ^predicates)

def option_value_to_label(options, values) do
Enum.map(values, fn key -> find_option_label(options, key) end)
|> Enum.intersperse(", ")
end

def find_option_label(options, key) do
Enum.find_value(options, fn option ->
if option.key == key, do: option.label
end) || ""
end

def predicates(options) do
Enum.map(options, fn %{predicate: p, key: k} -> {k, p} end)
|> Enum.into(%{})
end
end
Loading
Loading