leptos_struct_table/
lib.rs

1//! Easily create Leptos table components from structs.
2//!
3//! ![Hero Image](https://raw.githubusercontent.com/synphonyte/leptos-struct-table/master/hero.webp)
4//!
5//! # Features
6//!
7//! - **Easy to use** - yet powerful.
8//! - **Async data loading** - The data is loaded asynchronously. This allows to load data from a REST API or a database etc.
9//! - **Selection** - Can be turned off or single/multi select
10//! - **Customization** - You can customize every aspect of the table by plugging in your own components for rendering rows, cells, headers. See [Custom Renderers](#custom-renderers) for more information.
11//! - **Headless** - No default styling is applied to the table. You can fully customize the classes that are applied to the table. See [Classes customization](#classes-customization) for more information.
12//! - **Sorting** - Optional. If turned on: Click on a column header to sort the table by that column. You can even sort by multiple columns.
13//! - **Virtualization** - Only the visible rows are rendered. This allows for very large tables.
14//! - **Pagination** - Instead of virtualization you can paginate the table.
15//! - **Caching** - Only visible rows are loaded and cached.
16//! - **Editing** - Optional. You can provide custom renderers for editable cells. See [Editable Cells](#editable-cells) for more information.
17//!
18//! # Usage
19//!
20//! ```
21//! use leptos::*;
22//! use leptos_struct_table::*;
23//!
24//! #[derive(TableRow, Clone)]
25//! #[table(impl_vec_data_provider)]
26//! pub struct Person {
27//!     id: u32,
28//!     name: String,
29//!     age: u32,
30//! }
31//!
32//! fn main() {
33//!     mount_to_body(|| {
34//!         let rows = vec![
35//!             Person { id: 1, name: "John".to_string(), age: 32 },
36//!             Person { id: 2, name: "Jane".to_string(), age: 28 },
37//!             Person { id: 3, name: "Bob".to_string(), age: 45 },
38//!         ];
39//!
40//!         view! {
41//!             <table>
42//!                 <TableContent rows />
43//!             </table>
44//!         }
45//!     });
46//! }
47//! ```
48//!
49//! # Server-Side Rendering
50//!
51//! To use this with Leptos' server-side rendering, you can have to add `leptos-use` as a dependency to your `Cargo.toml` and
52//! then configure it for SSR like the following.
53//!
54//! ```toml
55//! [dependencies]
56//! leptos-use = "<current version>"
57//! # ...
58//!
59//! [features]
60//! hydrate = [
61//!     "leptos/hydrate",
62//!     # ...
63//! ]
64//! ssr = [
65//!     "leptos/ssr",
66//!     # ...
67//!     "leptos-use/ssr",
68//! ]
69//! ```
70//!
71//! Please see the [serverfn_sqlx example](https://github.com/Synphonyte/leptos-struct-table/blob/master/examples/serverfn_sqlx/Cargo.toml)
72//! for a working project with SSR.
73//!
74//! # Data Providers
75//!
76//! As shown in the inital usage example, when you add `#[table(impl_vec_data_provider)]` to your struct,
77//! the table will automatically generate a data provider for you. You can then directly pass a `Vec<T>` to the `rows` prop.
78//! Internally this implements the trait [`TableDataProvider`] for `Vec<T>`.
79//!
80//! To leverage the full power of async partial data loading with caching you should implement the trait
81//! [`PaginatedTableDataProvider`] or the trait [`TableDataProvider`] yourself. It's quite easy to do so.
82//! Which of the two traits you choose depends on your data source. If your data source provides
83//! paginated data, as is the case for many REST APIs, you should implement [`PaginatedTableDataProvider`].
84//! Otherwise you should probably implement [`TableDataProvider`].
85//!
86//! See the [paginated_rest_datasource example](https://github.com/Synphonyte/leptos-struct-table/blob/master/examples/paginated_rest_datasource/src/data_provider.rs)
87//! and the [serverfn_sqlx example](https://github.com/Synphonyte/leptos-struct-table/blob/master/examples/serverfn_sqlx/src/data_provider.rs)
88//! for working demo projects that implement these traits.
89//!
90//! # Macro options
91//!
92//! The `#[table(...)]` attribute can be used to customize the generated component. The following options are available:
93//!
94//! ## Struct attributes
95//!
96//! These attributes can be applied to the struct itself.
97//!
98//! - **`sortable`** - Specifies that the table should be sortable. This makes the header titles clickable to control sorting.
99//!    You can specify two sorting modes with the prop `sorting_mode` on the `TableContent` component:
100//!    - `sorting_mode=SortingMode::MultiColumn` (the default) allows the table to be sorted by multiple columns ordered by priority.
101//!    - `sorting_mode=SortingMode::SingleColumn"` allows the table to be sorted by a single column. Clicking on another column will simply replace the sorting column.
102//!    See the [simple example](https://github.com/synphonyte/leptos-struct-table/blob/master/examples/simple/src/main.rs) and the
103//!    [selectable example](https://github.com/synphonyte/leptos-struct-table/blob/master/examples/selectable/src/main.rs) for more information.
104//! - **`classes_provider`** - Specifies the name of the class provider. Used to quickly customize all of the classes that are applied to the table.
105//!    For convenience sensible presets for major CSS frameworks are provided. See [`TableClassesProvider`] and [tailwind example](https://github.com/synphonyte/leptos-struct-table/blob/master/examples/tailwind/src/main.rs) for more information.
106//! - **`head_cell_renderer`** - Specifies the name of the header cell renderer component. Used to customize the rendering of header cells. Defaults to [`DefaultTableHeaderRenderer`]. See the [custom_renderers_svg example](https://github.com/Synphonyte/leptos-struct-table/blob/master/examples/custom_renderers_svg/src/main.rs) for more information.
107//! - **`impl_vec_data_provider`** - If given, then [`TableDataProvider`] is automatically implemented for `Vec<ThisStruct>` to allow
108//!    for easy local data use. See the [simple example](https://github.com/synphonyte/leptos-struct-table/blob/master/examples/simple/src/main.rs) for more information.
109//! - **`row_type`** - Specifies the type of the rows in the table. Defaults to the struct that this is applied to. See the [custom_type example](https://github.com/synphonyte/leptos-struct-table/blob/master/examples/custom_type/src/main.rs) for more information.
110//!
111//! ## Field attributes
112//!
113//! These attributes can be applied to any field in the struct.
114//!
115//! - **`class`** - Specifies the classes that are applied to each cell (head and body) in the field's column. Can be used in conjuction with `classes_provider` to customize the classes.
116//! - **`head_class`** - Specifies the classes that are applied to the header cell in the field's column. Can be used in conjuction with `classes_provider` to customize the classes.
117//! - **`cell_class`** - Specifies the classes that are applied to the body cells in the field's column. Can be used in conjuction with `classes_provider` to customize the classes.
118//! - **`skip`** - Specifies that the field should be skipped. This is useful for fields that are not displayed in the table.
119//! - **`skip_sort`** - Only applies if `sortable` is set on the struct. Specifies that the field should not be used for sorting. Clicking it's header will not do anything.
120//! - **`skip_header`** - Makes the title of the field not be displayed in the head row.
121//! - **`title`** - Specifies the title that is displayed in the header cell. Defaults to the field name converted to title case (`this_field` becomes `"This Field"`).
122//! - **`renderer`** - Specifies the name of the cell renderer component. Used to customize the rendering of cells.
123//!    Defaults to [`DefaultTableCellRenderer`].
124//!  - **`format`** - Quick way to customize the formatting of cells without having to create a custom renderer. See [Formatting](#formatting) below for more information.
125//! - **`getter`** - Specifies a method that returns the value of the field instead of accessing the field directly when rendering.
126//! - **`none_value`** - Specifies a display value for `Option` types when they are `None`. Defaults to empty string
127//!
128//! ### Formatting
129//!
130//! The `format` attribute can be used to customize the formatting of cells. It is an easier alternative to creating a custom renderer when you just want to customize some basic formatting.
131//! It is type safe and tied to the type the formatting is applied on. see [`CellValue`] and the associated type for the type you are rendering to see a list of options
132//!
133//! See:
134//! - [`cell_value::NumberRenderOptions`]
135#![cfg_attr(feature = "chrono", doc = r##"- [`chrono::RenderChronoOptions`]"##)]
136#![cfg_attr(
137    feature = "rust_decimal",
138    doc = r##"- [`rust_decimal::DecimalNumberRenderOptions`]"##
139)]
140//!
141//!
142#![cfg_attr(
143    feature = "chrono",
144    doc = r##"
145Example:
146
147```
148# use leptos::*;
149# use leptos_struct_table::*;
150# use ::chrono::{NaiveDate, NaiveDateTime, NaiveTime};
151#
152#[derive(TableRow, Clone)]
153pub struct TemperatureMeasurement {
154    #[table(title = "Temperature (°C)", format(precision = 2usize))]
155    temperature: f32,
156    #[table(format(string = "%m.%d.%Y"))]
157    date: NaiveDate,
158}
159```
160"##
161)]
162
163//! # Features
164//!
165//! - **`chrono`** - Adds support for types from the crate `chrono`.
166//! - **`rust_decimal`** - Adds support for types from the crate `rust_decimal`.
167//! - **`time`** - Adds support for types from the crate `time`.
168//! - **`uuid`** - Adds support for types from the crate `uuid`.
169//!
170//! # Classes Customization
171//!
172//! Classes can be easily customized by using the `classes_provider` attribute on the struct.
173//! You can specify any type that implementats the trait [`TableClassesProvider`]. Please see the documentation for that trait for more information.
174//! You can also look at [`TailwindClassesPreset`] for an example how this can be implemented.
175//!
176//! Example:
177//!
178//! ```
179//! # use leptos::*;
180//! # use leptos_struct_table::*;
181//! #
182//! #[derive(TableRow, Clone)]
183//! #[table(classes_provider = "TailwindClassesPreset")]
184//! pub struct Book {
185//!     id: u32,
186//!     title: String,
187//! }
188//! ```
189//!
190//! # Field Getters
191//!
192//! Sometimes you want to display a field that is not part of the struct but a derived value either
193//! from other fields or sth entirely different. For this you can use either the [`FieldGetter`] type
194//! or the `getter` attribute.
195//!
196//! Let's start with [`FieldGetter`] and see an example:
197//!
198//! ```
199//! # use leptos::*;
200//! # use leptos_struct_table::*;
201//! # use serde::{Deserialize, Serialize};
202//! #
203//! #[derive(TableRow, Clone)]
204//! #[table(classes_provider = "TailwindClassesPreset")]
205//! pub struct Book {
206//!     id: u32,
207//!     title: String,
208//!     author: String,
209//!
210//!     // this tells the macro that you're going to provide a method called `title_and_author` that returns a `String`
211//!     title_and_author: FieldGetter<String>
212//! }
213//!
214//! impl Book {
215//!     // Returns the value that is displayed in the column
216//!     pub fn title_and_author(&self) -> String {
217//!         format!("{} by {}", self.title, self.author)
218//!     }
219//! }
220//! ```
221//!
222//! To provide maximum flexibility you can use the `getter` attribute.
223//!
224//! ```
225//! # use leptos::*;
226//! # use leptos_struct_table::*;
227//! #
228//! #[derive(TableRow, Clone)]
229//! #[table(classes_provider = "TailwindClassesPreset")]
230//! pub struct Book {
231//!     // this tells the macro that you're going to provide a method called `get_title` that returns a `String`
232//!     #[table(getter = "get_title")]
233//!     title: String,
234//! }
235//!
236//! impl Book {
237//!     pub fn get_title(&self) -> String {
238//!         format!("Title: {}", self.title)
239//!     }
240//! }
241//! ```
242//!
243//! ## When to use `FieldGetter` vs `getter` attribute
244//!
245//! A field of type `FieldGetter<T>` is a virtual field that doesn't really exist on the struct.
246//! Internally `FieldGetter` is just a new-typed `PhatomData` and thus is removed during compilation.
247//! Hence it doesn't increase memory usage. That means you should use it for purely derived data.
248//!
249//! The `getter` attribute should be used on a field that actually exists on the struct but whose
250//! value you want to modify before it's rendered.
251//!
252//! # Custom Renderers
253//!
254//! Custom renderers can be used to customize almost every aspect of the table.
255//! They are specified by using the various `...renderer` attributes on the struct or fields or props of the [`TableContent`] component.
256//! To implement a custom renderer please have a look at the default renderers listed below.
257//!
258//! On the struct level you can use this attribute:
259//! - **`thead_cell_renderer`** - Defaults to [`DefaultTableHeaderCellRenderer`] which renders `<th><span>Title</span></th>`
260//!    together with sorting functionality (if enabled).
261//!
262//! As props of the [`TableContent`] component you can use the following:
263//! - **`thead_renderer`** - Defaults to [`DefaultTableHeadRenderer`] which just renders the tag `thead`.
264//! - **`thead_row_renderer`** - Defaults to [`DefaultTableHeadRowRenderer`] which just renders the tag `tr`.
265//! - **`tbody_renderer`** - Defaults to the tag `tbody`. Takes no attributes.
266//! - **`row_renderer`** - Defaults to [`DefaultTableRowRenderer`].
267//! - **`loading_row_renderer`** - Defaults to [`DefaultLoadingRowRenderer`].
268//! - **`error_row_renderer`** - Defaults to [`DefaultErrorRowRenderer`].
269//! - **`row_placeholder_renderer`** - Defaults to [`DefaultRowPlaceholderRenderer`].
270//!
271//! On the field level you can use the **`renderer`** attribute.
272//!
273//! It defaults to [`DefaultTableCellRenderer`]
274//! Works for any type that implements the [`CellValue`] trait that is implemented for types in the standard library, popular crates with feature flags and for your own type if you implement this trait for them.
275//!
276//! Example:
277//!
278//! ```
279//! # use leptos::*;
280//! # use leptos_struct_table::*;
281//! #
282//! #[derive(TableRow, Clone)]
283//! pub struct Book {
284//!     title: String,
285//!     #[table(renderer = "ImageTableCellRenderer")]
286//!     img: String,
287//! }
288//!
289//! // Easy cell renderer that just displays an image from an URL.
290//! #[component]
291//! fn ImageTableCellRenderer<F>(
292//!     class: String,
293//!     #[prop(into)] value: MaybeSignal<String>,
294//!     on_change: F,
295//!     index: usize,
296//! ) -> impl IntoView
297//! where
298//!     F: Fn(String) + 'static,
299//! {
300//!     view! {
301//!         <td class=class>
302//!             <img src=value alt="Book image" height="64"/>
303//!         </td>
304//!     }
305//! }
306//! ```
307//!
308//! For more detailed information please have a look at the [custom_renderers_svg example](https://github.com/synphonyte/leptos-struct-table/blob/master/examples/custom_renderers_svg/src/main.rs) for a complete customization.
309//!
310//!
311//! ## Editable Cells
312//!
313//! You might have noticed the type parameter `F` in the custom cell renderer above. This can be used
314//! to emit an event when the cell is changed. In the simplest case you can use a cell renderer that
315//! uses an `<input>`.
316//!
317//! ```
318//! # use leptos::*;
319//! # use leptos_struct_table::*;
320//! #
321//! #[derive(TableRow, Clone, Default, Debug)]
322//! #[table(impl_vec_data_provider)]
323//! pub struct Book {
324//!     id: u32,
325//!     #[table(renderer = "InputCellRenderer")]
326//!     title: String,
327//! }
328//!
329//! // Easy input cell renderer that emits `on_change` when the input is changed.
330//! #[component]
331//! fn InputCellRenderer<F>(
332//!     class: String,
333//!     #[prop(into)] value: MaybeSignal<String>,
334//!     on_change: F,
335//!     index: usize,
336//! ) -> impl IntoView
337//! where
338//!     F: Fn(String) + 'static,
339//! {
340//!     view! {
341//!         <td class=class>
342//!             <input type="text" value=value on:change=move |evt| { on_change(event_target_value(&evt)); } />
343//!         </td>
344//!     }
345//! }
346//!
347//! // Then in the table component you can listen to the `on_change` event:
348//!
349//! #[component]
350//! pub fn App() -> impl IntoView {
351//!     let rows = vec![Book::default(), Book::default()];
352//!
353//!     let on_change = move |evt: ChangeEvent<Book>| {
354//!         logging::log!("Changed row at index {}:\n{:#?}", evt.row_index, evt.changed_row);
355//!     };
356//!
357//!     view! {
358//!         <table>
359//!             <TableContent rows on_change />
360//!         </table>
361//!     }
362//! }
363//! ```
364//!
365//! Please have a look at the [editable example](https://github.com/Synphonyte/leptos-struct-table/tree/master/examples/editable/src/main.rs) for fully working example.
366//!
367//! # Pagination / Virtualization / InfiniteScroll
368//!
369//! This table component supports different display acceleration strategies. You can set them through the `display_strategy` prop of
370//! the [`TableContent`] component.
371//!
372//! The following options are available. Check their docs for more details.
373//! - [`DisplayStrategy::Virtualization`] (default)
374//! - [`DisplayStrategy::InfiniteScroll`]
375//! - [`DisplayStrategy::Pagination`]
376//!
377//! Please have a look at the [pagination example](https://github.com/Synphonyte/leptos-struct-table/tree/master/examples/pagination/src/main.rs) for more information on how to use pagination.
378//!
379//! # I18n
380//!
381//! To translate the column titles of the table using `leptos-i18n` you can enable the `"i18n"`
382//! feature. The field names of the struct are used as keys.
383//!
384//! Please have a look at the
385//! [i18n example](https://github.com/Synphonyte/leptos-struct-table/tree/master/examples/i18n).
386//!
387//! # Contribution
388//!
389//! All contributions are welcome. Please open an issue or a pull request if you have any ideas or problems.
390
391#![allow(non_snake_case)]
392
393mod cell_value;
394#[cfg(feature = "chrono")]
395pub mod chrono;
396mod class_providers;
397mod components;
398mod data_provider;
399mod display_strategy;
400mod events;
401mod loaded_rows;
402mod reload_controller;
403mod row_reader;
404#[cfg(feature = "rust_decimal")]
405pub mod rust_decimal;
406mod scroll_container;
407mod selection;
408mod sorting;
409mod table_row;
410#[cfg(feature = "time")]
411pub mod time;
412#[cfg(feature = "uuid")]
413mod uuid;
414
415pub use cell_value::*;
416pub use class_providers::*;
417pub use components::*;
418pub use data_provider::*;
419pub use display_strategy::*;
420pub use events::*;
421pub use leptos_struct_table_macro::TableRow;
422pub use loaded_rows::RowState;
423pub use reload_controller::*;
424pub use row_reader::*;
425pub use scroll_container::*;
426pub use selection::*;
427pub use sorting::*;
428pub use table_row::*;
429
430use serde::{Deserialize, Serialize};
431use std::marker::PhantomData;
432
433/// Type of sorting of a column
434#[derive(Copy, Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
435pub enum ColumnSort {
436    Ascending,
437    Descending,
438    None,
439}
440
441impl ColumnSort {
442    /// Returns the a default class name
443    pub fn as_class(&self) -> &'static str {
444        match self {
445            ColumnSort::Ascending => "sort-asc",
446            ColumnSort::Descending => "sort-desc",
447            _ => "",
448        }
449    }
450
451    /// Returns the SQL sort order (ASC or DESC) or `None` if `ColumnSort::None`.
452    pub fn as_sql(&self) -> Option<&'static str> {
453        match self {
454            ColumnSort::Ascending => Some("ASC"),
455            ColumnSort::Descending => Some("DESC"),
456            _ => None,
457        }
458    }
459}
460
461/// Type of struct field used to specify that the value of this field is
462/// obtained by calling a getter method on the struct.
463///
464/// Please refer to the [`getter` example](https://github.com/Synphonyte/leptos-struct-table/tree/master/examples/getter) for how this is used
465#[derive(
466    Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Default, Serialize, Deserialize,
467)]
468pub struct FieldGetter<T>(PhantomData<T>);