leptos_form_tool 0.2.0

A declarative way to create forms for leptos.
Documentation
# Getting Started

This guide will walk you through creating your first leptos_form_tool form.

## Form Data

Start by creating the form data.

This struct should contain all the data for the entire form.

```rust
// all FormToolData implementors must also implement Clone and be 'static
// Default and Debug are not required, but helpful
#[derive(Clone, Default, Debug)]
struct HelloWorldFormData {
  first: String,
  last: String,
  age: u32,
  sport: String,
}
```

## Defining the Form Layout

Then, to define how the form should be rendered, implement the `FormToolData`
trait. You will need to define the style that this form will use and what
context it will have.

It is required to define the style here, as the style needs to be known to
add the StylingAttributes to the controls.

The `build_form` method provides you with a `FormBuilder<Self>`. You can define
controls on the form by using the builder methods. Some controls don't accept
input from the user, they just display information. These are referred to as
`VanityControls` by leptos_form_tool. An example of a vanity control would 
be a heading. Other controls do accept user input and are just referred to as 
`Controls` by leptos_form_tool.

### Defining Controls

There are other builder methods defined the the ControlBuilder for certain
controls. For example, the TextInput builder has a `placeholder` method
that sets the placeholder of the text input.

When building a control, you will need to provide a getter and setter
to get the field and set the field on the form data. The getter is a function
that takes the full form data and returns the field. The setter is a function
that takes the full form data and a value and sets the field to that value.
Examples of these getters and setters are shown below. 

VanityControls will never need a setter, as they only display information.
Sometimes they need getters if the information they display is based on the 
form data. For example, the `output` control can display information from the
form data, so it needs a getter.

#### Parse and Unparse Functions

Controls also need a set of parse and unparse functions. The type that the
control returns could be anything. For example, a checkbox might return a
`bool`, and a text input might return a `String`. leptos_form_tool needs a way to 
convert the type of the field, to the type of the control and vice versa.
This is what the un/parse functions are for.

The parse and unparse functions can always be specified with the `parse_custom`
method, but there are several builder methods that create the parse functions 
automatically. For example, if the field type and control type can be
converted to and from each other with the `From` trait (this is true if the
two types are the same) then you can call the `parse_from` method to 
automatically create the un/parse functions using that conversion.
If the control's type is String, and the field type implements `FromStr` and
`ToString`, you can call `parse_string` to generate un/parse functions using
that trait. `parse_trimmed` does the same, but trims the string before parsing.
This should cover most use cases, but you always have the option to define
your own.

It is important to note that parsing from the control's type to the field type
IS allowed to fail. If it fails, it will be displayed just like any other
validation error. Conversion from the field type to the control type is NOT
allowed to fail; the FormData should always be able to be displayed.

#### Validation Functions

Validation functions can be defined on a field to add some extra criteria
for what counts as a valid entry. The validation function takes the entire
form's data. This allows you to use the entire state of the form to decide if
this field is valid or not. If any validation function fails, the form will
not be allowed to be submitted. In addition, when you build a validator, it will
collect all of the validation functions that you define on these fields.

leptos_form_tool provides a `ValidationBuilder` to help you build validation
functions, which, in some cases, might be easier than defining a closure
yourself.

