Skip to content

Commit

Permalink
feat: add infinity scroll/search example
Browse files Browse the repository at this point in the history
  • Loading branch information
colindensem committed Jul 8, 2022
1 parent cb7db55 commit 557390c
Show file tree
Hide file tree
Showing 7 changed files with 205 additions and 5 deletions.
12 changes: 10 additions & 2 deletions assets/js/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,18 @@ import "phoenix_html"
import {Socket} from "phoenix"
import {LiveSocket} from "phoenix_live_view"
import topbar from "../vendor/topbar"
import InfinityScroll from "./infinity-scroll"

let Hooks = {}
Hooks.InfinityScroll = InfinityScroll;

let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content")
let liveSocket = new LiveSocket("/live", Socket, {params: {_csrf_token: csrfToken}})
let liveSocket = new LiveSocket("/live", Socket, {
params: { _csrf_token: csrfToken },
hooks: Hooks,
})



// Show progress bar on live navigation and form submits
topbar.config({barColors: {0: "#29d"}, shadowColor: "rgba(0, 0, 0, .3)"})
Expand All @@ -42,4 +51,3 @@ liveSocket.connect()
// >> liveSocket.enableLatencySim(1000) // enabled for duration of browser session
// >> liveSocket.disableLatencySim()
window.liveSocket = liveSocket

