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:

  • PersonFormlet defines a formlet parameterized with the user-defined type Person.
  • 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 Name field 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
            )
        ]