Getting started with formlets

In this tutorial you will learn how to create a newsletter sign-up form using WebSharper formlets.

For information on how to install and use WebSharper 2.0, see Getting Started. This tutorial will cover the basics of WebSharper formlets. This includes:

  • How to constructs input controls
  • How to compose formlets
  • How to add validation
  • How to enhance formlets with labels and validation icons
  • How to handle values produced by a form

Setting up a project

To try this example you can set up a new WebShaper project. The easiest way is to start with an empty WebSharper ASP.NET project: open Visual Studio and select: New -> Project -> WebSharper 2.0 WebApplication (ASP.NET).

This creates a WebSharper project containing a file, Control1, with a default WebSharper control. For simplicity you'll be using this control to host the example from this tutorial.

For a complete minimal example of using formlets, simply replace the code in Control1.fs with the following:
    namespace WebSharperProject

    open IntelliFactory.WebSharper
    open IntelliFactory.WebSharper.Html
    open IntelliFactory.WebSharper.Formlet

    module Forms =

        [<JavaScript>]
        let SignupForm () =
            Controls.Input ""

    type Control1() =
        inherit Web.Control()

        [<JavaScript>]
        override this.Body = upcast Forms.SignupForm ()

Here, you opened the IntelliFactory.WebSharper.Formlet namespace, added a module, Forms, and changed the Body property of Control1 to invoke the Forms.SignupForm function. SignupForm currently returns a textbox formlet.

To run the sample, hit CTRL-F5 and make sure to load the Default.aspx in your web browser. Doing this you will see a single textbox component rendered on the page:

Single textbox rendered

Building the newsletter sign-up form

For this tutorial you want to create a newsletter sign-up form, collecting three pieces of information - name, email, and email format (either HTML, text, or mobile).

To be able to represent values containing sign-up information, you define the following types:

    type EmailFormat = Html | Text | Mobile

    type UserInfo =
        {
            Name : string
            Email : string
            EmailFormat: EmailFormat
        }

Now you want to construct a formlet that on submission produces values of type UserInfo.

The name and email information are naturally gathered using text-boxes, and for the email format you can use a select box that allows the user to choose one of the available options. The Controls module contains basic HTML form controls such as buttons, text-boxes, text-areas and select-boxes. The different sub forms are defined as:

    [<JavaScript>]
    let SignupForm () =
        let nameF = 
            Controls.Input ""
        let emailF =
            Controls.Input ""
        let formatF =
            [
                "Html" , Html
                "Text", Text
                "Mobile", Mobile
            ]
            |> Controls.Select 0
        ...

The function Controls.Input constructs a formlet producing values of type string, corresponding to the text entered in a text-box. Controls.Select requires you to specify an index (of the default value) and a list of labels and value pairs. It then returns a formlet of same type as the type of the provided values. In our case, the type of the formatF formlet is Formlet<EmailFormat>.

To get a working form collecting UserInfo values, all you need to do is to compose the three sub formlets. Compositionality is one of the most important aspects of formlets, through this formlets allow you to construct instances of arbitrary complexity by combining simpler ones.

Basic composition is supported by Formlet.Yield and the static composition operator <*>.

Skipping the details, the function argument to Formlet.Yield can be thought of as a specification of how to compose the values produced by constituent sub formlets.

Here is the continuation of the sign-up formlet; composing the name, email and email format formlets into a UserInfo formlet:

    [<JavaScript>]
    let SignupForm () =
        let nameF = 
            Controls.Input ""
        let emailF =
            Controls.Input ""
        let formatF =
            [
                "Html" , Html
                "Text", Text
                "Mobile", Mobile
            ]
            |> Controls.Select 0
        Formlet.Yield (fun name email format -> 
            {
                Name = name
                Email = email
                EmailFormat = format
            }
        )
        <*> nameF 
        <*> emailF
        <*> formatF

It's important to realize that the order of the arguments of the lambda function applied to Formlet.Yield, matches the order of the composed formlets. For instance, the first argument corresponds to the value produced by the nameF formlet, and so on.

