Introduction
The WebSharper.Forms library, available on NuGet, provides a high-level abstraction for working with web forms and constructing interactive user interfaces. It can declaratively describe user data input such as forms, including data validation and feedback. It allows for a separation of concerns between the data model and the rendering of the form, while the two are connected in a type-safe manner.
Additional benefits are composability and reusability of form components, as well as the ability to create dynamic forms that adapt to user input, i.e. the type and appearance of input fields in part of the form dynamically depends on user input in previous fields.
Using forms is easier than it looks. Users might find the type signatures complex, so this guide explains the meaning of all elements that compose a form. In addition, introductory examples will be presented to get the user acquainted with WebSharper.Forms.
WebSharper.Forms is based on WebSharper UI. Therefore, familiarity with concepts such as Var
, View
and the Doc
type for HTML is necessary to work with it.
See the bottom of this page for a complete working example.
A simple form
Programming with reactive forms has two distinct steps:
-
Defining your form, i.e. defining the fields that compose the result, how they are composed, and what validation must be run on them.
-
Rendering your form, i.e. creating the markup that will be used and connecting the input fields with the reactive values created in the first step.
Defining a form
In this step we create a value of type Form<'T, 'R>
where:
-
'T
is the type returned by the form. -
'R
is the type of the render builder. It will always have the following shape:which means that the view function from the second step will take arguments
arg1 ... argn
and return whatever type of markup element we want.
Let's create a reactive form to input data about a pet. We will need the species and the name of the pet. First, let's define the corresponding types:
Then, let's define the form itself:
Let's break this down:
Form.Yield
creates a singular reactive value that we can later render, with an initial value.- The
<*>
operator (an alias forForm.Apply
) is used to apply a function to the values of multiple forms by chaining. In this case, it applies the functionfun s n -> { species = s; name = n }
to the values of the two forms that follow it. Form.Return
creates a form that returns a value of typePet
when the function is applied to the values of the two forms.- Validation can be applied at any level (singular or composed values). Here, a built-in validator is applied to the name field, which checks that it is not empty. If it is empty, an error message will be shown. Validation checks are performed when the form is evaluated (on every change or on submit, see later).
So in total, the PetForm
function describes a functionality of a form with two input fields and how they are initialized from and composed into a single Pet
value. This functionality is reusable and can be rendered in different ways.
However, this function is still generic, which will allow the type system to plug in the actual type of the rendered form. In this way, the form composition itself are not tied to a specific rendering implementation, like WebSharper.UI's Doc
.
We have now defined how a species and a name should be composed into a Pet, and how the name should be verified. Time to define how to render the corresponding reactive form.
Rendering a form
The form we defined has the following type:
The first type argument to our form is Pet
, as expected, since that's what we want to return. The second type argument has the shape described previously: it takes as argument a function from several arguments (two Var
s), and calls it with the appropriate Var
s to obtain the rendered document.
Here, species
has type Var<Species>
, and name
has type Var<string>
. So the type of RenderPet
corresponds to the argument of the second type parameter of PetForm
, with 'b
specialized to Doc
.
The functions Doc.InputType.Radio
and Doc.InputType.Input
come from WebSharper.UI, and create elements whose value is always synchronized with the Var
they receive.
Here their attributes are empty, but you are free to enhance them with styling, for example:
In order to use RenderPet
to render the pet form, we use Form.Render
:
We now have a value PetFormUI : Doc
that we can integrate directly into our HTML markup. It will display a radio list and a text input field, and update the resulting Pet
value according to user input in these two fields.
Note that right now, we are not doing anything with this resulting Pet
. The simplest way to do so is using Form.Run
, which calls a function every time the value is changed.
More complex forms
Submit button
The above PetFormUI
is not very user friendly: it triggers (and shows an alert window) every time the user inputs a character. Let's fix this by adding a submit button to the underlying form.
Now PetFormWithSubmit
only triggers a new return value when the user submits the form. A new value of type Submitter<Pet>
is passed to the view function, and rendering it is just as simple:
If you want the submit button to be grayed out when the input is invalid (i.e. in our case, when the name field is empty), use Doc.ButtonValidate
instead.
Displaying values and error messages
We have already seen Form.Run
; but another common action to do with the result value is to display it. You can get the result from the View
property on the submitter. It has a value of the following type:
where ErrorMessage
has a Text
field containing the text message. Here is an example:
Note that we've been showing the result after submission. If you want to use the live value as it is input by the user, either to display it or for some other purpose, it is available as submit.Input
.
Forms for collections
Let's make this form more complex by asking the user about their own name and a list of their pets. They will be able to add, remove and reorder pets in the form.
Here is the final data we want to collect:
Defining a form for this type is relatively straightforward using a function from the Form.Many*
family:
The function Form.Many
takes three arguments:
-
The initial collection of values, of type
seq<Pet>
. -
The value of type
Pet
with which the new sub-form should be initialized when the user inserts a new pet. -
A function taking an initial
Pet
value and returning theForm<Pet, _>
that will be shown for each pet.
It returns a form whose value is a sequence of Pet
s, and adds an argument to the render function of type Form.Many.CollectionWithDefault<'T, 'V, 'W>
. The type 'T
is the type of items in the collection, and 'V -> 'W
is the type of the render builder for a single item. This is how you render such a stream:
The function passed to pets.Render
is called once for every new item in the collection, and defines how this individual item should be rendered. It takes as arguments:
-
A value of type
Form.Many.ItemOperations
, namedops
here. This value has members that allow to move the current item up or down in the collection, or to delete it. -
The arguments of the render function for the item rendering form, ie. the form that was passed as the third argument to
Form.Many
.
CollectionWithDefault
also contains a callback called Add
that adds a new pet at the end of the collection.
Localized errors
We have seen how to show all the errors together from submit
. But in many cases it is useful to show the error associated with a given field next to that field. For that purpose, the type View<Result<'T>>
has an extension method Through
that takes a Var
or a Form
, and returns a new View<Result<'T>>
whose value is the same as the original one, except on failure, only error messages associated with the given Var
or Form
are kept. For example, the following shows the error messages associated with firstName
:
Complete example
Here is now the complete example, showcasing all the elements described in this tutorial.