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}