leptos-struct-table

repository·master·Indexed 42 days ago

https://github.com/synphonyte/leptos-struct-table

A library for creating highly customizable, headless table components in Leptos by deriving table logic directly from Rust structs. Version 0.19.1 allows developers to generate complete, batteries-included data table components from struct definitions, supporting features like custom renderers, pagination, and integration with REST APIs or sqlx via server functions.

Tokens
16.5K
Snippets
55
Records
72
Agent score
50%

What's inside leptos-struct-table

  1. How Data Providers work

    master

    Data providers manage how the table accesses its data.

    • Local Data: Adding #[table(impl_vec_data_provider)] to your struct automatically implements the TableDataProvider trait for Vec<T>, allowing you to pass a simple vector to the rows prop.
    • Async/Remote Data: For REST APIs or databases, you should implement one of the following traits:
      • TableDataProvider: For general data sources.
      • PaginatedTableDataProvider: Specifically for data sources that support pagination (e.g., most REST APIs). This enables advanced features like async partial loading and caching.
  2. Compare `FieldGetter` vs `getter` attribute

    master
    FeatureFieldGetter<T>#[table(getter = "...")]
    Use CasePurely derived/virtual data that doesn't exist in the struct.Modifying/formatting an existing struct field before rendering.
    MemoryZero overhead (uses PhantomData internally).No extra overhead, but operates on existing data.
    ImplementationRequires a new field in the struct definition.Applied to an existing field.
  3. Display derived values using Field Getters

    master

    If you need to display a value that is not a direct field of your struct (e.g., a concatenated string or a calculated value), use the FieldGetter<T> type. This creates a 'virtual field' that exists only for the table macro and does not increase the struct's memory footprint at runtime.

    To use it:

    1. Add a field with type FieldGetter<T> to your struct.
    2. Implement a method on your struct with the same name as that field which returns the derived value.

    Note: The method name must match the field name exactly.

    #[derive(TableRow, Clone)]
    #[table(classes_provider = "TailwindClassesPreset")]
    pub struct Book {
        id: u32,
        title: String,
        author: String,
    
        // Tells the macro to look for a method named `title_and_author`
        title_and_author: FieldGetter<String>
    }
    
    impl Book {
        pub fn title_and_author(&self) -> String {
            format!("{} by {}", self.title, self.author)
        }
    }
  4. Modify existing field values using the `getter` attribute

    master

    If a field actually exists on your struct but you want to transform its value before it is rendered in the table, use the #[table(getter = "...")] attribute on that field. This attribute points to a method on your struct that takes &self and returns the modified value.

    #[derive(TableRow, Clone)]
    #[table(classes_provider = "TailwindClassesPreset")]
    pub struct Book {
        // Tells the macro to use `get_title` instead of the raw field value
        #[table(getter = "get_title")]
        title: String,
    }
    
    impl Book {
        pub fn get_title(&self) -> String {
            format!("Title: {}", self.title)
        }
    }
  5. Quickstart: Create a basic table from a struct

    master

    To create a table, define a struct and derive TableRow. Use the #[table(impl_vec_data_provider)] attribute to allow the table to work directly with a Vec<T>. Then, use the <TableContent /> component within a view! macro, passing your data to the rows prop.

    use leptos::prelude::*;
    use leptos_struct_table::*;
    
    #[derive(TableRow, Clone)]
    #[table(impl_vec_data_provider)]
    pub struct Person {
        id: u32,
        name: String,
        age: u32,
    }
    
    #[component]
    fn Demo() -> impl IntoView {
        let rows = vec![
            Person { id: 1, name: "John".to_string(), age: 32 },
            Person { id: 2, name: "Jane".to_string(), age: 28 },
            Person { id: 3, name: "Bob".to_string(), age: 45 },
        ];
    
        view! {
            <table>
                <TableContent rows=rows scroll_container="" />
            </table>
        }
    }
  6. Configure Leptos Struct Table for Server-Side Rendering (SSR)

    master

    To use the table with Leptos SSR, add leptos-use as a dependency and configure your features in Cargo.toml to include leptos-use/ssr under the ssr feature. This ensures compatibility with server-side environments.

    [dependencies]
    leptos-use = "<current version>"
    # ...
    
    [features]
    hydrate = [
        "leptos/hydrate",
        # ...
    ]
    sr = [
        "leptos/ssr",
        # ...
        "leptos-use/ssr",
    ]
  7. Run the table grouping example

    master

    This example demonstrates grouping functionality where grouped columns do not repeat their values. It serves as a practical use-case for implementing TableRow::cell_renderer_for_column.

    Note: Because this example uses a path-based dependency (leptos-struct-table = { path = "../.." }), you must download or fork the entire repository to run it locally.

    trunk serve --open
  8. Run the generic struct table example

    master

    This example demonstrates how to use leptos-struct-table with a generic struct. Because the example uses a path dependency (leptos-struct-table = { path = "../.." }), you must download or fork the entire repository to run it locally.

    Prerequisites

    You need Trunk and the wasm32-unknown-unknown target installed.

    Setup and Execution

    1. Install Trunk and the WASM target:
    cargo install trunk
    rustup target add wasm32-unknown-unknown
    1. Run the example using Trunk:
    trunk serve --open
    cargo install trunk
    rustup target add wasm32-unknown-unknown
    
    trunk serve --open
  9. Customize table rendering with custom renderers

    master

    You can customize the table's appearance by providing custom renderer components. Renderers can be applied at three levels:

    1. Struct Level

    Use thead_cell_renderer on the struct to override how header cells are rendered.

    2. TableContent Component Props

    Use these props on the <TableContent /> component to override structural parts:

    • thead_renderer: Renders the <thead> tag.
    • thead_row_renderer: Renders the <tr> inside the head.
    • tbody_renderer: Renders the <tbody> tag.
    • row_renderer: Renders table rows.
    • loading_row_renderer: Renders rows during loading states.
    • error_row_renderer: Renders rows during error states.
    • row_placeholder_renderer: Renders placeholder rows.

    3. Field Level

    Use the #[table(renderer = "...")] attribute on a specific field to override how that specific cell is rendered. The renderer component must accept the following arguments:

    • class: String
    • value: Signal<T>
    • row: RwSignal<YourStruct>
    • index: usize
    #[derive(TableRow)]
    pub struct Book {
        title: String,
        #[table(renderer = "ImageTableCellRenderer")]
        img: String,
    }
    
    #[component]
    fn ImageTableCellRenderer(
        class: String,
        value: Signal<String>,
        row: RwSignal<Book>,
        index: usize,
    ) -> impl IntoView {
        view! {
            <td class=class>
                <img src=value alt="Book image" height="64"/>
            </td quite>
        }
    }
  10. Implement editable cells

    master

    To make cells editable, use a custom renderer that interacts with the row prop. The row prop is provided as an RwSignal<YourStruct>, allowing you to write changes directly back to the data source.

    1. Define a custom renderer component that takes row: RwSignal<YourStruct>.
    2. Inside the renderer, use row.write().field = new_value to update the data.
    3. In your main component, listen to the on_change prop of <TableContent /> to react to data updates.
    #[derive(TableRow, Clone, Default, Debug)]
    #[table(impl_vec_data_provider)]
    pub struct Book {
        id: u32,
        #[table(renderer = "InputCellRenderer")]
        title: String,
    }
    
    #[component]
    fn InputCellRenderer(
        class: String,
        value: Signal<String>,
        row: RwSignal<Book>,
        index: usize,
    ) -> impl IntoView {
        let on_change = move |evt| {
            row.write().title = event_target_value(&evt);
        };
    
        view! {
            <td class=class>
                <input type="text" value=value on:change=on_change />
            </td quite>
        }
    }
    
    // Usage in TableContent
    view! {
        <TableContent 
            rows 
            on_change={move |evt: ChangeEvent<Book>| {
                logging::log!("Changed row at index {}: {:?}", evt.row_index, evt.changed_row.get_untracked());
            }}
            scroll_container="" 
        />
    }
  11. Run the column order and visibility example

    master

    This example demonstrates a table where columns can be hidden using a multiselect component and re-ordered via drag-and-drop.

    Note: Because this example uses a path-based dependency (leptos-struct-table = { path = "../.." }), you must download or fork the entire repository to run it locally.

    To run the example, you need Trunk and the wasm32-unknown-unknown target installed.

    # 1. Install dependencies
    cargo install trunk
    rustup target add wasm32-unknown-unknown
    
    # 2. Run the example
    trunk serve --open