Skip to main content

yew_datatable/components/
pagination.rs

1//! Pagination component.
2
3use crate::hooks::use_table::UseTableHandle;
4use wasm_bindgen::JsCast;
5use yew::prelude::*;
6
7/// Props for the Pagination component.
8#[derive(Properties, Clone)]
9pub struct PaginationProps<T: Clone + PartialEq + 'static> {
10    /// The table handle from use_table hook.
11    pub table: UseTableHandle<T>,
12
13    /// Custom class for the pagination container.
14    #[prop_or_default]
15    pub class: Classes,
16
17    /// Custom class for buttons.
18    #[prop_or_default]
19    pub button_class: Classes,
20
21    /// Custom class for disabled buttons.
22    #[prop_or_default]
23    pub disabled_class: Classes,
24
25    /// Whether to show page size selector.
26    #[prop_or(true)]
27    pub show_page_size_selector: bool,
28
29    /// Whether to show page info.
30    #[prop_or(true)]
31    pub show_page_info: bool,
32
33    /// Page size options.
34    #[prop_or_else(|| vec![10, 20, 30, 50, 100])]
35    pub page_size_options: Vec<usize>,
36}
37
38/// Compares `PaginationProps` by all fields, including the table handle.
39impl<T: Clone + PartialEq + 'static> PartialEq for PaginationProps<T> {
40    fn eq(&self, other: &Self) -> bool {
41        // Compare the table handle for reactivity-aware equality.
42        self.table == other.table
43            // Compare all configuration fields.
44            && self.class == other.class
45            && self.button_class == other.button_class
46            && self.disabled_class == other.disabled_class
47            && self.show_page_size_selector == other.show_page_size_selector
48            && self.show_page_info == other.show_page_info
49            && self.page_size_options == other.page_size_options
50    }
51}
52
53/// Pagination component for navigating table pages.
54#[function_component(Pagination)]
55pub fn pagination<T: Clone + PartialEq + 'static>(props: &PaginationProps<T>) -> Html {
56    // Retrieve pagination state from the table handle.
57    let current_page = props.table.current_page();
58    let page_count = props.table.page_count();
59    let page_size = props.table.page_size();
60    let total_rows = props.table.filtered_row_count();
61    let can_previous = props.table.can_previous_page();
62    let can_next = props.table.can_next_page();
63
64    // Create the callback for navigating to the first page.
65    let on_first = {
66        let table = props.table.clone();
67        Callback::from(move |_: MouseEvent| {
68            table.go_to_page(0);
69        })
70    };
71
72    // Create the callback for navigating to the previous page.
73    let on_previous = {
74        let table = props.table.clone();
75        Callback::from(move |_: MouseEvent| {
76            table.previous_page();
77        })
78    };
79
80    // Create the callback for navigating to the next page.
81    let on_next = {
82        let table = props.table.clone();
83        Callback::from(move |_: MouseEvent| {
84            table.next_page();
85        })
86    };
87
88    // Create the callback for navigating to the last page.
89    let on_last = {
90        let table = props.table.clone();
91        let last_page = page_count.saturating_sub(1);
92        Callback::from(move |_: MouseEvent| {
93            table.go_to_page(last_page);
94        })
95    };
96
97    // Create the callback for changing the page size.
98    let on_page_size_change = {
99        let table = props.table.clone();
100        Callback::from(move |e: Event| {
101            // Extract the select element from the event target.
102            if let Some(target) = e.target() {
103                if let Ok(select) = target.dyn_into::<web_sys::HtmlSelectElement>() {
104                    // Parse the selected value and update the page size.
105                    let value = select.value();
106                    if let Ok(size) = value.parse::<usize>() {
107                        table.set_page_size(size);
108                    }
109                }
110            }
111        })
112    };
113
114    // Create a helper closure for button class resolution.
115    let button_class = |disabled: bool| {
116        if disabled {
117            classes!(props.button_class.clone(), props.disabled_class.clone())
118        } else {
119            props.button_class.clone()
120        }
121    };
122
123    // Calculate the visible row range for the info display.
124    let start_row = if total_rows == 0 {
125        0
126    } else {
127        current_page * page_size + 1
128    };
129    let end_row = ((current_page + 1) * page_size).min(total_rows);
130
131    html! {
132        <div class={classes!("pagination", props.class.clone())}>
133            {if props.show_page_size_selector {
134                html! {
135                    <div class="page-size-selector">
136                        <label>{"Show "}</label>
137                        <select onchange={on_page_size_change} value={page_size.to_string()}>
138                            {props.page_size_options.iter().map(|&size| {
139                                html! {
140                                    <option
141                                        value={size.to_string()}
142                                        selected={size == page_size}
143                                    >
144                                        {size}
145                                    </option>
146                                }
147                            }).collect::<Html>()}
148                        </select>
149                        <label>{" entries"}</label>
150                    </div>
151                }
152            } else {
153                html! {}
154            }}
155
156            <div class="page-navigation">
157                <button
158                    class={button_class(!can_previous)}
159                    onclick={on_first}
160                    disabled={!can_previous}
161                >
162                    {"⟪"}
163                </button>
164                <button
165                    class={button_class(!can_previous)}
166                    onclick={on_previous}
167                    disabled={!can_previous}
168                >
169                    {"◀"}
170                </button>
171                <span class="page-info-inline">
172                    {format!("Page {} of {}", current_page + 1, page_count.max(1))}
173                </span>
174                <button
175                    class={button_class(!can_next)}
176                    onclick={on_next}
177                    disabled={!can_next}
178                >
179                    {"▶"}
180                </button>
181                <button
182                    class={button_class(!can_next)}
183                    onclick={on_last}
184                    disabled={!can_next}
185                >
186                    {"⟫"}
187                </button>
188            </div>
189
190            {if props.show_page_info {
191                html! {
192                    <div class="page-info">
193                        {format!("Showing {} to {} of {} entries", start_row, end_row, total_rows)}
194                    </div>
195                }
196            } else {
197                html! {}
198            }}
199        </div>
200    }
201}