Last active
April 28, 2021 17:37
-
-
Save cblavier/52a4ea88cde6069f079add93f0a3600e to your computer and use it in GitHub Desktop.
Table live_component with phx_component_helpers
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
= live_component @socket, Table, | |
headers: [id: "ID", last_name: "Nom et email", actions: ""], | |
sortable: [:id, :last_name], | |
selectable: :checkbox, | |
mobile_hidden: [:id, :actions], | |
current_sorting: @table_sorting, | |
row_count: @user_count, | |
selected_row_count: @selected_user_count, | |
sticky_header: true do | |
= for user <- @users do | |
= live_component @socket, TableRow, id: "user-table-row-#{user.id}", | |
select_id: user.id, selectable: @selectable, selected: user.selected do | |
= live_component @socket, TableCell, class: "hidden md:table-cell" do | |
= user.id | |
= live_component @socket, TableCell do | |
.text-default-txt= "#{user.first_name} #{user.last_name}" | |
.text-sm.text-default-txt-informative= user.email | |
= live_component @socket, TableButtonCell, class: "hidden md:table-cell" do | |
= live_component @socket, TableButton, icon: "fas fa-trash text-red-400", | |
phx_click: "delete_user", phx_value_user_id: "#{user.id}", | |
confirm: "Confirmer la suppression ?" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
defmodule Storybook.Components.Table do | |
use Storybook, :live_component | |
alias Storybook.Components.IllustrationPlaceholder | |
@default_wrapper_class "grid gap-y-2 md:px-2 pt-2" | |
@default_table_class "rounded-md shadow border-b border-default-border min-w-full divide-y divide-default-border" | |
@default_thead_class "bg-default-bg" | |
@default_selection_input_class "table-select-all h-4 w-4 mt-0.5 cursor-pointer text-primary-txt rounded border-default-border focus:ring-primary-btn-focus" | |
@default_th_class "bg-default-bg px-2 md:px-6 py-3 text-left text-xs font-medium text-default-txt uppercase tracking-wider" | |
@default_tbody_class "" | |
def update(assigns, socket) do | |
assigns = | |
assigns | |
|> extend_class(@default_table_class) | |
|> extend_class(@default_wrapper_class, attribute: :wrapper_class) | |
|> extend_class(@default_thead_class, attribute: :thead_class) | |
|> extend_class(@default_selection_input_class, attribute: :selection_input_class) | |
|> extend_class(&default_th_class/1, attribute: :th_class) | |
|> extend_class(@default_tbody_class, attribute: :tbody_class) | |
|> set_attributes([ | |
phx_sort_event: "sort_table", | |
phx_select_all_event: "select_all_table", | |
placeholder_name: :empty, | |
placeholder_text: "Aucun résultat", | |
id: "table-component" | |
) | |
|> set_headers() | |
{:ok, assign(socket, assigns)} | |
end | |
def render(assigns) do | |
~L""" | |
<%= if assigns[:row_count] == 0 do %> | |
<%= live_component @socket, IllustrationPlaceholder, forward_assigns(assigns, prefix: :placeholder) %> | |
<% else %> | |
<div <%= @raw_wrapper_class %> <%= @raw_id %> phx-hook="TableHook"> | |
<table <%= @raw_class %>> | |
<%= if Enum.any?(@headers) do %> | |
<thead <%= @raw_thead_class%>> | |
<tr> | |
<%= for header <- @headers do %> | |
<th scope="col" class="<%= th_class(header, assigns)%>" colspan="<%= th_colspan(header, assigns) %>"> | |
<%= render_header(header, assigns) %> | |
</th> | |
<% end %> | |
</tr> | |
</thead> | |
<% end %> | |
<tbody <%= @tbody_class%>> | |
<%= render_block(@inner_block, selectable: assigns[:selectable]) %> | |
</tbody> | |
</table> | |
</div> | |
<% end %> | |
""" | |
end | |
defp default_th_class(assigns) do | |
if assigns[:sticky_header] do | |
@default_th_class <> " sticky -top-2" | |
else | |
@default_th_class | |
end | |
end | |
defp th_class(:select_all, assigns), do: assigns[:th_class] | |
defp th_class({name, _label}, assigns) do | |
if assigns[:mobile_hidden] && Enum.member?(assigns[:mobile_hidden], name) do | |
assigns[:th_class] <> " hidden md:table-cell" | |
else | |
assigns[:th_class] | |
end | |
end | |
defp th_colspan(:select_all, assigns) do | |
if length(assigns.headers) == 1, do: 100, else: 1 | |
end | |
defp th_colspan(_, _assigns), do: 1 | |
defp render_header(:select_all, assigns) do | |
selected_status = | |
cond do | |
assigns[:selected_row_count] == 0 -> "unchecked" | |
assigns[:selected_row_count] == assigns[:row_count] -> "checked" | |
true -> "indeterminate" | |
end | |
~L""" | |
<input type="checkbox" <%= @raw_selection_input_class %> phx-click="<%= @phx_select_all_event %>" | |
data-checked="<%= selected_status %>"> | |
</input> | |
""" | |
end | |
defp render_header({name, label}, assigns) do | |
~L""" | |
<span><%= label %><span> | |
<%= render_header_sorting_status(name, assigns) %> | |
""" | |
end | |
defp render_header_sorting_status(name, assigns) do | |
if assigns[:sortable] && Enum.member?(assigns[:sortable], name) do | |
sorting = assigns[:current_sorting] | |
cond do | |
sorting && sorting[name] == :asc -> | |
render_sort_icon("fa-sort-up", name, "desc", assigns) | |
sorting && sorting[name] == :desc -> | |
render_sort_icon("fa-sort-down", name, "none", assigns) | |
true -> | |
render_sort_icon("fa-sort", name, "asc", assigns) | |
end | |
else | |
"" | |
end | |
end | |
defp render_sort_icon(icon, name, sort_order, assigns) do | |
~L""" | |
<i class="fas <%= icon %> cursor-pointer text-default-txt hover:text-default-txt-hover" | |
phx-click="<%= @phx_sort_event %>" | |
phx-value-attribute="<%= name %>" | |
phx-value-order="<%= sort_order %>" | |
</i> | |
""" | |
end | |
defp set_headers(assigns) do | |
headers = Map.get(assigns, :headers, []) | |
headers = | |
if assigns[:selectable] == :checkbox do | |
[:select_all | headers] | |
else | |
headers | |
end | |
Map.put(assigns, :headers, headers) | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
defmodule Storybook.Components.Table.TableCell do | |
use Storybook, :live_component | |
def update(assigns, socket) do | |
assigns = | |
assigns | |
|> extend_class("px-2 md:px-6 py-2 md:py-4 whitespace-nowrap text-sm font-medium text-default-txt") | |
|> set_attributes([:colspan]) | |
{:ok, assign(socket, assigns)} | |
end | |
def render(assigns) do | |
~L""" | |
<td <%= @raw_class %> <%= @raw_colspan %>> | |
<%= render_block(@inner_block) %> | |
</td> | |
""" | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
defmodule Storybook.Components.Table.TableRow do | |
use Storybook, :live_component | |
alias Storybook.Components.Table.TableCell | |
def update(assigns, socket) do | |
assigns = | |
assigns | |
|> extend_class( | |
"even:bg-default-bg odd:bg-white hover:bg-primary-bg border-l-2 border-transparent\ | |
hover:border-primary flex-none group cursor-pointer" | |
) | |
|> extend_class(&default_selection_input_class/1, attribute: :selection_input_class) | |
|> set_attributes([:id, phx_select_event: "select_row"]) | |
|> set_phx_attributes() | |
{:ok, assign(socket, assigns)} | |
end | |
def render(assigns) do | |
~L""" | |
<tr <%= @raw_class %> <%= @raw_id %> <%= @raw_phx_attributes %>> | |
<%= render_selection_cell(assigns) %> | |
<%= render_block(@inner_block) %> | |
</tr> | |
""" | |
end | |
defp render_selection_cell(assigns) do | |
case assigns[:selectable] do | |
:checkbox -> render_selection_input(assigns, "checkbox") | |
:radio -> render_selection_input(assigns, "radio") | |
_ -> nil | |
end | |
end | |
# setting a useless data attribute (checked should be enough) on input | |
# otherwise liveview diff ignores the checked update | |
defp render_selection_input(assigns, type) do | |
selected = assigns[:selected] == true | |
raw_checked = if selected, do: {:safe, "checked=\"true\""}, else: "" | |
~L""" | |
<%= live_component nil, TableCell do %> | |
<input type="<%= type %>" <%= @raw_selection_input_class %> | |
phx-click="<%= @phx_select_event %>" phx-value-id="<%= @select_id %>" | |
<%= raw_checked %> data-selected="<%= assigns[:selected] %>"> | |
</input> | |
<% end %> | |
""" | |
end | |
defp default_selection_input_class(assigns) do | |
class = "h-4 w-4 mt-0.5 cursor-pointer text-primary-txt border-default-border\ | |
focus:ring-primary-btn-focus" | |
case assigns[:selectable] do | |
:checkbox -> class <> " rounded" | |
_ -> class | |
end | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
export const TableHook = { | |
mounted () { | |
renderTable(this.el, this) | |
}, | |
updated () { | |
renderTable(this.el, this) | |
} | |
} | |
function renderTable (el, hook) { | |
const selectAllCheckbox = el.querySelector('input.table-select-all') | |
if (selectAllCheckbox) { | |
if (selectAllCheckbox.dataset.checked == "checked") { | |
selectAllCheckbox.checked = true; | |
} else if (selectAllCheckbox.dataset.checked == "unchecked") { | |
selectAllCheckbox.checked = false; | |
} else { | |
selectAllCheckbox.indeterminate = true; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment