HTML5 Form Helpers For WebMatrix

It's an odd thing: WebMatrix 2 is lauded for its support of HTML5, but the Web Pages framework doesn't include any helpers for the new HTML5 form input types such as email or color. It's fair to say that support among browsers for the new inputs is patchy at the moment, but with barely a week flying past without a new version of one or other browser being released, it won't take long for that to change. So in the meantime, if you prefer to use helpers, you have to write your own for rendering HTML5 forms.

The new HTML5 form inputs cater for a wide variety of data types:

  • Color - should display as a colour picker of some sort
  • Date - will probably invoke a date picker
  • DateTime - will probably invoke a date picker combined with a mechanism for selecting hours and minutes and a time zone
  • DateTime-Local - as above but without specifying a time zone
  • Email - validates a string to ensure it is in an acceptable format for an email address
  • Month - will enable the user to select a month
  • Number - will allow users to supply a number value, possible through the use of a spinner control or similar
  • Range - a slider for selecting a number within a range
  • Search - behaves like a normal text box, but may be styled differently by the browser
  • Tel - text box used for telephone numbers. May be given some additional behaviour by mobile browsers
  • Time - enables input of a time in 24 hour format and validates it
  • Url - validates a string to ensure it is in an accepted format for a URL
  • Week - will enable the user to select a week number

While browser support varies, you can begin to use these new input types right now as browsers that don't understand the value you provide for the type attribute of an input element will always fall back on type="text" and render the element as a regular text box. Where they are implemented in browsers, they feature a number of new attributes such as "required", "min" and "max", allowing you to ensure that fields are mandatory, and to restrict the range of acceptable values. Moreover, validation on submission is performed by the browser, which means no more need for client-side JavaScript validation. Different browsers implement this in different ways (and to different extents at the moment).

The Web Pages framework was released as Open Source not long ago. That means the source code for the existing input helpers is available for all to see without having to obtain some kind of disassembly software. The code for the HTML5 helpers borrows a lot of inspiration from that:

using System;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.WebPages;
using System.Web.WebPages.Html;

public static class Html5Extensions
{
    private enum InputType{
        Color, Date, DateTime, DateTime_Local, Email, Month, Number, Range, Search, Tel, Text, Time, Url, Week
    }

    public static IHtmlString Color(this HtmlHelper helper, string name, object value = null, object attributes = null){
        if (name.IsEmpty()) {
            throw new ArgumentException("Value cannot be null or an empty string.", "name");
        }
        return BuildInputTag(name, InputType.Color, value, attributes);
    }

    //Other methods removed for brevity

    private static IHtmlString BuildInputTag(string name, InputType inputType, object value = null, object attributes = null) {
        TagBuilder tag = new TagBuilder("input"); 
        tag.MergeAttribute("type", GetInputTypeString(inputType));
        tag.MergeAttribute("name", name, replaceExisting: true);
        tag.GenerateId(name);
        if (value != null || HttpContext.Current.Request[name] != null) {
            value = value != null ? value : HttpContext.Current.Request[name];
            tag.MergeAttribute("value", value.ToString());
        }
        if (attributes != null) {
            var dictionary = attributes.GetType()
             .GetProperties()
             .ToDictionary(prop => prop.Name, prop => prop.GetValue(attributes, null));
            tag.MergeAttributes(dictionary, replaceExisting: true);
        }
        return new HtmlString(tag.ToString(TagRenderMode.SelfClosing));
    }

    private static string GetInputTypeString(InputType inputType) {
        if (!Enum.IsDefined(typeof(InputType), inputType)) {
            inputType = InputType.Text;
        }
        return inputType.ToString().Replace('_', '-').ToLowerInvariant();
    }
}

 

The class is declared as static. This is needed because the helpers are all extension methods on the HtmlHelper class. That means that theyr will appear as additional methods on the WebPage's HtmlHelper property. The enumeration (InutType) is declared to hold values that will be used to identify the type of input that the BuildInputTag method is responsible for constructing. I could pass in simple strings to represent the type of tag to be built, but enumerations are type-safe and prevent typo errors. The GetInputTypeString method is responsible for converting the enum value to its string representation.

The main method is the BuildInputTag method. This is responsible for constructing and outputting the HTML for the input. It uses the TagBuilder class, and piece by piece, builds the tag with any attributes specified. It also sets the "value" by default to a matching value if one is found in the Request.Form collection. This will repopulate form fields in the event, say, of failed validation.