Running the web project again, you should now see the composed formlet:

Although functional, the form is not very user friendly. To improve on this, you can decorate with some labels. Simple text labels are easily added to any formlet using the function Enhance.WithTextLabel. It accepts a string representing the label, and a formlet. In return it produces a new formlet, enhanced with the label information.

Note the additional Enhance.WithTextLabel rows:

    [<JavaScript>]
    let SignupForm () =
        let nameF = 
            Controls.Input ""
            |> Enhance.WithTextLabel "Your Name"
        let emailF =
            Controls.Input ""
            |> Enhance.WithTextLabel "Your Email"
        let formatF =
            [
                "Html" , Html
                "Text", Text
                "Mobile", Mobile
            ]
            |> Controls.Select 0
            |> Enhance.WithTextLabel "Email Format"
        Formlet.Yield (fun name email format -> 
            {
                Name = name
                Email = email
                EmailFormat = format
            }
        )
        <*> nameF 
        <*> emailF
        <*> formatF

A quick CTRL-F5 unveils the new look:

Adding validation

One limitation of this current form is that it accepts any kind of input. On the other hand, you probably want to require a non-empty name and a valid email address. Luckily, the Validator module provides the necessary toolset for this.

For example, Validator.IsNotEmpty accepts a string argument representing the error message for invalid input, along with a string formlet and returns a new string formlet that only triggers values that meet the non-emptiness criteria. Similarly, you may use Validator.IsEmail to validate email addresses.

While you're at it, you may also extend the form with Submit and Reset buttons. This is most easily accomplished using the Enhance.WithSubmitAndResetButtons function as shown in the listing below. Also note the extra validation lines.

    [<JavaScript>]
    let SignupForm () =
        let nameF = 
            Controls.Input ""
            |> Validator.IsNotEmpty "Please add a non empty value"
            |> Enhance.WithTextLabel "Your Name"
            
        let emailF =
            Controls.Input ""
            |> Validator.IsEmail "Please add a valid email address"
            |> Enhance.WithTextLabel "Your Email"
        let formatF =
            [
                "Html" , Html
                "Text", Text
                "Mobile", Mobile
            ]
            |> Controls.Select 0
            |> Enhance.WithTextLabel "Email Format"
        (
            Formlet.Yield (fun name email format -> 
                {
                    Name = name
                    Email = email
                    EmailFormat = format
                }
            )
            <*> nameF 
            <*> emailF
            <*> formatF
        )
        |> Enhance.WithSubmitAndResetButtons

The final step is to pimp the form by wrapping it in a formlet container, and adding validation icons.

Validation icons provide visual feedback to the user about the current state of a form. To enhance a formlet with a validation icon, you simply pipe it through the Enhance.WithValidationIcon function. Icons are only visible if the rendered form element is attached with the css class formlet. The function Enhance.WithFormContainer does this, and assures that the default formlet CSS kicks in. If you are not happy with the default look, you may of course override the appropriate CSS properties. Here is the final version of the sign-up form, extended with a form container and validation icons:

    [<JavaScript>]
    let SignupForm () =
        let nameF = 
            Controls.Input ""
            |> Validator.IsNotEmpty "Please add a non empty value"
            |> Enhance.WithValidationIcon
            |> Enhance.WithTextLabel "Your Name"

        let emailF =
            Controls.Input ""
            |> Validator.IsEmail "Please add a valid email address"
            |> Enhance.WithValidationIcon
            |> Enhance.WithTextLabel "Your Email"
        let formatF =
            [
                "Html" , Html
                "Text", Text
                "Mobile", Mobile
            ]
            |> Controls.Select 0
            |> Enhance.WithTextLabel "Email Format"
        (
            Formlet.Yield (fun name email format -> 
                {
                    Name = name
                    Email = email
                    EmailFormat = format
                }
            )
            <*> nameF 
            <*> emailF
            <*> formatF
        )
        |> Enhance.WithSubmitAndResetButtons
        |> Enhance.WithFormContainer

