defmodule PhoenixBetterTable do @moduledoc """ A table component whose contents can be sorted by clicking on each header. ## Assigns * `:meta` - a map containing a `:headers` key: * `headers:` - a list of maps, each representing a header in the table: * `:id` - the column's id, which will be used as the key for rendering and sorting * `:label` - the column's label (optional) * `:sort` - either a boolean indicating whether the column is sortable (optional, default true), or a compare/2 function that returns true if the first argument precedes or is in the same place as the second one. * `:render` - an optional component that renders cells in the column * `:rows` - a list of maps, each representing a row in the table * `:sort` - a tuple containing the column id and the sort order (`:asc` or `:desc`) (optional) * `:class` - a string containing additional classes to be added to the table (optional) """ use Phoenix.LiveComponent import Phoenix.LiveView.TagEngine, only: [component: 3] @impl true def handle_event( "sort", %{"header" => header_id}, socket ) do sort_column = String.to_existing_atom(header_id) socket = update(socket, :sort, fn nil -> {sort_column, :asc} {^sort_column, :asc} -> {sort_column, :desc} {^sort_column, :desc} -> {sort_column, :asc} _ -> {sort_column, :asc} end) <<<<<<< HEAD {:noreply, process_rows(socket)} ||||||| parent of f96a43f (WIP: basic filter support) {:noreply, assign(socket, :processed_rows, update_rows(rows, socket.assigns.sort, meta))} ======= {:noreply, process_rows(socket)} end def handle_event( "filter_toggle", %{"header" => header_id}, socket ) do filter_column = String.to_existing_atom(header_id) socket = update(socket, :filter, fn %{^filter_column => _} -> Map.put(socket.assigns.filter, filter_column, "") _ -> Map.put(socket.assigns.filter, filter_column, "") end) {:noreply, socket} end def handle_event( "filter_change", %{"header" => header_id, "value" => value}, socket ) do filter_column = String.to_existing_atom(header_id) socket = update(socket, :filter, fn state -> Map.put(state, filter_column, value) end) {:noreply, process_rows(socket)} >>>>>>> f96a43f (WIP: basic filter support) end @impl true def mount(socket) do {:ok, socket} end @impl true def update(%{meta: %{headers: []}}, _socket) do raise ArgumentError, "meta.headers must not be empty" end def update(assigns, socket) do socket |> assign(assigns) |> assign_new(:sort, fn -> nil end) |> assign_new(:filter, fn -> %{} end) |> assign_new(:class, fn -> "" end) |> process_rows() |> then(&{:ok, &1}) end # <<<<<<< HEAD defp process_rows(%{assigns: %{rows: rows}} = socket) do assign(socket, :processed_rows, rows |> sort_rows(socket)) ||||||| parent of f96a43f (WIP: basic filter support) defp update_rows(rows, nil, _meta) do rows ======= defp process_rows(%{assigns: %{rows: rows}} = socket) do assign(socket, :processed_rows, rows |> filter_rows(socket) |> sort_rows(socket)) end defp sort_rows(rows, %{assigns: %{sort: {column, order}, meta: %{headers: headers}}}) do sorter = column_sorter(order, Enum.find(headers, &(&1.id == column))) Enum.sort_by(rows, &Map.get(&1, column), sorter) end defp sort_rows(rows, _socket) do rows >>>>>>> f96a43f (WIP: basic filter support) end <<<<<<< HEAD defp sort_rows(rows, %{assigns: %{sort: {column, order}, meta: %{headers: headers}}}) do sorter = column_sorter(order, Enum.find(headers, &(&1.id == column))) Enum.sort_by(rows, &Map.get(&1, column), sorter) ||||||| parent of f96a43f (WIP: basic filter support) defp update_rows(rows, {column, order}, %{headers: headers}) do sorter = column_sorter(order, Enum.find(headers, &(&1.id == column))) Enum.sort_by(rows, &Map.get(&1, column), sorter) ======= defp filter_rows(rows, %{assigns: %{filter: nil}}) do rows end defp filter_rows(rows, %{assigns: %{filter: filter}}) do Enum.filter(rows, fn row -> Enum.all?(filter, fn {column, value} -> String.contains?(to_string(Map.get(row, column)), value) end) end) >>>>>>> f96a43f (WIP: basic filter support) end defp sort_rows(rows, _socket) do rows end # defp column_sorter(order, %{sort: sort}) when is_function(sort) do if order == :asc, do: sort, else: &sort.(&2, &1) end defp column_sorter(order, %{sort: module}) when is_atom(module) do {order, module} end defp column_sorter(order, _), do: if(order == :asc, do: &<=/2, else: &>=/2) defp sort_arrow(nil, _column), do: "—" defp sort_arrow({column, order}, column) when order == :asc, do: "▲" defp sort_arrow({column, order}, column) when order == :desc, do: "▼" defp sort_arrow(_, _), do: "—" end