Packages
flop_phoenix
0.18.2
0.26.1
0.26.0
0.25.3
0.25.2
0.25.1
0.25.0
0.24.1
0.24.0
0.23.1
0.23.0
0.22.10
0.22.9
0.22.8
0.22.7
0.22.6
0.22.5
0.22.4
0.22.3
0.22.2
0.22.1
0.22.0
0.21.2
0.21.1
0.21.0
0.20.0
0.19.1
0.19.0
0.18.2
0.18.1
0.18.0
0.17.2
0.17.1
0.17.0
0.16.0
0.15.2
0.15.1
0.15.0
0.14.2
0.14.1
0.14.0
0.13.0
0.12.0
0.11.1
0.11.0
0.10.0
0.9.1
0.9.0
0.8.1
0.8.0
0.7.0
0.6.1
0.6.0
0.5.1
0.5.0
0.4.0
0.3.0
0.2.0
0.1.0
Phoenix components for pagination, sortable tables and filter forms using Flop.
Current section
Files
Jump to
Current section
Files
lib/flop_phoenix/table.ex
defmodule Flop.Phoenix.Table do
@moduledoc false
use Phoenix.Component
alias Flop.Phoenix.Misc
alias Phoenix.HTML
alias Phoenix.LiveView.JS
require Logger
@path_event_error_msg """
the :path or :event option is required when rendering a table
The :path value can be a {module, function_name, args} tuple, a
{function, args} tuple, or a 1-ary function.
The :event value needs to be a string.
## Examples
<Flop.Phoenix.table
items={@pets}
meta={@meta}
path={~p"/pets"}
>
or
<Flop.Phoenix.table
items={@pets}
meta={@meta}
path={{Routes, :pet_path, [@socket, :index]}}
>
or
<Flop.Phoenix.table
items={@pets}
meta={@meta}
path={{&Routes.pet_path/3, [@socket, :index]}}
>
or
<Flop.Phoenix.table
items={@pets}
meta={@meta}
path={&build_path/1}
>
or
<Flop.Phoenix.table
items={@pets}
meta={@meta}
event="sort-table"
>
"""
@spec default_opts() :: [Flop.Phoenix.table_option()]
def default_opts do
[
container: false,
container_attrs: [class: "table-container"],
no_results_content: HTML.Tag.content_tag(:p, do: "No results."),
symbol_asc: "â–´",
symbol_attrs: [class: "order-direction"],
symbol_desc: "â–¾",
symbol_unsorted: nil,
table_attrs: [],
tbody_attrs: [],
tbody_td_attrs: [],
tbody_tr_attrs: [],
th_wrapper_attrs: [],
thead_th_attrs: [],
thead_tr_attrs: []
]
end
@doc """
Deep merges the given options into the default options.
"""
@spec init_assigns(map) :: map
def init_assigns(%{meta: meta} = assigns) do
assigns = assign(assigns, :opts, merge_opts(assigns[:opts] || []))
Misc.validate_path_or_event!(assigns, @path_event_error_msg)
assign_new(assigns, :id, fn ->
case meta.schema do
nil ->
"paginated_table"
module ->
module_name =
module |> Module.split() |> List.last() |> Macro.underscore()
module_name <> "_table"
end
end)
end
defp merge_opts(opts) do
default_opts()
|> Misc.deep_merge(Misc.get_global_opts(:table))
|> Misc.deep_merge(opts)
end
attr :id, :string, required: true
attr :meta, Flop.Meta, required: true
attr :path, :any, required: true
attr :event, :string, required: true
attr :target, :string, required: true
attr :caption, :string, required: true
attr :opts, :any, required: true
attr :col, :any, required: true
attr :items, :list, required: true
attr :foot, :any, required: true
attr :row_id, :any, default: nil
attr :row_click, JS, default: nil
attr :row_item, :any, required: true
attr :action, :any, required: true
def render(assigns) do
assigns =
with %{items: %Phoenix.LiveView.LiveStream{}} <- assigns do
assign(assigns, row_id: assigns.row_id || fn {id, _item} -> id end)
end
~H"""
<table {@opts[:table_attrs]}>
<caption :if={@caption}><%= @caption %></caption>
<colgroup :if={
Enum.any?(@col, & &1[:col_style]) or Enum.any?(@action, & &1[:col_style])
}>
<%= for col <- @col do %>
<col :if={show_column?(col)} style={col[:col_style]} />
<% end %>
<%= for action <- @action do %>
<col :if={show_column?(action)} style={action[:col_style]} />
<% end %>
</colgroup>
<thead>
<tr {@opts[:thead_tr_attrs]}>
<%= for col <- @col do %>
<.header_column
:if={show_column?(col)}
event={@event}
field={col[:field]}
label={col[:label]}
meta={@meta}
opts={@opts}
path={@path}
target={@target}
/>
<% end %>
<%= for action <- @action do %>
<.header_column
:if={show_column?(action)}
event={@event}
field={nil}
label={action[:label]}
meta={@meta}
opts={@opts}
path={nil}
target={@event}
/>
<% end %>
</tr>
</thead>
<tbody
id={@id}
phx-update={match?(%Phoenix.LiveView.LiveStream{}, @items) && "stream"}
{@opts[:tbody_attrs]}
>
<tr
:for={item <- @items}
id={@row_id && @row_id.(item)}
{@opts[:tbody_tr_attrs]}
>
<%= for col <- @col do %>
<td
:if={show_column?(col)}
{@opts[:tbody_td_attrs]}
{Map.get(col, :attrs, [])}
phx-click={@row_click && @row_click.(item)}
>
<%= render_slot(col, @row_item.(item)) %>
</td>
<% end %>
<td
:for={action <- @action}
{@opts[:tbody_td_attrs]}
{Map.get(action, :attrs, [])}
>
<%= render_slot(action, @row_item.(item)) %>
</td>
</tr>
</tbody>
<tfoot :if={@foot != []}><%= render_slot(@foot) %></tfoot>
</table>
"""
end
defp show_column?(%{hide: true}), do: false
defp show_column?(%{show: false}), do: false
defp show_column?(_), do: true
attr :meta, Flop.Meta, required: true
attr :field, :atom, required: true
attr :label, :string, required: true
attr :path, :any, required: true
attr :event, :string, required: true
attr :target, :string, required: true
attr :opts, :any, required: true
defp header_column(assigns) do
index = order_index(assigns.meta.flop, assigns.field)
direction = order_direction(assigns.meta.flop.order_directions, index)
assigns =
assigns
|> assign(:order_index, index)
|> assign(:order_direction, direction)
~H"""
<%= if is_sortable?(@field, @meta.schema) do %>
<th
{@opts[:thead_th_attrs]}
aria-sort={aria_sort(@order_index, @order_direction)}
>
<span {@opts[:th_wrapper_attrs]}>
<%= if @event do %>
<.sort_link
event={@event}
field={@field}
label={@label}
target={@target}
/>
<% else %>
<.link patch={
Flop.Phoenix.build_path(
@path,
Flop.push_order(@meta.flop, @field),
backend: @meta.backend,
for: @meta.schema
)
}>
<%= @label %>
</.link>
<% end %>
<.arrow direction={@order_direction} opts={@opts} />
</span>
</th>
<% else %>
<th {@opts[:thead_th_attrs]}><%= @label %></th>
<% end %>
"""
end
defp aria_sort(0, direction), do: direction_to_aria(direction)
defp aria_sort(_, _), do: nil
defp direction_to_aria(:desc), do: "descending"
defp direction_to_aria(:desc_nulls_last), do: "descending"
defp direction_to_aria(:desc_nulls_first), do: "descending"
defp direction_to_aria(:asc), do: "ascending"
defp direction_to_aria(:asc_nulls_last), do: "ascending"
defp direction_to_aria(:asc_nulls_first), do: "ascending"
attr :direction, :atom, required: true
attr :opts, :list, required: true
defp arrow(assigns) do
~H"""
<%= if @direction in [:asc, :asc_nulls_first, :asc_nulls_last] do %>
<span {@opts[:symbol_attrs]}><%= @opts[:symbol_asc] %></span>
<% end %>
<%= if @direction in [:desc, :desc_nulls_first, :desc_nulls_last] do %>
<span {@opts[:symbol_attrs]}><%= @opts[:symbol_desc] %></span>
<% end %>
<%= if is_nil(@direction) && !is_nil(@opts[:symbol_unsorted]) do %>
<span {@opts[:symbol_attrs]}><%= @opts[:symbol_unsorted] %></span>
<% end %>
"""
end
attr :field, :atom, required: true
attr :label, :string, required: true
attr :event, :string, required: true
attr :target, :string, required: true
defp sort_link(assigns) do
~H"""
<.link {sort_link_attrs(@field, @event, @target)}>
<%= @label %>
</.link>
"""
end
defp sort_link_attrs(field, event, target) do
[phx_value_order: field, to: "#"]
|> Misc.maybe_put(:phx_click, event)
|> Misc.maybe_put(:phx_target, target)
end
defp order_index(%Flop{order_by: nil}, _), do: nil
defp order_index(%Flop{order_by: order_by}, field) do
Enum.find_index(order_by, &(&1 == field))
end
defp order_direction(_, nil), do: nil
defp order_direction(nil, _), do: :asc
defp order_direction(directions, index), do: Enum.at(directions, index)
defp is_sortable?(nil, _), do: false
defp is_sortable?(_, nil), do: true
defp is_sortable?(field, module) do
field in (module |> struct() |> Flop.Schema.sortable())
end
end