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.
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:
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
Getting Started
- Understanding what versions are available
- Installing WebSharper 2.0
- Working with WebSharper Manager
- Working with the WebSharper project templates in Visual Studio
Fundamentals
- Understanding WebSharper ASP.NET integration
- The four pillars of ASP.NET and WebSharper Integration
- Running the WebSharper samples from this site
Abstractions
Migrating
Testimonials
A few random comments from hundreds of WebSharper testimonials:
"The elegance of F# to build web applications? It's a web dev's dream."
– Michael from Denver, USA
"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's highly composable architecture and statically checked JavaScript generation can shorten time to market and lower maintenance costs."
– Richard from New York, USA
"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 is such a great platform, sometimes I forget I'm developing web applications."
– Michael from USA
"Using WebSharper to develop web apps with jQuery has cut my debug time in half."
– Alan from Denver, USA
"Thank you for creating a great product!"
– Jørgen from Norway
"The promise of combining the power of FP to JavaScript and markup is very interesting - thanks for the great product!"
– Jason from USA

