dioxus-tabular 0.3.0

Type-safe and composable table framework for Dioxus
Documentation
use dioxus::prelude::*;
use dioxus_tabular::*;
use serde::Serialize;

#[derive(Clone, PartialEq)]
pub struct RowData {
    pub id: Id,
    pub name: Name,
}

#[derive(Clone, PartialEq)]
pub struct Id(pub String);

#[derive(Clone, PartialEq)]
pub struct Name(pub String);

impl Row for RowData {
    fn key(&self) -> impl Into<String> {
        self.id.0.clone()
    }
}

impl GetRowData<Id> for RowData {
    fn get(&self) -> Id {
        self.id.clone()
    }
}

impl GetRowData<Name> for RowData {
    fn get(&self) -> Name {
        self.name.clone()
    }
}

#[derive(Clone, PartialEq)]
pub struct IdColumn;

impl<R: Row + GetRowData<Id>> TableColumn<R> for IdColumn {
    fn column_name(&self) -> String {
        "id".into()
    }

    fn render_header(&self, _context: ColumnContext, attributes: Vec<Attribute>) -> Element {
        rsx! {
            th { ..attributes,"ID" }
        }
    }
    fn render_cell(&self, _context: ColumnContext, row: &R, attributes: Vec<Attribute>) -> Element {
        rsx! {
            td { ..attributes,"{row.get().0}" }
        }
    }

    fn filter(&self, _row: &R) -> bool {
        true
    }

    fn compare(&self, a: &R, b: &R) -> std::cmp::Ordering {
        a.get().0.cmp(&b.get().0)
    }
}

impl<R: Row + GetRowData<Id>> SerializableColumn<R> for IdColumn {
    fn serialize_cell(&self, row: &R) -> impl Serialize + '_ {
        row.get().0
    }
}

#[derive(Clone, PartialEq)]
pub struct NameColumn {
    serialize: Signal<bool>,
}

impl<R: Row + GetRowData<Name>> TableColumn<R> for NameColumn {
    fn column_name(&self) -> String {
        "name".into()
    }

    fn render_header(&self, _context: ColumnContext, attributes: Vec<Attribute>) -> Element {
        rsx! {
            th { ..attributes,"Name" }
        }
    }
    fn render_cell(&self, _context: ColumnContext, row: &R, attributes: Vec<Attribute>) -> Element {
        rsx! {
            td { ..attributes,"{row.get().0}" }
        }
    }
    fn filter(&self, _row: &R) -> bool {
        true
    }
    fn compare(&self, a: &R, b: &R) -> std::cmp::Ordering {
        a.get().0.cmp(&b.get().0)
    }
}

impl<R: Row + GetRowData<Name>> SerializableColumn<R> for NameColumn {
    fn serialize_cell(&self, row: &R) -> impl Serialize + '_ {
        row.get().0
    }

    fn include_in_export(&self) -> bool {
        *self.serialize.read()
    }
}

struct CsvExporter {
    output: String,
}

impl CsvExporter {
    fn finish(&mut self) {
        self.output.push('\n');
    }
}

impl Exporter for CsvExporter {
    type Error = serde_json::Error;

    fn serialize_header(&mut self, col: usize, header: &str) -> Result<(), Self::Error> {
        if col != 0 {
            self.output.push(',');
        }
        self.output.push_str(&serde_json::to_string(header)?);
        Ok(())
    }

    fn serialize_cell<'a>(
        &mut self,
        _row: usize,
        col: usize,
        cell: impl Serialize + 'a,
    ) -> Result<(), Self::Error> {
        if col == 0 {
            self.output.push('\n');
        } else {
            self.output.push(',');
        }
        self.output.push_str(&serde_json::to_string(&cell)?);
        Ok(())
    }
}

#[component]
pub fn Table<R: Row, C: Columns<R> + SerializableColumns<R>>(
    rows: ReadSignal<Vec<R>>,
    columns: C,
) -> Element {
    let mut serialized = use_signal(String::new);
    let data = use_tabular(columns, rows);
    rsx! {
        table {
            thead {
                tr {
                    TableHeaders { data }
                }
            }
            tbody {
                for row in data.rows() {
                    tr { key: "{row.key()}",
                        TableCells { row }
                    }
                }
            }
        }
        button {
            onclick: move |_| {
                let mut exporter = CsvExporter {
                    output: String::new(),
                };
                data.serialize(&mut exporter).unwrap();
                exporter.finish();
                serialized.set(exporter.output);
            },
            "serialize"
        }
        pre { "{serialized()}" }
    }
}

fn app() -> Element {
    let mut serialize_name = use_signal(|| true);
    let rows = use_signal(|| {
        vec![
            RowData {
                id: Id("1".to_string()),
                name: Name("Ryo".to_string()),
            },
            RowData {
                id: Id("2".to_string()),
                name: Name("Dioxus".to_string()),
            },
        ]
    });
    rsx! {
        div {
            input {
                r#type: "checkbox",
                checked: serialize_name(),
                oninput: move |_| {
                    serialize_name.set(!serialize_name());
                },
            }
            "serialize name"
        }
        Table {
            rows,
            columns: (
                IdColumn,
                NameColumn {
                    serialize: serialize_name,
                },
            ),
        }
    }
}

fn main() {
    dioxus::launch(app);
}