The code above illustrates the Color input helper. The code for all other input helpers is identical except for the InputType value passed to the BuildInputTag method and obviously, the name of the method. For example, this is the Url method:

public static IHtmlString Url(this HtmlHelper helper, string name, object value = null, object attributes = null) {
    if (name.IsEmpty()) {
        throw new ArgumentException("Value cannot be null or an empty string.", "name");
    }
    return BuildInputTag(name, InputType.Url, value, attributes);
}

The download that accompanies this article includes methods for all other HTML5 input types. The helpers are saved in a C# class file called Html5Extensions.cs in the App_Code folder.

You use the helpers in largely the same way as the existing form helpers: start typing @Html. and the new extension methods will be available in IntelliSense. The blue downward-pointing arrow next to the pink cube denotes that the method is an extension method:

The following snippet shows a number of the helpers being used with some of the new HTML5 attributes

<form method="post">
    <div>Email</div>
    @Html.Email("email", attributes: new {required = "required"})
    <div>Colour</div>
    @Html.Color("colour")
    <div>Date Time</div>
    @Html.DateTime("date")
    <div>Range</div>
    @Html.Range("range")
    <div>Number</div>
    @Html.Number("number", attributes: new { min=5, max=100, step=5 })
    <div>Search</div>
    @Html.Search("search", attributes: new { placeholder = "Enter Search Term" })
    <div><input type="submit" /></div>
</form>

Different browsers offer differing levels of support for these, and where they do offer support for a particular inut type, they may render them in different ways:

The required attribute specifies that the field is mandatory, and browsers may prevent form submission if no value is provided:

The balloons or callouts in the above image belong to the browser. They have not been invoked using any client-side code or libraries.

You can also see the placeholder attribute working in all browsers except IE9. It provides a default value for the textbox, which disappears when you click into it to start typing. It reappears if you move the focus away without typing anything into the textbox. The default text does not get posted if the form is submitted. You have to use JavaScript to achieve this within an HTML 4.1 or XHTML 1.0 document.

The min, max and step attributes have been applied to the Number input. The Number input is represented as a spinner control in Chrome, Opera and Safari. It will restrict the range of values that can be selected, and will ensure that they are only available in steps of 5 in this particular example.

While HTML5 controls include built-in validation support, you should not rely on that any more than you can rely on client-side Javascript validation currently. Ignoring the fact that not all browsers support these new attributes or input types, where they are supported, the values can be altered or even removed very easily using the developer tools that come with most browsers these days. Server-side validation works with these helpers in exactly the same way as with the built-in form helpers:

@{
    Validation.Add("email", 
        Validator.Required("You must provide an email address"),
        Validator.Regex(@"^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4}$", 
        "Invalid format for an email address")
        );
    if(IsPost){
        if (!Validation.IsValid()) {
            ModelState.AddFormError("There are some errors with your submission"); 
        }
    }
}

<!DOCTYPE html>

<html lang="en">
    <head>
        <meta charset="utf-8" />
        <title>My Site's Title</title>
        <style>
            body{font-family:Arial; font-size:12px; margin-left:20px;}
            div{padding-top: 5px;}
        </style>
    </head>
    <body>
        @Html.ValidationSummary(true)
        <form method="post">
            <div>Email</div>
            @Html.Email("email", attributes: new {required = "required"})
            @Html.ValidationMessage("email")
            <div>Colour</div>
            @Html.Color("colour")
            <div>Date Time</div>
            @Html.DateTime("date")
            <div>Range</div>
            @Html.Range("range")
            <div>Number</div>
            @Html.Number("number", attributes: new { min=5, max=100, step=5 })
            <div>Search</div>
            @Html.Search("search", attributes: new { placeholder = "Enter Search Term" })
            <div><input type="submit" /></div>
         </form>
    </body>
</html>

Server-side validation will kick in on the two browsers that do not support the HTML5 required attribute:

HTML5 support among browsers is growing rapidly - especially in the mobile browser space. If you are working predominantly with HTML5, I hope these form helpers will be useful. If you are not there yet, the article should give you a good idea about how to create your own helpers to work with WebMatrix.

A repo containing the complete code for this article is available at GitHub.