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