WebSharper Forms
Documentation for WebSharper.Forms
Introduction to Forms
The WebSharper.Forms (aka. UI.Piglets or reactive piglets/forms, see also ordinary piglets) library provides a high-level abstraction for working with web forms and constructing interactive user interfaces. It is closely related to formlets (see also UI.Formlets) as they both provide the capability to declaratively describe user data input such as forms, including data validation and feedback. The main difference comes from the way actual widgets are rendered: Formlets automatically generate input fields and layout markup, while reactive forms let the developer render the composed form using custom markup.
You should use Piglets/Forms in one of these cases:
-
You want to have absolute control over the rendering of the form.
-
You are creating an application for different frontends (e.g. a web version using JQueryUI and a mobile version using JQueryMobile) and would like to factor the data definition and validation code, leaving only the actual rendering to be differenciated.
You should use Formlets in one of these cases:
-
You want your code to be extremely concise and define at the same time how input data is composed and validated, and how input fields are rendered.
-
You are developing a back-office application, prioritizing speed of development over pixel-perfect design.
In all cases, Piglets/Forms and Formlets have a lot in common:
-
They are type-safe: unlike many "strongly typed" templating engines, in which a field is only identified by an id and there is no way to check that, say, a datepicker is indeed associated with a field of type
Date
, Formlets and Piglets/Forms are able to guarantee such properties. -
Data composition and validation is done declaratively, and the dynamic aspects of computing and checking a result value are automated.
-
They are composable: you can define a Xlet and use it as part of a bigger Xlet.
-
They can express dependent sub-forms, 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 easy. Users might be frightened by their cryptic type signature so this guide intends to explain what is the meaning of all the elements that compose a Form. In addition, introductory examples will be presented to get the user acquainted with Forms.
We recommend to read the Formlets documentation first, as some concepts, such as the <*>
operator or validation, will be introduced with less detail here.
Forms is an implementation of piglets based on WebSharper UI. Therefore familiarity with concepts such as Var
, View
and the Doc
type for HTML is necessary to work with it.
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:
If you learned about Formlets already, this should look familiar. We first define a Form with a function type, and then successively compose it with each field. The main difference is that here, the fields do not declare how they will be rendered. Form.Yield init.name
only creates a Form whose value has type string
and which is initialized with init.name
. Contrast with Formlet's Controls.Input init.name
, which also declares that it should be rendered as an input field.
The types of Form.Return
, <*>
and Form.Yield
are more complex than their Formlet counterparts, since they also deal with composing the view builder.
Validation is also very similar to Formlet validation: the Form is passed through a combinator, Validation.IsNotEmpty
, that defines the condition that must be fulfilled and the error message in case it isn't.
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 Element
.
The functions Doc.Radio
and Doc.Input
come from WebSharper.UI, and create elements whose value is always synchronized with the Var
they receive. Note that, unlike Formlets which include layout markup, these functions only render the needed input elements, allowing you to lay them out and style them as you want. For example, you can add attributes directly to the input element:
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.