Skip to content

Commit c171fca

Browse files
committed
implemented pagination
1 parent 0889362 commit c171fca

17 files changed

Lines changed: 1510 additions & 32 deletions

File tree

.github/workflows/ci.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ jobs:
2929
strategy:
3030
fail-fast: false
3131
matrix:
32-
example: [bootstrap, custom_renderers_svg, getter, rest_datasource, selectable, simple, tailwind, generic]
32+
example: [ bootstrap, custom_renderers_svg, custom_row_renderer, editable, generic, getter, paginated_rest_datasource, pagination, selectable, simple, tailwind ]
3333
steps:
3434
- uses: actions/checkout@v3
3535
- uses: ./.github/actions/rust_toolchain/
@@ -42,7 +42,7 @@ jobs:
4242
publish:
4343
name: Publish
4444
runs-on: ubuntu-latest
45-
needs: [cargo_checks, test_examples]
45+
needs: [ cargo_checks, test_examples ]
4646
steps:
4747
- uses: actions/checkout@v3
4848
- uses: ./.github/actions/rust_toolchain/

.idea/leptos-struct-table.iml

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

examples/paginated_rest_datasource/src/data_provider.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,10 @@ impl PaginatedTableDataProvider<Book> for BookDataProvider {
5959
const PAGE_ROW_COUNT: usize = 50;
6060

6161
async fn get_page(&self, page_index: usize) -> Result<Vec<Book>, String> {
62+
if page_index >= 10000 / Self::PAGE_ROW_COUNT {
63+
return Ok(vec![]);
64+
}
65+
6266
let url = self.get_url(page_index);
6367

6468
let resp: ArchiveOrgApiResponse = Request::get(&url)

examples/pagination/Cargo.toml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
[package]
2+
name = "pagination"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
[dependencies]
7+
leptos = { version = "0.6", features = ["nightly", "csr"] }
8+
leptos-struct-table = { path = "../..", features = ["chrono"] }
9+
console_error_panic_hook = "0.1"
10+
console_log = "1"
11+
log = "0.4"
12+
serde = { version = "1", features = ["derive"] }
13+
async-trait = "0.1"
14+
gloo-net = { version = "0.5.0", features = ["http"] }
15+
16+
[dev-dependencies]
17+
wasm-bindgen = "0.2"
18+
wasm-bindgen-test = "0.3.0"
19+
web-sys = "0.3"

examples/pagination/README.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
Example that shows how to use pagination with a table.
2+
3+
If you don't have it installed already, install [Trunk](https://trunkrs.dev/)
4+
as well as the nightly toolchain for Rust and the wasm32-unknown-unknown target:
5+
6+
```bash
7+
cargo install trunk
8+
npm install -D tailwindcss
9+
rustup toolchain install nightly
10+
rustup target add wasm32-unknown-unknown
11+
```
12+
13+
Then, open two terminals. In the first one, run:
14+
15+
```
16+
npx tailwindcss -i ./input.css -o ./style/output.css --watch
17+
```
18+
19+
In the second one, run:
20+
21+
```bash
22+
trunk serve --open
23+
```

examples/pagination/index.html

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<link data-trunk rel="css" href="style/output.css">
5+
</head>
6+
<body></body>
7+
</html>

examples/pagination/input.css

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
@tailwind base;
2+
@tailwind components;
3+
@tailwind utilities;
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[toolchain]
2+
channel = "nightly"
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
use crate::models::{ArchiveOrgApiResponse, ArchiveOrgCountRespone, Book};
2+
use async_trait::async_trait;
3+
use gloo_net::http::Request;
4+
use leptos::logging;
5+
use leptos_struct_table::{ColumnSort, PaginatedTableDataProvider};
6+
use std::collections::VecDeque;
7+
8+
pub struct BookDataProvider {
9+
sorting: VecDeque<(usize, ColumnSort)>,
10+
}
11+
12+
impl Default for BookDataProvider {
13+
fn default() -> Self {
14+
Self {
15+
sorting: VecDeque::new(),
16+
}
17+
}
18+
}
19+
20+
impl BookDataProvider {
21+
fn url_sort_param_for_column(&self, column: usize) -> &'static str {
22+
match column {
23+
0 => "identifierSorter",
24+
1 => "titleSorter",
25+
2 => "creatorSorter",
26+
3 => "publicdate",
27+
_ => "",
28+
}
29+
}
30+
31+
fn url_sort_param_for_sort_pair(&self, pair: &(usize, ColumnSort)) -> String {
32+
let col = self.url_sort_param_for_column(pair.0);
33+
34+
let dir = match pair.1 {
35+
ColumnSort::Ascending => "asc",
36+
ColumnSort::Descending => "desc",
37+
ColumnSort::None => return "".to_string(),
38+
};
39+
40+
format!("&sort%5B%5D={}+{}", col, dir)
41+
}
42+
43+
fn get_url(&self, page_index: usize) -> String {
44+
let mut sort = String::new();
45+
for pair in &self.sorting {
46+
sort.push_str(&self.url_sort_param_for_sort_pair(pair));
47+
}
48+
49+
format!(
50+
"https://archive.org/advancedsearch.php?q=creator%3A%28Lewis%29&fl%5B%5D=creator&fl%5B%5D=identifier&fl%5B%5D=publicdate&fl%5B%5D=title{sort}&rows={}&page={}&output=json&callback=",
51+
Self::PAGE_ROW_COUNT,
52+
page_index + 1,
53+
)
54+
}
55+
}
56+
57+
#[async_trait(?Send)]
58+
impl PaginatedTableDataProvider<Book> for BookDataProvider {
59+
const PAGE_ROW_COUNT: usize = 50;
60+
61+
async fn get_page(&self, page_index: usize) -> Result<Vec<Book>, String> {
62+
if page_index >= 10000 / Self::PAGE_ROW_COUNT {
63+
return Ok(vec![]);
64+
}
65+
66+
let url = self.get_url(page_index);
67+
68+
let resp: ArchiveOrgApiResponse = Request::get(&url)
69+
.send()
70+
.await
71+
.map_err(|e| e.to_string())?
72+
.json()
73+
.await
74+
.map_err(|e| e.to_string())?;
75+
76+
match resp {
77+
ArchiveOrgApiResponse::Err { error } => Err(error),
78+
ArchiveOrgApiResponse::Ok { response } => Ok(response.docs),
79+
}
80+
}
81+
82+
async fn row_count(&self) -> Option<usize> {
83+
let resp: Option<ArchiveOrgCountRespone> = Request::get("https://archive.org/advancedsearch.php?q=creator%3A(Lewis)&fl[]=creator&fl[]=identifier&fl[]=publicdate&rows=0&page=0&output=json&callback=")
84+
.send()
85+
.await
86+
.map_err(|err| logging::error!("Failed to load count: {:?}", err))
87+
.ok()?
88+
.json()
89+
.await
90+
.map_err(|err| logging::error!("Failed to parse count response: {:?}", err))
91+
.ok();
92+
93+
// This API only allows to display up to 10000 results
94+
resp.map(|r| r.response.num_found.min(10000))
95+
}
96+
97+
fn set_sorting(&mut self, sorting: &VecDeque<(usize, ColumnSort)>) {
98+
self.sorting = sorting.clone();
99+
}
100+
}

examples/pagination/src/main.rs

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
mod data_provider;
2+
mod models;
3+
mod tailwind;
4+
5+
use crate::data_provider::BookDataProvider;
6+
use leptos::*;
7+
use leptos_struct_table::*;
8+
9+
#[component]
10+
pub fn App() -> impl IntoView {
11+
let rows = BookDataProvider::default();
12+
13+
let pagination_controller = PaginationController::default();
14+
15+
view! {
16+
<div class="rounded-md overflow-clip m-10 border dark:border-gray-700".to_string()>
17+
<table class="text-sm text-left text-gray-500 dark:text-gray-400 mb-[-1px]">
18+
<TableContent
19+
rows=rows
20+
display_strategy=DisplayStrategy::Pagination {
21+
controller: pagination_controller,
22+
row_count: 10,
23+
}
24+
/>
25+
26+
</table>
27+
</div>
28+
29+
<Paginator pagination_controller />
30+
}
31+
}
32+
33+
#[component]
34+
pub fn Paginator(pagination_controller: PaginationController) -> impl IntoView {
35+
let current_page = pagination_controller.current_page;
36+
let page_count = pagination_controller.page_count();
37+
38+
let page_range = move || {
39+
let mut start = current_page().saturating_sub(2);
40+
41+
let mut end = start + 5;
42+
43+
if let Some(row_count) = page_count() {
44+
if end > row_count {
45+
end = row_count;
46+
start = end.saturating_sub(5);
47+
}
48+
}
49+
50+
start..end
51+
};
52+
53+
view! {
54+
<nav aria-label="Page navigation example" class="m-10 flex justify-end">
55+
<ul class="inline-flex -space-x-px text-sm">
56+
<li>
57+
<a
58+
href="#"
59+
class="flex items-center justify-center px-3 h-8 ms-0 leading-tight text-gray-500 bg-white border border-e-0 border-gray-300 rounded-s-lg hover:bg-gray-100 hover:text-gray-700 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white"
60+
on:click=move |evt| {
61+
evt.prevent_default();
62+
evt.stop_propagation();
63+
pagination_controller.previous();
64+
}
65+
>
66+
67+
Previous
68+
</a>
69+
</li>
70+
71+
<For each=page_range key=|page| *page let:page>
72+
<PageLink page pagination_controller />
73+
</For>
74+
75+
<li>
76+
<a
77+
href="#"
78+
class="flex items-center justify-center px-3 h-8 leading-tight text-gray-500 bg-white border border-gray-300 rounded-e-lg hover:bg-gray-100 hover:text-gray-700 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white"
79+
on:click=move |evt| {
80+
evt.prevent_default();
81+
evt.stop_propagation();
82+
pagination_controller.next();
83+
}
84+
>
85+
86+
Next
87+
</a>
88+
</li>
89+
</ul>
90+
</nav>
91+
}
92+
}
93+
94+
#[component]
95+
pub fn PageLink(page: usize, pagination_controller: PaginationController) -> impl IntoView {
96+
let is_selected = move || pagination_controller.current_page.get() == page;
97+
98+
let class = move || {
99+
if is_selected() {
100+
"flex items-center justify-center px-3 h-8 text-blue-600 border border-gray-300 bg-blue-50 hover:bg-blue-100 hover:text-blue-700 dark:border-gray-700 dark:bg-gray-700 dark:text-white"
101+
} else {
102+
"flex items-center justify-center px-3 h-8 leading-tight text-gray-500 bg-white border border-gray-300 hover:bg-gray-100 hover:text-gray-700 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white"
103+
}
104+
};
105+
106+
view! {
107+
<li>
108+
<a
109+
href="#"
110+
class=class
111+
on:click=move |evt| {
112+
evt.prevent_default();
113+
evt.stop_propagation();
114+
pagination_controller.current_page.set(page);
115+
}
116+
>
117+
118+
{page + 1}
119+
</a>
120+
</li>
121+
}
122+
}
123+
124+
fn main() {
125+
_ = console_log::init_with_level(log::Level::Debug);
126+
console_error_panic_hook::set_once();
127+
128+
mount_to_body(|| {
129+
view! { <App/> }
130+
})
131+
}

0 commit comments

Comments
 (0)