29 changes: 29 additions & 0 deletions assets/js/infinity-scroll.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/***
* Hook to send push event "load-more" to server on window scroll events
*/
export default {
rootElement() {
return (
document.documentElement || document.body.parentNode || document.body
);
},
scrollPosition() {
const { scrollTop, clientHeight, scrollHeight } = this.rootElement();
return ((scrollTop + clientHeight) / scrollHeight) * 100;
},

mounted() {
this.threshold = 95;
this.lastScrollPosition = 0;

window.addEventListener("scroll", () => {
const currentScrollPosition = this.scrollPosition();
const isCloseToBottom =
currentScrollPosition > this.threshold &&
this.lastScrollPosition <= this.threshold;

if (isCloseToBottom) this.pushEvent("load-more", {});
this.lastScrollPosition = currentScrollPosition;
});
},
};
27 changes: 25 additions & 2 deletions lib/ascend/hills.ex
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,28 @@ defmodule Ascend.Hills do
[%Hill{}, ...]
"""
def list_hills(opts) do
def list_hills(opts \\ %{}) do
from(h in Hill)
|> filter(opts)
|> sort(opts)
|> Repo.all()
end

@doc """
Returns a count of hills.
## Examples
iex> hill_count()
10
"""
def hill_count(opts \\ %{}) do
from(h in Hill)
|> filter(opts)
|> Repo.aggregate(:count)
end

@doc """
Returns the list of hills with a total count
Expand All @@ -32,7 +47,7 @@ defmodule Ascend.Hills do
%{hills: [%Hill{}, ...], total_count: 2}
"""
def list_hills_with_total_count(opts) do
def list_hills_with_total_count(opts \\ %{}) do
query = from(h in Hill) |> filter(opts)

total_count = Repo.aggregate(query, :count)
Expand All @@ -46,6 +61,14 @@ defmodule Ascend.Hills do
%{hills: result, total_count: total_count}
end

def list_hills_with_pagination(offset, limit, opts \\ %{}) do
from(h in Hill)
|> filter(opts)
|> limit(^limit)
|> offset(^offset)
|> Repo.all()
end

defp paginate(query, %{page: page, page_size: page_size})
when is_integer(page) and is_integer(page_size) do
offset = max(page - 1, 0) * page_size
Expand Down
1 change: 0 additions & 1 deletion lib/ascend_web/live/filter_component.ex
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ defmodule AscendWeb.Live.FilterComponent do
def handle_event("search", %{"filter" => filter}, socket) do
case FilterForm.parse(filter) do
{:ok, opts} ->
IO.puts("Send self 1")
send(self(), {:update, opts})
{:noreply, socket}

Expand Down
117 changes: 117 additions & 0 deletions lib/ascend_web/live/infinity_live.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
defmodule AscendWeb.InfinityLive do
use AscendWeb, :live_view

alias Ascend.Hills
alias AscendWeb.Forms.FilterForm

def render(assigns) do
~H"""
<h1>Listing <%= @count %> Hills</h1>
<.live_component
module={AscendWeb.Live.FilterComponent}
id="filter"
filter={@filter}
/>
<table>
<tbody id={"hills-#{@count}"} phx-update="append" phx-hook="InfinityScroll">
<%= for hill <- @hills do %>
<tr id={"hill-#{hill.id}"}>
<td>
<%= live_redirect "#{hill.name}", to: Routes.hill_show_path(@socket, :show, hill) %>
</td>
<td><%= hill.dobih_id %></td>
<td><%= hill.metres %></td>
<td><%= hill.feet %></td>
<td><%= hill.grid_ref %></td>
<td><%= hill.classification %></td>
<td><%= hill.region %></td>
<td><%= hill.area %></td>
</tr>
<% end %>
</tbody>
</table>
"""
end

def mount(_params, _session, socket) do
count = Hills.hill_count()

socket =
socket
|> assign(offset: 0, limit: 25, count: count)

{:ok, socket, temporary_assigns: [hills: []]}
end

def handle_params(params, _url, socket) do
socket =
socket
|> assign(:page_title, "Listing Hills")
|> parse_params(params)
|> load_hills()

{:noreply, socket}
end

def handle_event("load-more", _params, socket) do
%{offset: offset, limit: limit, count: count} = socket.assigns

socket =
if offset < count do
socket
|> assign(offset: offset + limit)
|> load_hills()
else
socket
end

{:noreply, socket}
end

def handle_info({:update, opts}, socket) do
params = merge_and_sanitize_params(socket, opts)
path = Routes.live_path(socket, AscendWeb.InfinityLive, params)

socket =
socket
|> assign(offset: 0)
|> assign(limit: 25)

{:noreply, push_patch(socket, to: path, replace: true)}
end

defp merge_and_sanitize_params(socket, overrides \\ %{}) do
%{filter: filter} = socket.assigns

%{}
|> Map.merge(filter)
|> Map.merge(overrides)
|> Enum.reject(fn {_key, value} -> is_nil(value) end)
|> Map.new()
end

defp parse_params(socket, params) do
with {:ok, filter_opts} <- FilterForm.parse(params) do
socket
|> assign_filter(filter_opts)
else
_error ->
socket
|> assign_filter()
end
end

defp assign_filter(socket, overrides \\ %{}) do
assign(socket, :filter, FilterForm.default_values(overrides))
end

defp load_hills(socket) do
%{offset: offset, limit: limit} = socket.assigns

params = merge_and_sanitize_params(socket)

socket
|> assign(:hills, Hills.list_hills_with_pagination(offset, limit, params))
|> assign(:count, Hills.hill_count(params))
end
end
1 change: 1 addition & 0 deletions lib/ascend_web/router.ex
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ defmodule AscendWeb.Router do
pipe_through :browser

live "/", HillLive.Index, :index
live "/infinity", InfinityLive

live "/hills/:id", HillLive.Show, :show
end
Expand Down
23 changes: 23 additions & 0 deletions test/ascend/hills_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,29 @@ defmodule Ascend.HillsTest do
}
end

test "hill_count/1 returns a count" do
insert(:hill)
assert Hills.hill_count(%{}) == 1
end

test "hill_count/1 returns a filtered count" do
insert(:hill, name: "Z Hill")
insert(:hill, name: "A Hill1")
insert(:hill, name: "A Hill2")
insert(:hill, name: "A Hill3")

assert Hills.hill_count(%{name: "a"}) == 3
end

test "list_hills_with_pagination/3" do
hill1 = insert(:hill, name: "A Hill1")
hill2 = insert(:hill, name: "A Hill2")
hill3 = insert(:hill, name: "A Hill3")

assert Hills.list_hills_with_pagination(0, 1, %{}) == [hill1]
assert Hills.list_hills_with_pagination(1, 2, %{}) == [hill2, hill3]
end

test "get_hill!/1 returns the hill with given id" do
hill = insert(:hill)
assert Hills.get_hill!(hill.id) == hill
Expand Down

0 comments on commit 557390c

Please sign in to comment.