Skip to main content

yew_datatable/components/
table_body.rs

1//! Table body component.
2
3use crate::components::cell_render_context::CellRenderContext;
4use crate::hooks::use_table::UseTableHandle;
5use yew::prelude::*;
6
7/// Props for the TableBody component.
8#[derive(Properties, Clone)]
9pub struct TableBodyProps<T: Clone + PartialEq + 'static> {
10    /// The table handle from use_table hook.
11    pub table: UseTableHandle<T>,
12
13    /// Custom class for the tbody element.
14    #[prop_or_default]
15    pub class: Classes,
16
17    /// Custom class for tr elements.
18    #[prop_or_default]
19    pub tr_class: Classes,
20
21    /// Custom class for td elements.
22    #[prop_or_default]
23    pub td_class: Classes,
24
25    /// Custom class for selected rows.
26    #[prop_or_default]
27    pub selected_class: Classes,
28
29    /// Whether rows are selectable by clicking.
30    #[prop_or(true)]
31    pub selectable: bool,
32
33    /// Custom cell renderer.
34    #[prop_or_default]
35    pub render_cell: Option<Callback<CellRenderContext<T>, Html>>,
36}
37
38/// Compares `TableBodyProps` by all fields, including the table handle.
39///
40/// The `render_cell` callback is always treated as changed when present,
41/// since `Callback` does not implement `PartialEq`.
42impl<T: Clone + PartialEq + 'static> PartialEq for TableBodyProps<T> {
43    fn eq(&self, other: &Self) -> bool {
44        // Compare the table handle for reactivity-aware equality.
45        self.table == other.table
46            // Compare all configuration fields.
47            && self.class == other.class
48            && self.tr_class == other.tr_class
49            && self.td_class == other.td_class
50            && self.selected_class == other.selected_class
51            && self.selectable == other.selectable
52            // Callbacks cannot be compared; assume changed if either side has one.
53            && self.render_cell.is_none()
54            && other.render_cell.is_none()
55    }
56}
57
58/// Table body component that renders rows and cells.
59#[function_component(TableBody)]
60pub fn table_body<T: Clone + PartialEq + 'static>(props: &TableBodyProps<T>) -> Html {
61    // Retrieve the ordered list of visible column identifiers.
62    let column_ids = props.table.visible_column_ids();
63
64    // Collect the visible rows after processing.
65    let rows = props.table.visible_rows();
66
67    html! {
68        <tbody class={props.class.clone()}>
69            {rows.into_iter().enumerate().map(|(row_idx, row)| {
70                // Extract the row identifier and selection state.
71                let row_id = row.id.clone();
72                let is_selected = props.table.is_row_selected(&row_id);
73
74                // Apply the selected class if the row is selected.
75                let row_class = if is_selected {
76                    classes!(props.tr_class.clone(), props.selected_class.clone())
77                } else {
78                    props.tr_class.clone()
79                };
80
81                // Create the click handler for row selection toggling.
82                let onclick = if props.selectable {
83                    let table = props.table.clone();
84                    let row_id = row_id.clone();
85                    Some(Callback::from(move |_: MouseEvent| {
86                        table.toggle_row_selection(row_id.clone());
87                    }))
88                } else {
89                    None
90                };
91
92                html! {
93                    <tr
94                        key={row_id.as_str().to_string()}
95                        class={row_class}
96                        onclick={onclick}
97                    >
98                        {column_ids.iter().enumerate().map(|(col_idx, column_id)| {
99                            // Retrieve the cell value for the current column.
100                            let value = props.table.get_cell_value(&row.original, column_id)
101                                .unwrap_or_default();
102
103                            // Use the custom renderer if provided, otherwise render plain text.
104                            let cell_html = if let Some(render_cell) = &props.render_cell {
105                                let ctx = CellRenderContext {
106                                    row: row.original.clone(),
107                                    row_id: row_id.clone(),
108                                    row_index: row_idx,
109                                    column_id: column_id.as_str().to_string(),
110                                    column_index: col_idx,
111                                    value: value.clone(),
112                                };
113                                render_cell.emit(ctx)
114                            } else {
115                                html! { {value} }
116                            };
117
118                            html! {
119                                <td
120                                    key={column_id.as_str().to_string()}
121                                    class={props.td_class.clone()}
122                                >
123                                    {cell_html}
124                                </td>
125                            }
126                        }).collect::<Html>()}
127                    </tr>
128                }
129            }).collect::<Html>()}
130        </tbody>
131    }
132}