Skip to main content

yew_datatable/hooks/
use_table.rs

1//! The `use_table` hook for managing table state in Yew components.
2
3use std::cell::RefCell;
4use std::rc::Rc;
5use yew::prelude::*;
6use yew_datatable_core::prelude::{
7    ColumnDef, ColumnId, DataTable, DataTableOptions, DataTableRow, DataTableRowId, DataTableState, SortDirection,
8};
9
10/// Handle returned by `use_table` hook.
11#[derive(Clone)]
12pub struct UseTableHandle<T: Clone + 'static> {
13    table: Rc<RefCell<DataTable<T>>>,
14    state: UseStateHandle<DataTableState>,
15    trigger: UseStateHandle<u32>,
16    data_version: Rc<RefCell<u32>>,
17}
18
19impl<T: Clone + 'static> PartialEq for UseTableHandle<T> {
20    fn eq(&self, other: &Self) -> bool {
21        // Compare by trigger state and data version for reactivity
22        *self.trigger == *other.trigger
23            && *self.data_version.borrow() == *other.data_version.borrow()
24            && Rc::ptr_eq(&self.table, &other.table)
25    }
26}
27
28impl<T: Clone + 'static> UseTableHandle<T> {
29    /// Returns the visible rows after processing.
30    pub fn visible_rows(&self) -> Vec<DataTableRow<T>> {
31        self.table.borrow().visible_rows().cloned().collect()
32    }
33
34    /// Returns the visible column IDs in order.
35    pub fn visible_column_ids(&self) -> Vec<ColumnId> {
36        self.table.borrow().visible_column_ids()
37    }
38
39    /// Returns visible columns.
40    pub fn visible_columns(&self) -> Vec<ColumnId> {
41        self.table.borrow().visible_column_ids()
42    }
43
44    /// Gets column header by ID.
45    pub fn get_column_header(&self, id: &ColumnId) -> Option<String> {
46        self.table.borrow().get_column(id).map(|c| c.header().to_string())
47    }
48
49    /// Gets whether a column is sortable.
50    pub fn is_column_sortable(&self, id: &ColumnId) -> bool {
51        self.table
52            .borrow()
53            .get_column(id)
54            .map(|c| c.is_sortable())
55            .unwrap_or(false)
56    }
57
58    /// Gets value from a row for a column.
59    pub fn get_cell_value(&self, row: &T, column_id: &ColumnId) -> Option<String> {
60        self.table
61            .borrow()
62            .get_column(column_id)
63            .and_then(|col| col.get_value(row))
64            .map(|v| v.as_string())
65    }
66
67    /// Returns the total number of rows.
68    pub fn total_row_count(&self) -> usize {
69        self.table.borrow().total_row_count()
70    }
71
72    /// Returns the number of filtered rows.
73    pub fn filtered_row_count(&self) -> usize {
74        self.table.borrow().filtered_row_count()
75    }
76
77    /// Returns the number of rows on the current page.
78    pub fn page_row_count(&self) -> usize {
79        self.table.borrow().page_row_count()
80    }
81
82    /// Returns the current table state.
83    pub fn state(&self) -> DataTableState {
84        (*self.state).clone()
85    }
86
87    /// Triggers a re-render.
88    fn update(&self) {
89        self.state.set(self.table.borrow().state().clone());
90        self.trigger.set(*self.trigger + 1);
91    }
92
93    /// Toggles sorting for a column.
94    pub fn toggle_sort(&self, column_id: impl Into<ColumnId>, multi: bool) {
95        {
96            let mut table = self.table.borrow_mut();
97            table.toggle_sort(column_id, multi);
98            table.process();
99        }
100        self.update();
101    }
102
103    /// Sets a column filter.
104    pub fn set_column_filter(&self, column_id: impl Into<ColumnId>, value: impl Into<String>) {
105        {
106            let mut table = self.table.borrow_mut();
107            table.set_column_filter(column_id, value);
108            table.process();
109        }
110        self.update();
111    }
112
113    /// Sets the global filter.
114    pub fn set_global_filter(&self, value: impl Into<String>) {
115        {
116            let mut table = self.table.borrow_mut();
117            table.set_global_filter(value);
118            table.process();
119        }
120        self.update();
121    }
122
123    /// Toggles row selection.
124    pub fn toggle_row_selection(&self, row_id: DataTableRowId) {
125        self.table.borrow_mut().toggle_row_selection(row_id);
126        self.update();
127    }
128
129    /// Selects all rows.
130    pub fn select_all_rows(&self) {
131        self.table.borrow_mut().select_all_rows();
132        self.update();
133    }
134
135    /// Clears row selection.
136    pub fn clear_selection(&self) {
137        self.table.borrow_mut().clear_selection();
138        self.update();
139    }
140
141    /// Toggles row expansion.
142    pub fn toggle_row_expansion(&self, row_id: DataTableRowId) {
143        {
144            let mut table = self.table.borrow_mut();
145            table.toggle_row_expansion(row_id);
146            table.process();
147        }
148        self.update();
149    }
150
151    /// Toggles column visibility.
152    pub fn toggle_column_visibility(&self, column_id: ColumnId) {
153        self.table.borrow_mut().toggle_column_visibility(column_id);
154        self.update();
155    }
156
157    /// Goes to a specific page (0-indexed).
158    pub fn go_to_page(&self, page: usize) {
159        {
160            let mut table = self.table.borrow_mut();
161            table.go_to_page(page);
162            table.process();
163        }
164        self.update();
165    }
166
167    /// Goes to the next page.
168    pub fn next_page(&self) {
169        {
170            let mut table = self.table.borrow_mut();
171            table.next_page();
172            table.process();
173        }
174        self.update();
175    }
176
177    /// Goes to the previous page.
178    pub fn previous_page(&self) {
179        {
180            let mut table = self.table.borrow_mut();
181            table.previous_page();
182            table.process();
183        }
184        self.update();
185    }
186
187    /// Sets the page size.
188    pub fn set_page_size(&self, size: usize) {
189        {
190            let mut table = self.table.borrow_mut();
191            table.set_page_size(size);
192            table.process();
193        }
194        self.update();
195    }
196
197    /// Resets all table state.
198    pub fn reset(&self) {
199        {
200            let mut table = self.table.borrow_mut();
201            table.reset();
202            table.process();
203        }
204        self.update();
205    }
206
207    /// Returns whether a row is selected.
208    pub fn is_row_selected(&self, row_id: &DataTableRowId) -> bool {
209        self.table.borrow().state().row_selection.is_selected(row_id)
210    }
211
212    /// Returns whether a row is expanded.
213    pub fn is_row_expanded(&self, row_id: &DataTableRowId) -> bool {
214        self.table.borrow().state().expanding.is_expanded(row_id)
215    }
216
217    /// Returns whether a column is visible.
218    pub fn is_column_visible(&self, column_id: &ColumnId) -> bool {
219        self.table.borrow().state().column_visibility.is_visible(column_id)
220    }
221
222    /// Returns the current page index (0-indexed).
223    pub fn current_page(&self) -> usize {
224        self.table.borrow().state().pagination.page_index()
225    }
226
227    /// Returns the page size.
228    pub fn page_size(&self) -> usize {
229        self.table.borrow().state().pagination.page_size()
230    }
231
232    /// Returns the total number of pages.
233    pub fn page_count(&self) -> usize {
234        let table = self.table.borrow();
235        table.state().pagination.page_count(table.filtered_row_count())
236    }
237
238    /// Returns whether there is a previous page.
239    pub fn can_previous_page(&self) -> bool {
240        self.table.borrow().state().pagination.can_go_previous()
241    }
242
243    /// Returns whether there is a next page.
244    pub fn can_next_page(&self) -> bool {
245        let table = self.table.borrow();
246        table.state().pagination.can_go_next(table.filtered_row_count())
247    }
248
249    /// Gets the sort direction for a column.
250    pub fn get_sort_direction(&self, column_id: &ColumnId) -> Option<SortDirection> {
251        self.table.borrow().state().sorting.get_direction(column_id)
252    }
253
254    /// Gets the sort index for a column (for multi-sort).
255    pub fn get_sort_index(&self, column_id: &ColumnId) -> Option<usize> {
256        self.table.borrow().state().sorting.get_sort_index(column_id)
257    }
258
259    /// Updates the data in the table.
260    pub fn set_data(&self, data: Vec<T>) {
261        // Update the table data and reprocess.
262        {
263            let mut table = self.table.borrow_mut();
264            table.set_data_indexed(data);
265            table.process();
266        }
267        // Increment the data version counter.
268        *self.data_version.borrow_mut() += 1;
269        // Trigger a re-render.
270        self.update();
271    }
272
273    /// Returns the current data version (incremented on each data change).
274    pub fn data_version(&self) -> u32 {
275        *self.data_version.borrow()
276    }
277}
278
279/// Hook for creating and managing a table instance.
280///
281/// # Arguments
282///
283/// * `columns` - Column definitions for the table
284/// * `data` - The data to display in the table
285/// * `options` - Optional table configuration
286///
287/// # Returns
288///
289/// A `UseTableHandle` that provides access to table state and methods.
290#[hook]
291pub fn use_table<T: Clone + PartialEq + 'static>(
292    columns: Vec<ColumnDef<T>>,
293    data: Vec<T>,
294    _options: Option<DataTableOptions>,
295) -> UseTableHandle<T> {
296    // Preserve the latest incoming data value for this render.
297    let incoming_data = data;
298
299    // Clone the incoming data for one-time table initialization.
300    let initial_data = incoming_data.clone();
301
302    // Store the table persistently across renders
303    let table_ref: Rc<RefCell<DataTable<T>>> = use_mut_ref(|| {
304        let mut t = DataTable::with_data(columns, initial_data, |_, idx| DataTableRowId::from_index(idx));
305        t.process();
306        t
307    });
308
309    // Track the last data value passed as hook input.
310    let last_prop_data: Rc<RefCell<Vec<T>>> = use_mut_ref(|| incoming_data.clone());
311
312    let data_version: Rc<RefCell<u32>> = use_mut_ref(|| 0);
313    let state = use_state(|| table_ref.borrow().state().clone());
314    let trigger = use_state(|| 0u32);
315
316    // Synchronize table data when the hook input changes.
317    if *last_prop_data.borrow() != incoming_data {
318        // Apply the new data and recompute table row models.
319        {
320            let mut table = table_ref.borrow_mut();
321            table.set_data_indexed(incoming_data.clone());
322            table.process();
323        }
324
325        // Record the synchronized prop value.
326        *last_prop_data.borrow_mut() = incoming_data;
327
328        // Track data mutations for downstream equality checks.
329        *data_version.borrow_mut() += 1;
330    }
331
332    UseTableHandle {
333        table: table_ref,
334        state,
335        trigger,
336        data_version,
337    }
338}