Skip to content

Commit

Permalink
WIP: move pagination logic and components into a shared module
Browse files Browse the repository at this point in the history
continuation of #96
  • Loading branch information
leandrocp committed Feb 2, 2024
1 parent 63a37ac commit 1633cf6
Show file tree
Hide file tree
Showing 6 changed files with 175 additions and 109 deletions.
52 changes: 44 additions & 8 deletions lib/beacon/live_admin/components/admin_components.ex
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ defmodule Beacon.LiveAdmin.AdminComponents do

alias Phoenix.LiveView.JS
alias Beacon.LiveAdmin.CoreComponents
alias Beacon.LiveAdmin.PageBuilder.Table

import Beacon.LiveAdmin.Gettext
import Beacon.LiveAdmin.Router, only: [beacon_live_admin_path: 3]
Expand Down Expand Up @@ -623,20 +624,55 @@ defmodule Beacon.LiveAdmin.AdminComponents do
end

@doc """
Renders navigation by defined pagination.
Renders a search input text to filter table results.
"""
attr :table, Beacon.LiveAdmin.PageBuilder.Table, required: true
attr :placeholder, :string

def table_search(assigns) do
~H"""
<.simple_form :let={f} for={%{}} as={:search} phx-change="beacon:table-search">
<.input type="search" field={f[:query]} value={@table.query} autofocus={true} placeholder={@placeholder || "Search"} phx-debounce={200} />
</.simple_form>
"""
end

@doc """
Renders a select input to sort table results.
"""
attr :table, Beacon.LiveAdmin.PageBuilder.Table, required: true
attr :options, :list, required: true

def table_sort(assigns) do
~H"""
<.simple_form :let={f} for={%{}} as={:sort} phx-change="beacon:table-sort">
<.input type="select" field={f[:sort]} value={@table.sort} options={@options} />
</.simple_form>
"""
end

@doc """
Renders pagination to nagivate table results.
"""
attr :current_page, :integer, required: true
attr :pages, :integer, required: true
attr :socket, Phoenix.LiveView.Socket, required: true
attr :page, Beacon.LiveAdmin.PageBuilder.Page, required: true

def table_pagination(assigns) do
assigns = assign(assigns, :table, assigns.page.table)

def pagination(assigns) do
~H"""
<div class="flex flex-row justify-center space-x-6 pt-8 text-xl font-semibold">
<button phx-click="prev-page" disabled={@current_page == 1} class="px-2 font-medium disabled:text-gray-400">&#8592; prev</button>
<button :for={page <- 1..@pages} phx-click="set-page" phx-value-page={page} class={if @current_page == page, do: "text-indigo-700", else: ""}>
<.link patch={Table.prev_path(@socket, @page)}>
<.icon name="hero-arrow-long-left-solid" class="mr-2" /> prev
</.link>
<.link :for={page <- 1..@table.pages} patch={Table.goto_path(@socket, @page, page)} class={if @table.page == page, do: "text-indigo-700", else: ""}>
<%= page %>
</button>
<button phx-click="next-page" disabled={@current_page == @pages} class="px-2 font-medium disabled:text-gray-400">next &#8594;</button>
</.link>
<.link patch={Table.next_path(@socket, @page)}>
next <.icon name="hero-arrow-long-right-solid" class="mr-2" />
</.link>
</div>
"""
end
Expand Down
4 changes: 2 additions & 2 deletions lib/beacon/live_admin/content.ex
Original file line number Diff line number Diff line change
Expand Up @@ -97,8 +97,8 @@ defmodule Beacon.LiveAdmin.Content do
call(site, Beacon.Content, :list_pages, [site, opts])
end

def count_pages(site) do
call(site, Beacon.Content, :count_pages, [site])
def count_pages(site, opts \\ []) do
call(site, Beacon.Content, :count_pages, [site, opts])
end

def change_page_variant(site, variant, attrs \\ %{}) do
Expand Down
118 changes: 21 additions & 97 deletions lib/beacon/live_admin/live/page_editor_live/index.ex
Original file line number Diff line number Diff line change
@@ -1,100 +1,36 @@
defmodule Beacon.LiveAdmin.PageEditorLive.Index do
@moduledoc false

use Beacon.LiveAdmin.PageBuilder
use Beacon.LiveAdmin.PageBuilder, table: [ per_page: 2, sort: "title" ]

alias Beacon.LiveAdmin.Content

on_mount {Beacon.LiveAdmin.Hooks.Authorized, {:page_editor, :index}}

@per_page 20
@default_sort :title

