#table #dioxus #front-end

dioxus-table

Make data-driven table rendering easy with Dioxus

2 releases

0.1.1 May 5, 2022
0.1.0 May 5, 2022

#87 in #dioxus

Custom license

13KB
119 lines

Dioxus Table

Crates.io version GitHub Sponsors

Make data-driven table rendering easy with Dioxus (Live Example)

Installation

Add the following to your Cargo.toml

[dependencies]
dioxus-table = "0.1.1"

Quickstart

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.

Event Handlers

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.

Customization

You can customize most aspects of the table rendering. Here is an overview of all available options.

Per-column/field 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

Per-table options

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()

Dynamic 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

Custom renderers are a powerful way to customize almost all aspects of the table rendering. Yet they are very easy to use.

Custom cell renderer

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(cell_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.

Custom row and head cell renderers

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.

Dependencies

~7–13MB
~174K SLoC