leptos_struct_table/lib.rs
1//! Easily create Leptos table components from structs.
2//!
3//! 
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>);