Here the rendered version of the form from above:

Handling values

With the visuals out of the way, the question of submitting form data is still open. In other words, how do you collect the values produced by the form, and, for example, call a server-side method for saving the values in the database?

There are several ways to accomplish this. One of them is to utilize the Formlet.Run function. This function accepts a callback to be invoked whenever a value is produced by the provided formlet.

To mimic the server-side function for saving the user entry, an additional Server module, with a dummy SubmitUserInfo function is included:

module Server =
    []    
    let SubmitUserInfo userInfo =
        // Save in data base
        ()

To apply it as a handler to your sign-up formlet, you use the Formlet.Run function as follows:

type Control1() =
    inherit Web.Control()
    [<JavaScript>]
    override this.Body = 
        Forms.SignupForm ()
        |> Formlet.Run Server.SubmitUserInfo

Conclusion

Subsequent tutorials will cover how to create more user friendly interfaces for posting form data. For example, by outputting some feedback on the submission status, or redirecting to another page after a successful submission.

Finally, here is the listing of the complete code for this tutorial:

namespace WebSharperProject

open IntelliFactory.WebSharper
open IntelliFactory.WebSharper.Html
open IntelliFactory.WebSharper.Formlet

module Forms =

    type EmailFormat = Html | Text | Mobile
    type UserInfo =
        {
            Name : string
            Email : string
            EmailFormat: EmailFormat
        }

    [<JavaScript>]
    let SignupForm () =
        let nameF = 
            Controls.Input ""
            |> Validator.IsNotEmpty "Please add a non empty value"
            |> Enhance.WithValidationIcon
            |> Enhance.WithTextLabel "Your Name"
            
        let emailF =
            Controls.Input ""
            |> Validator.IsEmail "Please add a valid email address"
            |> Enhance.WithValidationIcon
            |> Enhance.WithTextLabel "Your Email"
        let formatF =
            [
                "Html" , Html
                "Text", Text
                "Mobile", Mobile
            ]
            |> Controls.Select 0
            |> Enhance.WithTextLabel "Email Format"
        (
            Formlet.Yield (fun name email format -> 
                {
                    Name = name
                    Email = email
                    EmailFormat = format
                }
            )
            <*> nameF 
            <*> emailF
            <*> formatF
        )
        |> Enhance.WithSubmitAndResetButtons
        |> Enhance.WithFormContainer

module Server =

    [<Rpc>] 
    let SubmitUserInfo userInfo =
        // Save data
        ()

type Control1() =
    inherit Web.Control()

    [<JavaScript>]
    override this.Body = 
        Forms.SignupForm ()
        |> Formlet.Run Server.SubmitUserInfo

Testimonials

A few random comments from hundreds of WebSharper testimonials:

WebSharper quote

"The elegance of F# to build web applications? It's a web dev's dream."

Michael from Denver, USA

WebSharper quote

"I'm really excited to build sites in this way, I think it'll make my workflow a lot smoother."

Ryan from Washington, USA

WebSharper quote

"WebSharper's highly composable architecture and statically checked JavaScript generation can shorten time to market and lower maintenance costs."

Richard from New York, USA

WebSharper quote

"I'd like to take this opportunity and say that I really love WebSharper and F# and have become a strong advocate of both."

Terry from New Zealand

WebSharper quote

"WebSharper is such a great platform, sometimes I forget I'm developing web applications."

Michael from USA

WebSharper quote

"Using WebSharper to develop web apps with jQuery has cut my debug time in half."

Alan from Denver, USA

WebSharper quote

"Thank you for creating a great product!"

Jørgen from Norway

WebSharper quote

"The promise of combining the power of FP to JavaScript and markup is very interesting - thanks for the great product!"

Jason from USA

Join thousands of developers

Follow us on Twitter, check out our Google or LinkedIn groups, or subscribe to our blog RSS feed.