@impl true
def menu_link(_, :index), do: {:root, "Pages"}

@impl true
def mount(_params, _session, socket) do
{:ok,
socket
|> assign(
page: 1,
pages: number_of_pages(socket.assigns.beacon_page.site),
sort: @default_sort,
query: ""
)
|> stream_configure(:pages, dom_id: &"#{Ecto.UUID.generate()}-#{&1.id}")}
{:ok, stream_configure(socket, :pages, dom_id: &"#{Ecto.UUID.generate()}-#{&1.id}")}
end

@impl true
def handle_params(params, _uri, socket) do
query = params["query"]
offset = set_offset(params["page"])
sort = set_sort(params["sort"], socket)
socket = set_page(offset, params["page"], socket)
socket = Table.handle_params(socket, params, &Content.count_pages(&1.site))

%{site: site} = socket.assigns.beacon_page
%{per_page: per_page, offset: offset, query: query, sort: sort} = socket.assigns.beacon_page.table

pages =
list_pages(socket.assigns.beacon_page.site,
per_page: @per_page,
list_pages(site,
per_page: per_page,
offset: offset,
query: query,
sort: sort
)

{:noreply,
socket
|> assign(sort: sort, query: query)
|> stream(:pages, pages, reset: true)}
end

@impl true
def handle_event("search", %{"search" => %{"query" => query, "sort" => sort}}, socket) do
path =
beacon_live_admin_path(
socket,
socket.assigns.beacon_page.site,
"/pages?page=#{socket.assigns.page}&sort=#{sort}#{query_param(query)}"
)

{:noreply,
socket
|> assign(sort: set_sort(sort, socket))
|> push_patch(to: path)}
end

def handle_event("set-page", %{"page" => page}, socket) do
page
|> String.to_integer()
|> set_page(socket)
end

def handle_event("prev-page", _, %{assigns: %{page: 1}} = socket) do
{:noreply, socket}
end

def handle_event("prev-page", _, socket) do
set_page(socket.assigns.page - 1, socket)
end

def handle_event("next-page", _, %{assigns: %{page: page, pages: page}} = socket) do
{:noreply, socket}
end

def handle_event("next-page", _, socket) do
set_page(socket.assigns.page + 1, socket)
end

defp set_page(page, socket) do
path =
beacon_live_admin_path(
socket,
socket.assigns.beacon_page.site,
"/pages?page=#{page}"
)

{:noreply,
socket
|> assign(page: page)
|> push_patch(to: path)}
{:noreply, stream(socket, :pages, pages, reset: true)}
end

@impl true
Expand All @@ -109,16 +45,16 @@ defmodule Beacon.LiveAdmin.PageEditorLive.Index do
</:actions>
</.header>
<.simple_form :let={f} for={%{}} as={:search} phx-change="search">
<div class="flex justify-between">
<div class="basis-10/12">
<.input field={f[:query]} value={@query} type="search" autofocus={true} placeholder="Search by path or title (showing up to 20 results)" />
</div>
<div :if={@pages > 0} class="basis-1/12">
<.input type="select" field={f[:sort]} value={@sort} options={[{"Title", "title"}, {"Path", "path"}]} />
</div>
<%= inspect(@beacon_page) %>
<div class="flex justify-between">
<div class="basis-10/12">
<.table_search table={@beacon_page.table} placeholder="Search by path or title (showing up to 20 results)" />
</div>
</.simple_form>
<div class="basis-1/12">
<.table_sort table={@beacon_page.table} options={[{"Title", "title"}, {"Path", "path"}]} />
</div>
</div>
<.main_content class="h-[calc(100vh_-_210px)]">
<.table id="pages" rows={@streams.pages} row_click={fn {_dom_id, page} -> JS.navigate(beacon_live_admin_path(@socket, @beacon_page.site, "/pages/#{page.id}")) end}>
Expand All @@ -135,7 +71,8 @@ defmodule Beacon.LiveAdmin.PageEditorLive.Index do
</:action>
</.table>
<.pagination :if={@pages > 0} current_page={@page} pages={@pages} />
<%!-- <.pagination :if={@pages > 0} current_page={@beacon_page.table.page} pages={@pages} /> --%>
<.table_pagination socket={@socket} page={@beacon_page} />
</.main_content>
"""
end
Expand All @@ -148,19 +85,6 @@ defmodule Beacon.LiveAdmin.PageEditorLive.Index do
end)
end

defp number_of_pages(site) do
site
|> Content.count_pages()
|> Kernel./(@per_page)
|> ceil()
end

defp set_offset(nil), do: 0
defp set_offset(page) when is_binary(page), do: String.to_integer(page) * @per_page - @per_page
defp set_offset(page), do: page * @per_page - @per_page

defp set_page(0, _page, socket), do: socket
defp set_page(_offset, page, socket), do: assign(socket, page: String.to_integer(page))

defp set_sort(nil, socket), do: socket.assigns.sort
defp set_sort("", socket), do: socket.assigns.sort
Expand Down
10 changes: 9 additions & 1 deletion lib/beacon/live_admin/page_builder.ex
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ end

defmodule Beacon.LiveAdmin.PageBuilder.Page do
@moduledoc false
defstruct site: nil, path: nil, module: nil, params: %{}, session: %{}
defstruct site: nil, path: nil, module: nil, params: %{}, session: %{}, table: nil
end

# https://github.com/phoenixframework/phoenix_live_dashboard/blob/32fef8da6a7df97f92f05bd6e7aab33be4036490/lib/phoenix/live_dashboard/page_builder.ex
Expand All @@ -19,6 +19,7 @@ defmodule Beacon.LiveAdmin.PageBuilder do
"""

use Phoenix.Component
alias Beacon.LiveAdmin.PageBuilder.Table
alias Phoenix.LiveView.Socket

@type session :: map()
Expand Down Expand Up @@ -71,6 +72,7 @@ defmodule Beacon.LiveAdmin.PageBuilder do

import Phoenix.LiveView
alias Phoenix.LiveView.JS
alias Beacon.LiveAdmin.PageBuilder.Table

@behaviour Beacon.LiveAdmin.PageBuilder

Expand All @@ -79,6 +81,12 @@ defmodule Beacon.LiveAdmin.PageBuilder do

def init(opts), do: {:ok, opts}
defoverridable init: 1

def __beacon_page_table__ do
unquote(opts)
|> Keyword.get(:table)
|> Table.build()
end
end
end

Expand Down
65 changes: 65 additions & 0 deletions lib/beacon/live_admin/page_builder/table.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
defmodule Beacon.LiveAdmin.PageBuilder.Table do
defstruct [:per_page, :page, :pages, :offset, :sort, :query]

import Beacon.LiveAdmin.Router, only: [beacon_live_admin_path: 4]
alias Beacon.LiveAdmin.PageBuilder.Page
alias Beacon.LiveAdmin.PageBuilder.Table
alias Phoenix.LiveView.Socket

def build(opts) when is_list(opts) do
per_page = Keyword.get(opts, :per_page, 20)
sort = Keyword.get(opts, :sort) || raise ":sort is required in :table options"

%__MODULE__{per_page: per_page, page: 1, pages: 0, offset: 0, sort: sort, query: nil}
end

def build(_opts), do: nil

def update(%Socket{} = socket, new_table) when is_list(new_table) do
new_table = Map.new(new_table)

Phoenix.Component.update(socket, :beacon_page, fn page ->
table = Map.merge(page.table, new_table)
Map.put(page, :table, table)
end)
end

def prev_path(socket, %Page{site: site, path: path, table: %{page: page} = table}) do
page = if page <= 1, do: 1, else: page - 1
query_params = query_params(table, page: page)
beacon_live_admin_path(socket, site, path, query_params)
end

def next_path(socket, %Page{site: site, path: path, table: %{page: page, pages: pages} = table}) do
page = if page >= pages, do: page, else: page + 1
query_params = query_params(table, page: page)
beacon_live_admin_path(socket, site, path, query_params)
end

def goto_path(socket, %Page{site: site, path: path, table: table}, page) do
query_params = query_params(table, page: page)
beacon_live_admin_path(socket, site, path, query_params)
end

def handle_params(socket, params, count_fn) do
%{per_page: per_page, sort: sort} = socket.assigns.beacon_page.table

page = params |> Map.get("page", "1") |> String.to_integer()
pages = ceil(count_fn.(socket.assigns.beacon_page) / per_page)
offset = page * per_page - per_page
# TODO: revisit safe_to_atom
sort = params |> Map.get("sort", sort) |> Beacon.Types.Atom.safe_to_atom()
query = Map.get(params, "query", nil)

update(socket, page: page, pages: pages, offset: offset, sort: sort, query: query)
end

def query_params(%Table{} = table, new_params) when is_list(new_params) do
new_params = Map.new(new_params)

# TODO remove empty k/v
table
|> Map.take([:page, :sort, :query])
|> Map.merge(new_params)
end
end
Loading

0 comments on commit 1633cf6

Please sign in to comment.