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