Derives forms from structs. This derive macro provides a way of generating [`Form`](leptos_router::Form) components and configuring their styling and behavior. It creates a struct of signals, one per field, where each signal which contains the appropriate html type (essentially [`String`], although specialized behavior is provided for collections). Upon form submission, the values in the form are all parsed back into the type this macro is derived on and then submitted to the suitable endpoint / server function. Note that serde integration is not completed, so in order for graceful degradation to work the deriving type must not change the serialized names of any of its fields. See an [extended example](#example) below. # Struct attributes | Attribute | Description | Type | Optional | |-------------|--------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------|----------| | component | Derive a component for this type using [`leptos::component`] | [component](#component-attributes) | Y | | error | Specify error rendering behavior which will be used as a default for all fields; defaults to `default` | [error handler](#error-handler-attributes) | Y | | field_class | Class property set on the wrapping element for each field by default | string | Y | | groups | A list of all groups within the form | list\<[container](#container-attributes)\> | Y | | id | `id` property set on the wrapping \ element. Note that this id will prefixed by other ids if this type is used as a field in another form | string | Y | | label | Default label configuration used for all fields | [label](#label-attributes) | Y | | wrapper | Can only be used when derived on a newtype -- required to correctly produce the `name` attribute on fields | none | Y | # Field attributes Any type which implements [`trait@FormField`] can be used as a field in a struct which derives Form. | Attribute | Description | Type | Optional | |-----------|--------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------|----------| | class | `class` property set on this field's wrapping element | string | Y | | config | A Rust expression whose type is the [`FormField::Config`] type of this field's type | expr | Y | | el | The Rust type representing the html tag used to encode this field (note that the field type must implement `FormField<$el>`) | type | Y | | error | Specify error rendering behavior for this field, falling back on the container default where needed; defaults to `default` | [error handler](#error-handler-attributes) | Y | | group | Group number if this field should be included in a group (0-indexed) | usize | Y | | id | `id` property set on the wrapping \ element. Note that this id will prefixed by other ids if this type is used as a field in another form | string | Y | | label | Label configuration used for this field, falling back on the container default where needed | [label](#label-attributes) | Y | | style | `style` property set on this field's wrapping element | string | Y | ## Component attributes If specified, a leptos component will be produced for this type which will render a form derived from this type's fields. The below table documents which subparameters can be provided to the `component` parameter. These subparameters provide a way of controlling the actual UI behavior of the generated component. Note that essentially all of these behaviors (excluding things such as `class`, `style`, etc.) require client side wasm. | Attribute | Description | Type | Optional | |---------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------|----------| | action | Leptos action configuration for this form | [action](#action-attribute) | Y | | cache | Cache configuration for this form | [cache](#cache-attribute) | Y | | class | `class` property set on the wrapping \ element | string | Y | | field_changed_class | An additional class to be appended to the containing element of any field whose value has changed | string | Y | | map_submit | Maps this type given its initial and current values into another type which will then be passed to the provided action | [`MapSubmit`] | Y | | name | The name of the component function produced; if this type is a tuple struct, name cannot be the type name or the type name prepended with an underscore | ident | Y | | on_error | A callback which is called after a form submission error; called with the action's error and the action signal | [`OnError`](components::OnError) | Y | | on_loading | A callback which is called to render a loading view while a form's action is loading | [`OnLoading`](components::OnLoading) | Y | | on_submit | A callback returning a future which can be used as the form submission handler (useful for client side rendered Forms which do not call server functions); cannot be used with `action`; parameters provided are `(Self, SubmitEvent)` | [`OnSubmit`](components::OnSubmit) | Y | | on_success | A callback which is called after a successful form submission; called with the successful action outcome and the action signal | [`OnSuccess`](components::OnSuccess) | Y | | reset_on_success | Configures whether the form's fields should be reset to the form's initial values upon successful submission; defaults to false | bool | Y | | style | `style` property set on the wrapping \ element | string | Y | ### Generated component props | Name | Description | Type | Optional | |---------|------------------------------------------------------------------------------------------------------------------------------------------|------------------------------|----------| | initial | The initial value of `Self` to be used for the form -- typically is a default value. | `Self` | N | | top | Additional DOM node to be rendered inside of the `
` element prior to any labels/fields generated using the deriving type's fields. | `impl Fn() -> impl IntoView` | Y | | bottom | Additional DOM node to be rendered inside of the `` element below any labels/fields generated using the deriving type's fields. | `impl Fn() -> impl IntoView` | Y | ## Action attribute If specified, an action will be attached to the rendered [`Form`](leptos_router::Form) component. An action can be specified one of two ways: - a string literal, representing the url the form should be submitted to; in this case, quality of life attributes like `component.map_submit`, `component.on_error`, `component.on_success`, and `component.reset_on_success` cannot be used because the page will immediately reload upon form submission - a path to a server function specified in a particular way: ```rust mod my_mod { use leptos::{server, ServerFnError}; use leptos_form::Form; use serde::{Deserialize, Serialize}; #[derive(Clone, Debug, Deserialize, Form, Serialize)] #[form(component(action = my_server_fn(my_data)))] struct MyForm {} #[server] async fn my_server_fn(my_data: MyForm) -> Result<(), ServerFnError> { Ok(()) } } ``` leptos_form *does not* use [`ActionForm`](leptos_router::ActionForm), and therefore must produce the props to the server function itself which requires knowing the argument names of the server function. Thus the peculiar way of specifying server functions. ActionForm is avoided because it does not provide hooks for parsing the form prior to form submission. We parse the form first into the deriving struct (i.e. the type [`macro@Form`] is derived on) and then either submit it to the server function or pass it to `map_submit` if specified. This allows us to keeps the internals of state managament in the form completely separate from representation of the form's data. ## Cache attribute Forms can be configured to cache their serialized values, debouncing after any changes occur in the form. | Attribute | decsription | Type | Optional | |-------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------|----------| | debounce_ms | How long to wait after most recent change until serializing form and writing it to cache (defaults to 1500) | number | Y | | key | Name of serialized item in cache (defaults to `format!("{url domain}{url path}#{form id}")`, omitting the `#{form id}` if no form id is provided) | string | Y | | value | An expression evaluating to a type which implements [`Cache`](cache::Cache). For JSON local storage, `leptos_form::cache::LocalStorage(leptos_form::cache::SerdeJson))`. | expression | N | ## Container attributes Specifies a containing element to contain wrap children. | Attribute | Description | Optional | |-----------|---------------------------------------------------|--------------------------------------------------------------------------------------------------------| | tag | The html tag to use for the wrapping html element | N (Y if used in the context of an adjacent field label and the form also has a default adjacent label) | | id | `id` property set on the wrapping html element | Y | | class | `class` property set on the wrapping html element | Y | | style | `style` property set on the wrapping html element | Y | ## Error handler attributes When focus is removed from a field's input element, the input's value is parsed into this deriving struct's field's type. If there is an error while parsing the input, it can be displayed around the input. This attribute configures how that error is displayed. Only one of the below attributes can be used at a time. | Attribute | Description | Type | |-----------|---------------------------------------------------------------------------------|------------------------------------| | component | A leptos component which takes a single prop `error` with type [`FormError`] | ident | | container | A containing element which will render the stringified error as a child element | [container](#container-attributes) | | default | Renders the stringified error in a `` element | none | | none | No errors are ever rendered | none | | raw | The stringified error is rendered without any containing element | none | ## Label attributes Configuration for how to render a field's label. Only one of the below attributes can be used at a time. | Attribute | Description | Type | |-----------|----------------------------------------------------------------------------------------------------------------------------|----------------------------------------------| | adjacent | Renders the field label adjacent to the field input within a containing element (adjacent in the sense of the DOM tree) | [adjacent label](#adjacent-label-attributes) | | default | Renders the field label adjacent to the field input without any containing element (adjacent in the sense of the DOM tree) | none | | none | No label is rendered | none | | wrap | Wraps the field label string and input in a \ element | [wrapped label](#wrapped-label-attributes) | ## Adjacent label attributes Configuration for an adjacent label. Rendered adjacent labels have the following html structure: ```html ``` | Attribute | Description | Type | Optional | | -----------|----------------------------------------------------|------------------------------------|-------------------------------------| | container | A containing element which will wrap the \ | [container](#container-attributes) | N if struct-level, Y if field-level | | id | `id` property set on the wrapping html element | string | Y | | class | `class` property set on the wrapping html element | string | Y | | rename_all | Rename all labels to a particular case | string, see [`LabelCase`] | Y (only allowed at struct-level) | | style | `style` property set on the wrapping html element | string | Y | | value | A literal string to override the label value | string | Y (only allowed at field-level) | ## Wrapped label attributes Configuration for a wrapped label. Rendered wrapped labels have the following html structure: ```html ``` | Attribute | Description | Type | Optional | | -----------|----------------------------------------------------|---------------------------|-------------------------------------| | id | `id` property set on the wrapping html element | string | Y | | class | `class` property set on the wrapping html element | string | Y | | rename_all | Rename all labels to a particular case | string, see [`LabelCase`] | Y (only allowed at struct-level) | | style | `style` property set on the wrapping html element | string | Y | | value | A literal string to override the label value | string | Y (only allowed at field-level) | # Example This example derives two forms: one for creating blog posts and another for updating them. Note that this example assumes makes use of Tailwind classes. See additional [examples](https://github.com/tlowerison/leptos_form/tree/main/examples). ```rust mod blog_post { use ::leptos::*; use ::leptos::html::Textarea; use ::leptos_form::prelude::*; use ::leptos_router::*; use ::serde::*; use ::typed_builder::*; use ::uuid::Uuid; /// db model of a blog post -- this type is here just to facilitate /// the use of server functions and is not particularly relevant to /// this example #[derive(Clone, Debug, Deserialize, Serialize, TypedBuilder)] pub struct DbBlogPost { pub id: Uuid, pub slug: String, pub title: String, pub summary: String, pub tags: Vec, pub content: String, } /// ui model of a blog post -- it contains all the data necessary to create a blog post /// from the front end and also uses accurate field types like `Uuid` and `Vec` rather than /// the stringified types placed inside html (leptos_form handles that conversion!) #[derive(Clone, Debug, Deserialize, Form, Serialize, TypedBuilder)] #[form( field_class = "appearance-none block w-full bg-gray-200 text-gray-700 border border-gray-200 rounded py-3 px-4 leading-tight focus:outline-none focus:bg-white focus:border-gray-500", component( action = create_blog_post(data), class = "w-full max-w-lg", reset_on_success, on_success = |value, _| (view! {
{move || format!(r#"Created blog post with id "{}"."#, value.id)}
}.into_view()), ), groups( container(tag = "div", class="flex flex-wrap -mx-3 mb-6", style = "color: red;"), container(tag = "div", class="flex flex-wrap -mx-3 mb-6"), container(tag = "div", class="flex flex-wrap -mx-3 mb-2"), ), label(adjacent(container(tag = "div", class="w-full md:w-1/2 px-3"), class = "block uppercase tracking-wide text-gray-700 text-xs font-bold mb-2")), )] pub struct BlogPost { #[form(class = "hidden", label = "none")] pub id: Uuid, #[form(group = 0)] pub slug: String, #[builder(default)] #[form(group = 0)] pub title: String, #[builder(default)] #[form(group = 1)] pub summary: String, #[builder(default)] #[form(group = 1, config = vec_config())] #[serde(default)] pub tags: Vec, #[builder(default)] #[form( el(HtmlElement