Working with WebSharper formlets
One of the most common tasks in web development is creating web forms for collecting user data. Among other things this involves:
- Creating forms to collect data
- Looking up the submitted data on the server side.
- Validating this data.
- Providing validation feedback to the user.
WebSharper formlets simplify all of the above tasks. The advantages of using formlets for web-form construction are:
Type Safety
Every Formlet<'T> collects data of type 'T, preventing
type errors. This data type is not limited to primitive values, and often includes
records and unions.
Composability
Complex formlets are constructed by composing simpler components.
Reusability
Once defined, a formlet may be reused as a component of many other formlets and pages.
Validation
Formlets are aware of the need to validate the collected data and provide interactive feedback to the user.
Below is an example of a formlet for entering name and email information:
type Person = {
Name: string
Email: string
}
[<JavaScript>]
let PersonFormlet () : Formlet<Person> =
let nameF =
Controls.Input ""
|> Validator.IsNotEmpty "Empty name not allowed"
|> Enhance.WithValidationIcon
|> Enhance.WithTextLabel "Name"
let emailF =
Controls.Input ""
|> Validator.IsEmail "Please enter valid email address"
|> Enhance.WithValidationIcon
|> Enhance.WithTextLabel "Email"
Formlet.Yield (fun name email -> {Name = name; Email = email})
<*> nameF
<*> emailF
|> Enhance.WithSubmitAndResetButtons
|> Enhance.WithLegend "Add a New Person"
|> Enhance.WithFormContainer
[<JavaScript>]
let Main () =
Div [PersonFormlet ()]
The above formlet is rendered as:
In short:
PersonFormletdefines a formlet parameterized with the user-defined typePerson.- As with any formlet, it can be rendered as a web form on its own or be composed with other formlets.
- It contains validation logic to guarantee that the
Namefield is not empty and the email address is of the correct format.
Declarative specification
When using formlets, you only need to specify the essence of your input forms - which controls to add, what validation logic to apply, and how to aggregate the result of the sub-formlets. All the tedious work of creating HTML elements, extracting form data, and applying data validation is automated.
Controls
In the Control module you find a set of predefined formlets corresponding
to some basic web form components such as input text fields, text areas and check
boxes.
Composing formlets
The key feature of formlets is the ability to build complex formlets by composing simpler ones.
The Formlet.Yield and Formlet.Apply (or <*>)
functions provide the default way to combine formlets. Formlet.Yield
accepts a function specifying how to combine the values of several formlets, and
Formlet.Apply incrementally provides the formlets to compose.
In the following example two string formlets are composed:
[<JavaScript>]
let ComposedFormlet () : Formlet<string> =
Formlet.Yield (fun x y -> x + " " + y)
<*> Controls.Select 0 ["Email", "E"; "Mail", "M"]
<*> Controls.Input ""
The value of the composed formlet is the string concatenation of its two constituent formlets. The resulting form is rendered as:
Enhancing formlets
The Enhance module provides a set of predefined functions for enhancing
formlets with additional properties.
As an example, the Enhance.WitTextLabel accepts string corresponding
to the label and a formlet, returning a new formlet with a label.
[<JavaScript>]
let LabeledFormlet =
Control.Input ""
|> Enhance.WithTextLabel "Label"
Dependent Formlets
Dependent formlets are formlets that depend on the values produced by some other formlet. You can specify such dependencies using F# computation expressions:
[<JavaScript>]
let DependentFormlet () =
Formlet.Do {
let! x = Controls.Input ""
return! Controls.Input x
}
The formlet above uses the values produced by the first text-box as input when constructing an additional (dependent) text-box.
Validation
By adding validation you restrict the admissible formlet values by putting the formlet
into a failing state whenever the current value is invalid. Among other things,
this means that the formlet cannot be submitted since no value is available. Some
predefined validators are found in the Validator module.
Here is an example enhancing a text-box formlet with validation requiring that input string is not empty:
Controls.Input ""
|> Enhance.WithTextLabel "Label"
|> Validator.IsNotEmpty "Enter non-empty value"
|> Enhance.WithValidationIcon
|> Enhance.WithSubmitAndResetButtons
|> Enhance.WithFormContainer
Layout
Each formlet carries a layout manager responsible for rendering the visual components
produced when running the formlet. You can use the function Formlet.WithLayout
to specify the layout manager to be used. Three default layout managers are provided:
Formlet.Vertical- Lays out components vertically.Formlet.Horizontal- Lays out components horizontally.Formlet.Flowlet- Creates a wizard-like interface where subsequent components replace the previous ones.
Running Formlets
Since formlets implement the IPagelet interface, they may be directly
embedded inside HTML combinators:
[<JavaScript>]
let Main () =
Div [
Controls.Input "Text-field formlet in div tag"
]
If you want to handle the values produced by a formlet, you may use the Run
function. It accepts a handler that is invoked every time a new value is produced
by the formlet, and returns an IPagelet instance corresponding to the
form body:
[<JavaScript>]
let Main () =
let input =
Controls.Input ""
|> Enhance.WithSubmitButton
Div [
input.Run (fun s ->
processResult s
)
]