```rust
impl FormToolData for HelloWorldFormData {
  // The form style to use.
  type Style = GridFormStyle;
  // The external context needed for rendering the form.
  // In this case, nothing.
  type Context = ();

  fn build_form(fb: FormBuilder<Self>) -> FormBuilder<Self> {
    fb.heading(|h| h.title("Welcome"))
      .text_input(|t| {
        t.named("data[first]")
          .labeled("First Name")
          .getter(|fd| fd.first)
          .setter(|fd, value| fd.first = value)
          // trim the string before writing to the field
          .parse_trimmed()
          .validation_fn(
            // Using the ValidationBuilder to set a required field
            ValidationBuilder::for_field(|fd: &HelloWorldFormData| fd.first.as_str())
              .named("First Name")
              .required()
              .build(),
          )
          // width out of 12
          .style(Width(4))
          // defines text that shows up when hovering over it
          .style(Tooltip("Your given first name".to_string()))
      })
      .text_input(|t| {
        t.named("data[last]")
          .labeled("Last Name")
          .getter(|fd| fd.last)
          .setter(|fd, value| fd.last = value)
          // dont trim the string, just write it to the field
          .parse_from()
          .validation_fn(
            // using the ValidationBuilder to set a required field
            ValidationBuilder::for_field(|fd: &HelloWorldFormData| fd.last.as_str())
              .named("Last Name")
              .required()
              .build(),
          )
          .style(Width(8))
          .style(Tooltip("Your last name".to_string()))
      })
      // using the _cx varient allows access to the context
      // in this case, its `()` which doesnt help us that much.
      .stepper_cx(|s, _cx| {
        s.named("data[age]")
          .labeled("Age")
          .getter(|fd| fd.age)
          .setter(|fd, value| fd.age = value)
          // trim the string then try to parse to a `u32`
          .parse_trimmed()
          .validation_fn(move |fd| {
            // defining a validation function with a closure
            (fd.age > 13)
            .then_some(())
            .ok_or_else(|| String::from("Too Young"))
          })
          .style(Width(6))
          .style(Tooltip("Your age in years".to_string()))
      })
      .select(|s| {
        s.named("data[sport]")
          .labeled("Favorite Sport")
            .getter(|fd| fd.sport)
            .setter(|fd, value| fd.sport = value)
            .parse_from()
            // set the options for the select along with their values
            .with_options_valued(vec![
              ("Football", "football"),
              ("Soccer", "soccer"),
              ("Ice Hockey", "ice_hockey"),
              ("Golf", "golf"),
            ].into_iter())
            .style(Width(6))
      })
      .submit(|s| s.text("Submit"))
  }
}
```

Now, using the form is quite simple. You just need to provide the form data,
style, context, and where the form should point to.

```rust
let form = ExampleFormData::default().get_plain_form("/api/endpoint", GridFormStyle::default(), ());

view! {
  <div>
    <h1> "This is My Form!" </h1>
    {form}
  </div>
}
```

You can also have the form build to an `ActionForm`, this will be very 
familiar if you've used an `ActionForm` before.

You might have noticed the goofy names that we put in our form above, like
"data[first]" instead of just "first". This is done to allow the form to use
the SubmitForm as an action. See 
[ActionForms](https://book.leptos.dev/progressive_enhancement/action_form.html#complex-inputs)
in the Leptos book for more.

```rust
#[component]
fn FormWrapper() -> impl IntoView {
  let server_fn_action = create_server_action::<SubmitForm>();
 
  let form = HelloWorldFormData::default().get_action_form(server_fn_action, GridFormStyle::default(), ());
  let response = server_fn_action.value();

  view! {
    <div>
      <h1> "This is My Form!" </h1>
      // display the form
      {form}
      // display the result from the server
      {move || response.get().map(|result| result.ok())}
    </div>
  }
}

#[server(SubmitForm)]
async fn submit_form(data: HelloWorldFormData) -> Result<String, ServerFnError> {
  data.validate(()).map_err(ServerFnError::new)?;

  Ok(format!(
    "Hello {} {} ({}), You must like {}!",
    data.first, data.last, data.age, data.sport
  ))
}
```

Lastly, there is the `get_form` method. This is almost identical to the ActionForm 
version. In fact, if you do everything right, you won't even notice a
difference. Under the hood, `get_form` serializes and sends your FormToolData 
struct directly by calling the server function.
`get_action_form`, on the other hand, will try to construct your FormToolData
struct from the html form fields using the 
[`FromFormData`](https://docs.rs/leptos_router/latest/leptos_router/trait.FromFormData.html)
trait.

Since the `get_form` method will directly serialize and send the form data,
your form data needs to be `Serialize` and `Deserialize`. Since it doesn't
use the `FromFormData` trait, you can name the inputs whatever you want and 
it will still work (though you should try to name them correctly anyway to 
support progressive enhancement). The example is the same as above, 
just replace `get_action_form` with `get_form`, 
and add the `Serialize` and `Deserialize` derives.