Make data-driven table rendering easy with Dioxus (Live Example)
Add the following to your Cargo.toml
[dependencies]
dioxus-table = "0.1.1"
Attach the derive macro TableData
to a struct that represents a row of a table.
// in mod hotel:
#[derive(PartialEq, TableData)]
pub struct Hotel {
#[table(class = "text-end")] // right align numbers
pub id: i32,
#[table(title = "Hotel Name")] // custom title
pub name: String,
pub city: String,
#[table(skip)] // don't show this column
pub internal: u32,
}
This generates a Dioxus component called Table
in the module hotel
ready to be used in your app.
// in your app:
use hotel::{Hotel, Table as HotelTable};
fn App(cx: Scope) -> Element {
// get some hotels
let hotels = vec![
Hotel { id: 1, name: "Hotel 1".to_owned(), city: "City 1".to_owned(), internal: 42 },
Hotel { id: 2, name: "Hotel 2".to_owned(), city: "City 2".to_owned(), internal: 42 },
];
cx.render(rsx! {
h1 { "Hotel Table" }
HotelTable {
class: "table",
items: &hotels,
}
})
}
And that's it! Easy, right?
You can look at the examples in the examples
directory to get a more complete overview of what dioxus-table can do.
The generated table component provides two events: Clicking on a row or a head cell. Let's add some event handlers to our previous example.
cx.render(rsx! {
h1 { "Hotel Table" }
HotelTable {
class: "table",
items: &hotels,
onrowclick: move |evt: TableRowEvent<_, _>| {
web_sys::console::log_1(&format!("Row {}", evt.row_index).into());
},
onheadclick: move |evt: TableHeadEvent<_>| {
web_sys::console::log_1(&format!("Head {} '{}'", evt.column_index, evt.field).into());
},
}
})
When you click on a head cell or on a data row you'll see some information logged to the console.
You can customize most aspects of the table rendering. Here is an overview of all available options.
Option | Description |
---|---|
class | HTML class(es) added to the <th> and <td> tags |
cell_class | HTML class(es) added only to the <td> cell tag |
head_class | HTML class(es) added only to the <th> head tag |
title | Custom title that is put into the <th> . By default the capitalized field name is used. |
precision | For decimal types this sets the number of digits after the decimal point |
renderer | Custom cell render component |
skip | Don't render this column |
Option | Description |
---|---|
row_class | HTML class(es) added to the <tr> row tags except the first one (the header row) |
head_row_class | Added only to the first <tr> (the header row) |
tag | The HTML tag name used for the root of the table. Defaults to "table" . |
row_renderer | Custom row renderer component |
head_cell_renderer | Custom head cell renderer component |
dyn_row_classes | Enables reactive row classes through the method row_classes() |
A simple way to give feedback to interactions is to add a class to a row element. This makes is easy to
highlight a selected row for example. Above the struct
enable the dyn_row_classes
option.
#[derive(PartialEq, TableData)]
#[table(dyn_row_classes)]
struct Hotel {
...
}
What classes are added to a row is determined by calling the row_classes()
method. So let's implement it.
impl Hotel {
fn row_classes(&self, index: usize, cx: Scope<T>) -> Vec<String> {
if /* this hotel is selected */ {
vec!["selected".to_string()]
} else {
vec![]
}
}
}
The method row_classes()
is called for each row. It receives the index
of the row and the current context.
Please look at the hotel example in the examples
directory for a full example.
Custom renderers are a powerful way to customize almost all aspects of the table rendering. Yet they are very easy to use.
Probably the most common use for a custom renderer is to customize the representation of a value in a table cell.
Let's say we have a table of books with a title
and a rating
field.
The rating
field is an integer number from 1 to 5 that represents the number of stars a book has received.
#[derive(PartialEq, TableData)]
pub struct Book {
pub title: i32,
pub rating: u8,
}
With the default renderer we only see a number in the "Rating" column. If we want to display this number as stars we can write a custom renderer.
#[derive(PartialEq, TableData)]
pub struct Book {
pub title: i32,
#[table(renderer = "StarRenderer")] // specify the custom renderer
pub rating: u8,
}
// The actual renderer component. It has to accept the DefaultTableCellProps.
pub fn StarRenderer(cx: Scope<DefaultTableCellProps<i32>>) -> Element {
// the value of the rating field is provided as cx.props.value here
let count = cx.props.value as usize;
// create a string with #count filled stars.
let mut stars = "".to_owned();
for _ in 0..count {
stars += "★";
}
// then fill up the rest of the 5 stars with emtpy stars
for _ in count..5 {
stars += "☆";
}
// display the string
cx.render(rsx! {
td {
class: "{cx.props.class}",
"{stars}"
}
})
}
Now the rating is properly displayed as stars. To see this in action run the hotels example in the examples/
folder.
They work basically the same as the cell renderers but are specified above the struct definition.
#[derive(PartialEq, TableData)]
#[table(row_renderer = "MyRowRenderer", head_cell_renderer = "MyHeadCellRenderer")]
pub struct Book {
pub title: i32,
pub rating: u8,
}
To see how to implement them please refer to the default renderers in src/cell_renderers.rs and src/row_enderers.rs. The easiest way to get going is to copy and paste the respective default renderer and customize from there.