leptos_pagination/components/paginated_for.rs
1use std::{marker::PhantomData, sync::Arc};
2
3use leptos::prelude::*;
4use leptos_windowing::{
5 InternalLoader, ItemWindow, cache::CacheStoreFields, item_state::ItemState,
6};
7use reactive_stores::{Store, StoreFieldIterator};
8
9use crate::{PaginationState, UsePaginationOptions, use_pagination};
10
11/// Slot that is rendered when an error occurs.
12#[derive(Clone)]
13#[slot]
14pub struct LoadError {
15 children: Arc<dyn Fn(String) -> AnyView + Send + Sync>,
16}
17
18/// Slot that is rendered when the data is being loaded.
19#[derive(Clone)]
20#[slot]
21pub struct Loading {
22 children: ChildrenFn,
23}
24
25/// Quite similar to Leptos' `<For>` this displays a list of items.
26///
27/// But these items are loaded and cached on-demand using the provided `loader`.
28///
29/// ## Example
30///
31/// ```
32/// # use std::ops::Range;
33/// #
34/// # use leptos::prelude::*;
35/// # use leptos_pagination::{Loading, PaginatedFor, PaginationState, PaginationPrev, PaginationNext, ExactLoader};
36/// #
37/// pub struct Book {
38/// title: String,
39/// author: String,
40/// }
41///
42/// # #[component]
43/// # pub fn App() -> impl IntoView {
44/// let state = PaginationState::new_store();
45///
46/// view! {
47/// <ul>
48/// <PaginatedFor
49/// loader=BookLoader
50/// query=()
51/// state
52/// item_count_per_page=20
53/// let:idx_book
54/// >
55/// // Shown when the data has finished loading.
56/// <li class={if idx_book.0 % 2 == 0 { "even" } else { "odd" }}>
57/// <h3>{idx_book.1.title.clone()}</h3>
58/// <p>{idx_book.1.author.clone()}</p>
59/// </li>
60///
61/// // Shown while the data is loading.
62/// <Loading slot>
63/// <li class="loading">Loading...</li>
64/// </Loading>
65/// </PaginatedFor>
66/// </ul>
67///
68/// <div class="pagination-buttons">
69/// <PaginationPrev state attr:class="pagination-prev">
70/// "Previous"
71/// </PaginationPrev>
72/// <PaginationNext state attr:class="pagination-next">
73/// "Next"
74/// </PaginationNext>
75/// </div>
76/// }
77/// # }
78///
79/// pub struct BookLoader;
80///
81/// impl ExactLoader for BookLoader {
82/// type Item = Book;
83/// type Query = ();
84/// type Error = ();
85///
86/// async fn load_items(&self, range: Range<usize>, query: &Self::Query) -> Result<Vec<Self::Item>, Self::Error> {
87/// todo!()
88/// }
89///
90/// async fn item_count(&self, _query: &Self::Query) -> Result<Option<usize>, Self::Error> {
91/// todo!()
92/// }
93/// }
94///
95/// ```
96///
97/// For more in-depth demonstration, please refer to the example `pagination_rest_api`.
98#[component]
99pub fn PaginatedFor<T, L, Q, CF, V, M>(
100 /// The loader to get the data on-demand.
101 loader: L,
102
103 /// The query to get the data on-demand.
104 #[prop(into)]
105 query: Signal<Q>,
106
107 /// The pagination state.
108 ///
109 /// Used to communicate between the pagination controls and this component.
110 state: Store<PaginationState>,
111
112 /// How many items to display per page.
113 #[prop(into)]
114 item_count_per_page: Signal<usize>,
115
116 /// How many pages to load before and after the current page.
117 ///
118 /// A value of 1 means that the current page as well as the one before and after will be loaded.
119 /// Defaults to 1.
120 #[prop(default = 1)]
121 overscan_page_count: usize,
122
123 /// Slot that is rendered instead of `children` when the data is being loaded.
124 /// This is recommended to be used to show a loading skeleton.
125 #[prop(optional)]
126 loading: Option<Loading>,
127
128 /// Slot that is rendered instead of `children` when an error occurs.
129 #[prop(optional)]
130 load_error: Option<LoadError>,
131
132 /// The normal children are rendered when an item is loaded.
133 /// This would be a normal `<li>` or `<tr>` element for example.
134 children: CF,
135
136 #[prop(optional)] _marker: PhantomData<(M, L)>,
137) -> impl IntoView
138where
139 T: Send + Sync + 'static,
140 L: InternalLoader<M, Item = T, Query = Q> + 'static,
141 L::Error: Send + Sync,
142 Q: Send + Sync + 'static,
143 CF: Fn((usize, Arc<T>)) -> V + Send + Clone + 'static,
144 V: IntoView,
145{
146 let window: ItemWindow<T> = use_pagination(
147 state,
148 loader,
149 query,
150 item_count_per_page,
151 UsePaginationOptions::default().overscan_page_count(overscan_page_count),
152 );
153
154 view! {
155 <For each=move || window.range.get() key=|idx| *idx let:index>
156 {
157 let children = children.clone();
158 let loading = loading.clone();
159 let load_error = load_error.clone();
160 move || match &*window.cache.items().at_unkeyed(index).read() {
161 ItemState::Loaded(item) => {
162 children.clone()((index, Arc::clone(item))).into_any()
163 }
164 ItemState::Error(error) => {
165 load_error
166 .clone()
167 .map(|e| (e.children)(error.clone()).into_any())
168 .unwrap_or_else(|| {
169
170 view! { <div style="color: red;">Error: {error.clone()}</div> }
171 .into_any()
172 })
173 }
174 _ => {
175 loading
176 .clone()
177 .map(|l| (l.children)().into_any())
178 .unwrap_or_else(|| ().into_any())
179 }
180 }
181 }
182 </For>
183 }
184}