October 2010 - Posts

This article is divided into three parts. In the first, part I will show you how you can add Html5 forms in your ASP.NET MVC application with very minimum effort. In the second part, I will show you how to implement client side validation which will trigger automatically even when the browser does not have the html5 client side validation support and in the last part I will show widgetify the form in the client side with jQuery for the older browser that does not have support for Html5 form.

Let’s begin with the server side first. To add Html5 form I will utilize the ModelMetadata and DataAnnotation features that are available in ASP.NET MVC. The DataAnnotation has several attributes which we can use when generating the input types. Let’s say I have a dummy model which is fully decorated with the DataAnnotation attributes:

public class Html5FormModel
{
    [Display(Name = "String", Prompt = "Type a string"), Required]
    public string StringProperty { get; set; }

    [Display(Name = "Decimal", Prompt = "Type a decimal"), Range(1.0, 100.0), Required]
    public decimal? DecimalProperty { get; set; }

    [Display(Name = "Date", Prompt = "Type a date"), DataType(DataType.Date), Required]
    public DateTime? DateProperty { get; set; }

    [Display(Name = "Url", Prompt = "Type an url"), DataType(DataType.Url), Required]
    public string UrlProperty { get; set; }

    [Display(Name = "Email", Prompt = "Type an email"), DataType(DataType.EmailAddress), Required]
    public string EmailProperty { get; set; }

    [Display(Name = "Phone number", Prompt = "Type a phone number"), DataType(DataType.PhoneNumber), Required]
    public string PhoneProperty { get; set; }
}

And I am using the following code to generate the form:

@using (Html.BeginForm()) {
    @Html.ValidationSummary(true, "Validation errors. Please correct the errors and try again.")
    <div>
        <fieldset>

            <legend>Html5 form</legend>

            <div class="editor-label">
                @Html.LabelFor(m => m.StringProperty)
            </div>
            <div class="editor-field">
                @Html.EditorFor(m => m.StringProperty)
                @Html.ValidationMessageFor(m => m.StringProperty)
            </div>

            <div class="editor-label">
                @Html.LabelFor(m => m.DecimalProperty)
            </div>
            <div class="editor-field">
                @Html.EditorFor(m => m.DecimalProperty)
                @Html.ValidationMessageFor(m => m.DecimalProperty)
            </div>

            <div class="editor-label">
                @Html.LabelFor(m => m.DateProperty)
            </div>
            <div class="editor-field">
                @Html.EditorFor(m => m.DateProperty)
                @Html.ValidationMessageFor(m => m.DateProperty)
            </div>

            <div class="editor-label">
                @Html.LabelFor(m => m.UrlProperty)
            </div>
            <div class="editor-field">
                @Html.EditorFor(m => m.UrlProperty)
                @Html.ValidationMessageFor(m => m.UrlProperty)
            </div>

            <div class="editor-label">
                @Html.LabelFor(m => m.EmailProperty)
            </div>
            <div class="editor-field">
                @Html.EditorFor(m => m.EmailProperty)
                @Html.ValidationMessageFor(m => m.EmailProperty)
            </div>

            <div class="editor-label">
                @Html.LabelFor(m => m.PhoneProperty)
            </div>
            <div class="editor-field">
                @Html.EditorFor(m => m.PhoneProperty)
                @Html.ValidationMessageFor(m => m.PhoneProperty)
            </div>

            <p>
                <input type="submit" value="Submit" />
            </p>

        </fieldset>
    </div>
}

Check that I am not using the TextBox of HtmlHelper as it will inject the old input type="text" instead I am using the EditorFor so that I can put my own template to render the inputs. Now, If you open the zip file that I have attached in the bottom of the post, you will find there are separate editor template for each type of DataAnnotation DataType attribute and Html5 form input type (both chstml and aspx templates are included). Some of the codes are something like these:

<input type="date"/>

@Html.Html5Form().InputDate()

<input type="email"/>

@Html.Html5Form().InputEmail()

<input type="number"/>

@Html.Html5Form().InputNumber()

Behind the scene there is class InputHelper which does the actual heavy lifting, here is the code:

public static class HtmlHelperExtension
{
    public static Html5Form Html5Form(this HtmlHelper instance)
    {
        return new Html5Form(instance);
    }
}

public class Html5Form
{
    private readonly HtmlHelper htmlHelper;

    public Html5Form(HtmlHelper htmlHelper)
    {
        this.htmlHelper = htmlHelper;
    }

    private static CultureInfo Culture { get { return CultureInfo.CurrentCulture; } }

    public virtual IHtmlString InputText()
    {
        return Build("text");
    }

    public virtual IHtmlString InputEmail()
    {
        return Build("email");
    }

    public virtual IHtmlString InputUrl()
    {
        return Build("url");
    }

    public virtual IHtmlString InputNumber()
    {
        return Build("number");
    }

    public virtual IHtmlString InputRange()
    {
        return Build("range");
    }

    public virtual IHtmlString InputPassword()
    {
        return Build("password");
    }

    public virtual IHtmlString InputTelephone()
    {
        return Build("tel");
    }

    public virtual IHtmlString InputDate()
    {
        return Build("date");
    }

    public virtual IHtmlString InputTime()
    {
        return Build("time");
    }

    public virtual IHtmlString InputDateTime()
    {
        return Build("datetime");
    }

    private IHtmlString Build(string type)
    {
        var viewData = htmlHelper.ViewData;
        var metadata = viewData.ModelMetadata;

        var placeHolder = !string.IsNullOrWhiteSpace(metadata.Watermark) ? "placeholder=\"" + metadata.Watermark + "\"" : string.Empty;
        var value = viewData.TemplateInfo.FormattedModelValue.ToString();
        var valueAttribute = !string.IsNullOrWhiteSpace(value) ? "value=\"" + value + "\"" : string.Empty;
        var id = viewData.TemplateInfo.GetFullHtmlFieldId(string.Empty);
        var name = viewData.TemplateInfo.GetFullHtmlFieldName(string.Empty);
        var cssClass = "text-box single-line";

        ModelState state;

        if (viewData.ModelState.TryGetValue(name, out state) && (state.Errors.Count > 0))
        {
            cssClass += " " + HtmlHelper.ValidationInputCssClassName;
        }

        var validators = GetValidators(metadata);

        string input = string.Format(Culture, "<input type=\"{0}\" id=\"{1}\" name=\"{2}\" class=\"{3}\" {4} {5} {6}/>", type, id, name, cssClass, valueAttribute, placeHolder, validators);

        return MvcHtmlString.Create(input);
    }

    private string GetValidators(ModelMetadata metadata)
    {
        var unobtrusiveJavaScriptEnabled = htmlHelper.ViewContext.UnobtrusiveJavaScriptEnabled;

        var rules = new StringBuilder();
        var validatorsDictionary = ModelValidatorProviders.Providers
                                                          .GetValidators(metadata, htmlHelper.ViewContext)
                                                          .SelectMany(v => v.GetClientValidationRules());

        foreach (var rule in validatorsDictionary)
        {
            if (rule is ModelClientValidationRequiredRule)
            {
                rules.Append(" required=\"required\"");

                if (unobtrusiveJavaScriptEnabled)
                {
                    rules.AppendFormat(Culture, " data-val-required=\"{0}\"", HttpUtility.HtmlEncode(rule.ErrorMessage ?? string.Empty));
                }

            }
            else if (rule is ModelClientValidationRegexRule)
            {
                rules.AppendFormat(Culture, " pattern=\"{0}\"", rule.ValidationParameters["pattern"]);

                if (unobtrusiveJavaScriptEnabled)
                {
                    rules.AppendFormat(Culture, " data-val-pattern=\"{0}\"", HttpUtility.HtmlEncode(rule.ErrorMessage ?? string.Empty));
                }
            }
            else if (rule is ModelClientValidationRangeRule)
            {
                rules.AppendFormat(Culture, " min=\"{0}\"", rule.ValidationParameters["min"]);
                rules.AppendFormat(Culture, " max=\"{0}\"", rule.ValidationParameters["max"]);

                if (unobtrusiveJavaScriptEnabled)
                {
                    rules.AppendFormat(Culture, " data-val-range=\"{0}\"", HttpUtility.HtmlEncode(rule.ErrorMessage ?? string.Empty));
                }
            }
            else
            {
                if (!unobtrusiveJavaScriptEnabled)
                {
                    continue;
                }

                var dictionaryKey = "data-val-" + rule.ValidationType;

                rules.AppendFormat(Culture, " {0}=\"{1}\"", dictionaryKey, HttpUtility.HtmlEncode(rule.ErrorMessage));

                dictionaryKey = dictionaryKey + "-";

                foreach (var pair in rule.ValidationParameters)
                {
                    rules.AppendFormat(Culture, " {0}=\"{1}\"", dictionaryKey + pair.Key, HttpUtility.HtmlEncode(pair.Value));
                }
            }
        }


        return rules.ToString();
    }
}

Here is the markup that is generated when it is running in the browser:

<form action="/" method="post">
<div>
    <fieldset>
        <legend>Html5 form</legend>
        <div class="editor-label">
            <label for="StringProperty">String</label>
        </div>
        <div class="editor-field">
            <input type="text" id="StringProperty" name="StringProperty" class="text-box single-line" placeholder="Type a string" required="required" />
        </div>
        <div class="editor-label">
            <label for="DecimalProperty">Decimal</label>
        </div>
        <div class="editor-field">
            <input type="number" id="DecimalProperty" name="DecimalProperty" class="text-box single-line" placeholder="Type a decimal" min="1" max="100" required="required" />
        </div>
        <div class="editor-label">
            <label for="DateProperty">Date</label>
        </div>
        <div class="editor-field">
            <input type="date" id="DateProperty" name="DateProperty" class="text-box single-line" placeholder="Type a date" required="required" />
        </div>
        <div class="editor-label">
            <label for="UrlProperty">Url</label>
        </div>
        <div class="editor-field">
            <input type="url" id="UrlProperty" name="UrlProperty" class="text-box single-line" placeholder="Type an url" required="required" />
        </div>
        <div class="editor-label">
            <label for="EmailProperty">Email</label>
        </div>
        <div class="editor-field">
            <input type="email" id="EmailProperty" name="EmailProperty" class="text-box single-line" placeholder="Type an email" required="required" />
        </div>
        <div class="editor-label">
            <label for="PhoneProperty">Phone number</label>
        </div>
        <div class="editor-field">
            <input type="tel" id="PhoneProperty" name="PhoneProperty" class="text-box single-line" placeholder="Type a phone number" required="required" />
        </div>
        <p>
            <input type="submit" value="Submit" />
        </p>
    </fieldset>
</div>
</form>

As you can see all the DataAnnotation attributes are generated with correct input attributes. Input type to DataAnnotation data type, required and range also prompt to placeholder.

In my previous post, I mentioned that most of the current desktop browsers does not have the full support of Html5 form but it will fallback to regular input even we are using the html5 form. Here are the screenshots of some of the major browsers:

Opera 10.6

opera

Chorme 8.0

webkit

FF 3.6

ff

IE9

ie

 

As you can see both chorme and opera are utilizing some of the Html5 form features but firefox and ie is rendering it as regular input elements.

That’s it for today, in the next post I will show the client side validation of html5 form, so stay tuned.

Download: Html5Form.zip

Shout it

This is a continuation of my previous post. In this post, I will do a side by side comparison of Microsoft WebMatrix/WebPage with Ruby Sinatra. The reason I picked Sinatra because both Sinatra and WebMatrix can be used to develop web sites with very minimal effort.

For your reference the Sinatra site is hosted over to heroku(cloud hosting for ruby application) and the source codes are in github.

Lets start with the development model, in MS WebPage, you start with a page and put your application logic in the page intermixed with the markup, very similar to classic asp or php. For example, the following shows codes of the home page of the bakery:

@{
    Page.Title = "Home";

    var db = Database.Open("bakery");
    var products = db.Query("SELECT * FROM PRODUCTS").ToList();
    var featured = products[new Random().Next(products.Count)];
}

<h1>Welcome to Fourth Coffee!</h1>

<div id="featuredProdcut">
    <img alt="Featured Product" src="@HrefAttribute("~/Images/Products/" + featured.ImageName)" />
    <div id="featuredProductInfo">
        <div id="productInfo">
            <h2>@featured.Name</h2>
            <p class="price">$@string.Format("{0:f}", featured.Price)</p>
            <p class="description">@featured.Description</p>
        </div>
        <div id="callToAction">
            <a class="order-button" href="@HrefAttribute("~/order", featured.Id)" title="Order @featured.Name">Order Now</a>
        </div>
    </div>
</div>

<ul id="products">
    @foreach(var p in products){
        <li class="product">
            <div class="productInfo">
                <h3>@p.Name</h3>
                <img class="product-image" src="@HrefAttribute("~/Images/Products/Thumbnails/"+ p.ImageName)" alt="Image of @p.Name" />
                <p class="description">@p.Description</p>                    
            </div>
            <div class="action">
                <p class="price">$@string.Format("{0:f}", p.Price)</p>
                <a class="order-button" href="@HrefAttribute("~/order", p.Id)" title="Order @p.Name">Order Now</a>
            </div>
        </li>
    }
</ul>

But in Sinatra you start with a route, but unlike the ASP.NET MVC or Rails you do not have to declare the route, they are inlined, let me show you the sinatra version of the above code:

# Home
get "/" do

    @title = "Home" # Page title

    # Get all the products
    @products = Product.all(:order => [:name.asc])

    # Picking up a random product as featured
    count = @products.length
    @featured = count > 0 ? @products[rand(count)] : nil

    erb :index # We are using erb as view engine

end

As you can see, in sinatra you start with a http method like Get, Post, Put, Delete, next you have to define the endpoint for which the associated code block will execute, in this case the above code will execute when the root of the site is requested. Now, in the code unlike the WebMatrix which uses plain sql, we are using Ruby DataMapper(there are various other alternates like ActiveRecord, Sequel, MongoDB, CouchDB for data access) to get all the products and among it we are picking up a random product as featured one. Once we are done we are using ERB as view engine to render the view. Like the data access there are also severeal alternates when it comes to view engines, among it Haml is the most popular one, but to make it simple I have decided to go with the ERB. Another nice thing of sinatra is that I can include my view in the same file where I am handling the route or I can even embed my view as inline like erb "Current Time: <%= Time.now =>".  If I include the view in the same file the code will look like the following:

# Home
get "/" do

    @title = "Home" # Page title

    # Get all the products
    @products = Product.all(:order => [:name.asc])

    # Picking up a random product as featured
    count = @products.length
    @featured = count > 0 ? @products[rand(count)] : nil

    erb :index # We are using erb as view engine

end

__END__

@@ index
<h1>Welcome to Sinatra Fourth Coffee!</h1>
<% if @featured %>
<section id="featured">
    <figure><img alt="Featured Product <%= @featured.name %>" src="http://weblogs.asp.net/images/products/<%= @featured.picture %>"/></figure>
    <div>
        <header>
            <h2><%= @featured.name %></h2>
        </header>
        <div>
            <p>$<%= money(@featured.price)  %></p>
            <p><%= @featured.description %></p>
        </div>
        <div>
            <a class="order-button" href="http://weblogs.asp.net/order/<%= @featured.id %>" title="Order <%= @featured.name %>">Order Now</a>
        </div>
    </div>
</section>
<% end %>
<% @products.each do |product| %>
<section class="product">
    <header>
        <h2><%= product.name %></h2>
    </header>
    <figure>
        <img class="thumbnail" src="http://weblogs.asp.net/images/products/thumbnails/<%= product.picture %>" alt="<%= product.name %>"/>
        <figcaption><%= product.description %></figcaption>
    </figure>
    <p>
        <span>$<%= money(product.price) %></span>
        <a class="order-button" href="http://weblogs.asp.net/order/<%= product.id %>" title="Order <%= product.name %>">Order Now</a>
    </p>
</section>
<% end %>

But in the GitHub you will find that I have used separated view files instead of including in the same code file.

In the above code I have mentioned that it is using DataMapper, but I did not show you how it has been configured, here is the configuration of DataMapper, in the WebMatrix example you will find there is only one database table and when an order is received it just sends an email, but in the sinatra version i have extended it a bit more so that it stores the order in the database instead of sending email.

require "rubygems"
require "dm-core"
require "dm-validations"
require 'dm-migrations'

DataMapper::setup(:default, ENV['DATABASE_URL'] || "sqlite3://#{Dir.pwd}/bakery.db")

class Product
    include DataMapper::Resource

    property :id, Serial
    property :name, String, :length => 64
    property :description, Text, :lazy => false
    property :price, Decimal, :precision => 12, :scale => 2
    property :picture, String, :length => 256
end

class Order
    include DataMapper::Resource

    property :id, Serial
    property :created_at, DateTime, :required => true
    property :email, String, :length => 256, :required => true, :format => :email_address
    property :address, String, :length => 1024, :required => true
    property :quantity, Integer, :required => true, :min => 1, :max => 100

    belongs_to :product

end

configure :development do
    # Only Upgrade in development mode
    DataMapper.auto_upgrade!
end

If you are familiar with Fluent NHibernate or Entity Framework Code First then the above is nothing different other than it is ruby code. Also check that we are embedding the validation rules in the property but I think the latest EF code first also has this feature via the Data Annotation. Since we have added all validation rules in the model when an order is received the form validation becomes absolutely easy:

post "/order/:id" do |id|

    product = Product.get(id)

    # Only save if product exists
    if product

        quantity = params[:quantity].empty? ? 0 : Float(params[:quantity])

        @order = Order.new(:product => product, :created_at => Time.now.utc, :email => params[:email], :address => params[:address], :quantity => quantity)

        if @order.save
            redirect("/success/#{@order.id}")
        else
            @title = "Place Your Order: #{product.name}"  # Page title
            erb :order
        end

    else
        # Show 404
        throw_not_found
    end
end

And the View:

and here is the WebMatrix version:

@{
    Page.Title = "Place Your Order";

    var db = Database.Open("bakery");
    var productId = UrlData[0].AsInt();
    var product = db.QuerySingle("SELECT * FROM PRODUCTS WHERE ID = @0", productId);

    if(product == null){
        Response.Redirect("~/");
    }

    if (IsPost) {
        var email = Request["orderEmail"];
        if (email.IsEmpty()) {
            ModelState.AddError("orderEmail", "You must specify an email address.");
        }

        var shipping = Request["orderShipping"];
        if (shipping.IsEmpty()) {
            ModelState.AddError("orderShipping", "You must specify a shipping address.");
        }

        //If there is no error try to process order
        if(ModelState.Count == 0){
        // Mail Sending Code
        }
    }
}

As you can see that in the Sinatra version we are not doing any kind of manual validation, instead the DataMapper is taking care of it, these are done based upon the rules that we have setup in the model. When the order is saved we taking the user to the successful otherwise showing the user the same order page.

Here is the view of the order page:

<progress class="orderProgress" value="2.0" max="3.0">
    <ol>
        <li><span>1</span>Choose Item</li>
        <li class="current"><span>2</span>Details &amp; Submit</li>
        <li><span>3</span>Receipt</li>
    </ol>
</progress>
<h1><%= @title %></h1>
<form id="orderForm" action="" method="post">
    <fieldset>
        <legend>Place Your Order</legend>
        <img class="thumbnail" src="http://weblogs.asp.net/images/products/thumbnails/<%= @order.product.picture %>" alt="<%= @order.product.name %>"/>
        <ul>
            <li>
                <label for="email">Email Address:</label>
                <input type="email" name="email" id="email" value="<%= @order.email %>" placeholder="Type your email" required="required" autofocus="autofocus"/>
                <%= field_validation(@order, :email) %>
            </li>
            <li>
                <label for="address">Shipping Address:</label>
                <textarea rows="4" cols="20" name="address" id="address" placeholder="Type your address" required="required"><%= @order.address %></textarea>
                <%= field_validation(@order, :address) %>
            </li>
            <li>
            <label for="quantity">Quantity:</label>
            <input type="number" name="quantity" id="quantity" min="1" step="1" max="100" value="<%= @order.quantity %>" required="required"/>
            x <span id="price"><%= money(@order.product.price)  %></span> = <output id="total" for="quantity,price">$<%= money(@order.product.price * @order.quantity) %></output>
            <%= field_validation(@order, :quantity) %>
            </li>
            <li><button type="submit" class="order-button">Place Order</button></li>
        </ul>
    </fieldset>
</form>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
<script>!window.jQuery && document.write('<script src="js/jquery-1.4.2.min.js"><\/script>')</script>
<script>
    $(function () {

        var price = parseFloat($("#price").text()).toFixed(2);
        var total = $("#total");
        var quantity = $("#quantity");

        quantity.change(function () {

            var newQuantity = parseInt($(this).val());
            if (!newQuantity || newQuantity < 1) {
                quantity.val(1);
                newQuantity = 1;
            }
            else if (newQuantity.toString() !== quantity.val()) {
                quantity.val(newQuantity);
            }

            total.text("$" + (price * newQuantity).toFixed(2));
        });
    });
</script>

Both in the handler and in the view you will find few methods like throw_not_found, field_validation which are not built-in methods, those are some helper methods, to create helper methods you just have to put those methods in a helper do and end block like the following:

helpers do

    def throw_not_found()
        raise Sinatra::NotFound
    end

    def money(value)
        sprintf("%.02f", value)
    end

    def field_validation(target, field)
        "<span class=\"field-validation-error\">#{target.errors[field][0]}</span>" unless target.errors[field].empty?
    end

end

I think I have shown most of the codes of this application and explains how it works, if the above interests you I would suggest you download the code from the github and check it side by side. Overall I think Sinatra is superior comparing to WebMatrix/WebPage when it comes to writing clean code and separating parts of your application and yet maintaining the simplicity and minimalism. I wonder if Microsoft did evaluate it before choosing the asp/php path and I would definitely like to see similar features baked into it so that real professional can consider it instead of only hobbyist.

Shout it

Unless you are living in a cave, I expect you heard Html5 and know that most of the modern browsers has the reasonable support to render html5 pages. In this post, I will take the tiny sample “Bakery” which comes with the Microsoft WebMatrix as a site template and truly upgrade it to html5. The reason I mentioned “truly” because it is already using html5 doctype but the markup is still in xhtml. You can click here if you want to see a screenshot of this website.

The following is the skeleton of this site:

WebMatrix Bakery Skeleton

Now, let us see the markup when it is rendered in the browser:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <title>Fourth Coffee - Home</title>
    <link href="Styles/Site.css" rel="stylesheet" />
</head>
<body>
    <div id="page">
        <div id="header">
            <p class="site-title"><a href="./">Fourth Coffee</a></p>
            <ul id="menu">
                <li><a href="./">Home</a></li>
                <li><a href="About">About Us</a></li>
            </ul>
        </div>
        <div id="body">
            <h1>Welcome to Fourth Coffee!</h1>
            <div id="featuredProdcut">
                <img alt="Featured Product" src="Images/Products/bread.jpg" />
                <div id="featuredProductInfo">
                    <div id="productInfo">
                        <h2>Bread</h2>
                        <p class="price">$1.49</p>
                        <p class="description">Fresh baked French-style bread</p>
                    </div>
                    <div id="callToAction">
                        <a class="order-button" href="order/4" title="Order Bread">Order Now</a>
                    </div>
                </div>
            </div>
            <ul id="products">
                <li class="product">
                    <div class="productInfo">
                        <h3>Carrot Cake</h3>
                        <img class="product-image" src="Images/Products/Thumbnails/carrot_cake.jpg" alt="Image of Carrot Cake" />
                        <p class="description">A scrumptious mini-carrot cake encrusted with sliced almonds</p>
                    </div>
                    <div class="action">
                        <p class="price">$8.99</p>
                        <a class="order-button" href="order/1" title="Order Carrot Cake">Order Now</a>
                    </div>
                </li>
                <!-- More Products -->
            </ul>
        </div>
        <div id="footer">
            &copy;2010 - Fourth Coffee
        </div>
    </div>
</body>
</html>

As I mentioned earlier that it has some flavor of html5, for example, if you see the first line you will find that it does not use long declaration of doctype like <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">, in html5 it has trimmed down to <!DOCTYPE html>. Next, to specify the content type, it is not required to write <meta http-equiv="Content-Type" content="text/html"; charset="utf-8"/>, instead <meta charset="utf-8"/> is used and at last for script and stylesheet the type is no longer required, for example the stylesheet file has been included with <link href="Styles/Site.css" rel="stylesheet"/> without the type="text/css".

But other than the above, there are nothing more that are html5ish also the markup contains too many div and unnecessary css classes. In order to make it truly meaningful, we are going to use some of the new html tags that comes with the html5. First, let me show you the bare minimum of the layout:

<body>
    <header role="banner">
        <h1><a href="http://weblogs.asp.net/">Fourth Coffee</a></h1>
        <nav>
            <ul>
                <li><a href="http://weblogs.asp.net/" rel="home">Home</a></li>
                <li><a href="http://weblogs.asp.net/about">About Us</a></li>
            </ul>
        </nav>
    </header>
    <div role="main">
        <!-- Put your page content over here -->
    <div/>
    <footer role="contentinfo">
        <p>&copy;2010 - Fourth Coffee</p>
    </footer>
</body>

As you can see that we no longer using any container div (e.g. div id="page") since the body itself can act as the container and now it starts with the new header tag instead of the div id="header", the header also contains role="banner", it is not html5 specific instead it is called ARIA landmark role. Inside the header, rather than using p tag for the site title we are using the standard h1 and using the existing text replacing technique to replace it with the site banner. Next, for the top right navigation we are using the new nav element, but we are not using the ARIA role="navigation" as the nav itself stands for the navigation. The rest of the two element are pretty simple for the main content we are using the plain old div with an ARIA role="main" and the for the copyright we are using the new footer element of html5.

Next, for the products, the WebMatrix template is using div for the featured product and ul for the rest of the product listing. But in our case we are going to use another new element section. Initially, there was a misconception to replace div with section which was wrong, if it is only styling or DOM scripting purpose then you should stick with the div, but if want to group some thematically related content which are also self contained and independent part, you can use the section and in our case it is the product.

Now lets see the new markup:

<div role="main">
    <h1>Welcome to Fourth Coffee!</h1>
    <section id="featured">
        <figure>
            <img src="http://weblogs.asp.net/Images/Products/bread.jpg" alt="Featured Product Bread"/>
        </figure>
        <div>
            <header>
                <h2>Bread</h2>
            </header>
            <div>
                <p>$1.49</p>
                <p>Fresh baked French-style bread</p>
            </div>
            <div>
                <a title="Order Bread" href="http://weblogs.asp.net/order/4" class="order-button">Order Now</a>
            </div>
        </div>
    </section>
    <section class="product">
        <header>
            <h2>Carrot Cake</h2>
        </header>
        <figure>
            <img class="thumbnail" src="http://weblogs.asp.net/Images/Products/Thumbnails/carrot_cake.jpg" alt="Carrot Cake"/>
            <figcaption>A scrumptious mini-carrot cake encrusted with sliced almonds</figcaption>
        </figure>
        <p>
            <span>$8.99</span>
            <a class="order-button" href="http://weblogs.asp.net/order/1" title="Order Carrot Cake">Order Now</a>
        </p>
    </section>
    <!-- More Products -->
<div/>

As you can see that other than using section we are also using the new figure and we are putting the product description in the figcaption also check that each section has its own header like the main document. Now if are wondering what are the css style that are responsible for the styling, here you go:

article, aside, header, figcaption, figure, footer, nav, section
{
    display: block;
}

.thumbnail
{
    background-color: #edece8;
    border: 1px #e6e3d8 solid;
    height: 200px;
    margin: 0 0 7px 0;
    padding: 6px;
    width: 200px;
}

.order-button
{
    background-color: #a52f09;
    border: 1px solid #712107;
    color: #fdfcf7 !important;
    float: right;
    padding: 2px 7px; 
    /*CSS3 properties*/
    -moz-border-radius: 4px;
    -webkit-border-radius: 4px;
    border-radius: 4px;
    -webkit-box-shadow: 1px 2px 4px rgba(136,136,136, 0.6);
    -moz-box-shadow: 1px 2px 5px #b4b4b4;
    box-shadow: 1px 2px 5px #b4b4b4;
}

header[role="banner"]
{
    height: 80px;
}

header[role="banner"] > h1 > a
{
    background: transparent url(../images/brand.png) no-repeat;
    float: left;
    height: 50px;
    margin: 15px;
    padding: 0;
    text-indent: -9999px;
    width: 340px;
}

header[role="banner"] > nav
{
    float: right;
    font-size: 1.4em;
    list-style: none;
}

header[role="banner"] > nav ul
{
    margin-top: 50px;
    list-style: none outside none;
}

header[role="banner"] > nav li
{
    float: left;
    margin-left: 30px;
}

div[role="main"]
{
    border-top: 1px dotted #5d5a53;
    clear: both;
    margin-bottom: 20px;
    padding-top: 20px;
    overflow: hidden;
}

#featured
{
    background-color: #fdfcf7;
    border: 4px solid #e6e3d8;
    height: 300px;
    margin: 20px auto;
    width: 920px;
    /*CSS3 properties*/
    -moz-border-radius: 6px;
    -webkit-border-radius: 6px;
    border-radius: 6px;
    -webkit-box-shadow: 0px 2px 5px #888;
    -moz-box-shadow: 0px 2px 5px #888;
    box-shadow: 0px 2px 5px #888;
}

#featured > figure
{
    float: left;
    /*CSS3 properties*/
    -moz-border-radius: 3px 0px 0px 3px;
    -webkit-border-radius: 3px 0px 0px 3px;
    border-radius: 3px 0px 0px 3px;
}

#featured > div
{
    float: right;
    height: 100%;
    padding: 0 10px;
    position: relative;
    width: 230px;
}

#featured > div h2
{
    font-size: 2.5em;
    font-weight: normal;
    margin: 5px 0;
}

#featured > div header + div p
{
    font-size: 1.75em;
    margin: 5px 0;
}

#featured > div header + div p + p
{
    font-size: 1.2em;
    margin: 0;
}

#featured > div header + div + div
{
    position: absolute;
    left: 5px;
    right: 5px;
    bottom: 10px;
    width: 230px;
}

#featured > div header + div + div a.order-button
{
    font-size: 2em;
    padding: 5px 0;
    text-align: center;
    width: 220px;
}

.product
{
    float: left;
    margin: 10px 12px;
    width: 215px;
}

.product h2
{
    font-size: 1.75em;
    font-weight: normal;
    margin: 0 0 5px 0;
    padding: 0;
}

.product > figure
{
    height: 250px;
}

.product > p
{
    margin-top: 2em;
}

.product > p > span
{
    float: left;
    font-size: 1.5em;
}

One of the thing that I find incorrect in the WebMatrix template Css3 styling is the ordering, rather than calling it incorrect let me call it as backward looking rather than forward looking, for example when declaring the styles they decide to put the standard declaration first then the browser extension, but the correct order should be the browser extension first then the standard declaration:

Backward looking:

border-radius: 4px; 
-moz-border-radius: 4px;
-webkit-border-radius: 4px;
box-shadow: 1px 2px 5px #b4b4b4;
-webkit-box-shadow: 1px 2px 4px rgba(136,136,136, 0.6);
-moz-box-shadow: 1px 2px 5px #b4b4b4;

Forward looking:

-moz-border-radius: 4px;
-webkit-border-radius: 4px;
border-radius: 4px;
-webkit-box-shadow: 1px 2px 4px rgba(136,136,136, 0.6);
-moz-box-shadow: 1px 2px 5px #b4b4b4;
box-shadow: 1px 2px 5px #b4b4b4;

Now, lets see the other two pages, first the order page which show an order form, but the form does not have any html5 input types and attributes. The following shows the WebMatrix template version:

<form action="" method="post">
    <fieldset class="no-legend">
        <legend>Place Your Order</legend>
        <img class="product-image order-image" src="../Images/Products/Thumbnails/cupcakes.jpg" alt="Image of Cupcakes"/>
        <ol>
            <li class="email">
                <label for="orderEmail">Your Email Address</label>
                <input type="text" id="orderEmail" name="orderEmail"/>
            </li>
            <li class="shiping">
                <label for="orderShipping">Shipping Address</label>
                <textarea rows="4" cols="20" id="orderShipping" name="orderShipping"></textarea>
            </li>
            <li class="quantity">
                <label for="orderQty">Quantity</label>
                <input type="text" id="orderQty" name="orderQty" value="1"/>
                x
                <span id="orderPrice">5.99</span>
                =
                <span id="orderTotal">5.99</span>
            </li>
        </ol>
        <p class="actions">
            <input type="hidden" name="ProductId" value="3" />
            <input type="submit" value="Place Order"/>
        </p>
    </fieldset>
</form>

and here is the revised html5 version:

<form id="order" action="" method="post">
    <fieldset>
        <legend>Place Your Order</legend>
        <img class="thumbnail" src="http://weblogs.asp.net/images/products/thumbnails/cupcakes.jpg" alt="Cupcakes"/>
        <ul>
            <li>
                <label for="email">Email Address:</label>
                <input type="email" name="email" id="email" placeholder="Type your email" required="required" autofocus="autofocus"/>
            </li>
            <li>
                <label for="address">Shipping Address:</label>
                <textarea rows="4" cols="20" name="address" id="address" placeholder="Type your address" required="required"></textarea>
            </li>
            <li>
                <label for="quantity">Quantity:</label>
                <input type="number" name="quantity" id="quantity" min="1" step="1" max="100" value="1" required="required"/>
                x <span id="price">9.00</span> = <output id="total" for="quantity,price">$9.00</output>
            </li>
            <li><button type="submit" class="order-button">Place Order</button></li>
        </ul>
    </fieldset>
</form>

As you can see that our revised version has some new input types like <input="email"/> and <input type="number"/> and attributes like placeholder, required, autofocus etc etc, but currently only few browsers has the support to take advantages of it, for example if I run it in Opera latest and click the place order with some invalid data it will show the following screen:

Opera html5 form

As you can see Opera does have the built-in support to validate the form, also it is showing the quantity input in an range input rather than plain textbox. I think the iPhone Safari also takes the advantages of html5 form. Though not all the browser has the support of these new values but the good news is it will automatically fallback to the plain old input type="text" even if these new values are used. Maybe in future I will show you how can use some intelligent scripting to trigger these client side features depending upon the browser it is running. One last thing I want to highlight in this form is that to show the total amount we are also using the new output element and to associate with the other elements that are involves in the calculation we are using the for attribute with comma separated values.

At the top of Order and Successful page some kind of wizard like interface is shown which indicates the current steps, I think the best way to code it if we use the new progress element. I this case, we will just wrap the ol with the progress. For example:

Order:

<progress value="2.0" max="3.0">
    <ol>
        <li><span>1</span>Choose Item</li>
        <li class="current"><span>2</span>Details &amp; Submit</li>
        <li><span>3</span>Receipt</li>
    </ol>
</progress>

Successful:

<progress value="3.0" max="3.0">
    <ol>
        <li><span>1</span>Choose Item</li>
        <li><span>2</span>Details &amp; Submit</li>
        <li class="current"><span>3</span>Receipt</li>
    </ol>
</progress>

The last page in the site is the About Us page which basically contains the history of the company and in the side bar a Twitter widgets that shows the result with bakery hash tag. The company history is the prime candidate for the html5 new article element and side bar as aside as it is showing related content with bakery, so the markup becomes:

<article id="about">
    <header>
        <h1>A little bit about Fourth Coffee</h1>
    </header>
    <p>
        Fourth Coffee was founded in 2010 and delivers coffee and fresh baked goods right to your door. 
        In another life, Bill Baker was a developer by day and pastry chef by night.  
        But soon Bill's innate skills with all things involving butter, flour and sugar put him 
        even more in demand than his programming talents and what started out as a way to satisfy 
        his own sweet tooth became all-consuming.  Fourth Coffee is not only a candy-coated wonderland 
        of coffee, pastries, cookies and cakes, it also honors his tech background by employing a state 
        of the art online ordering system that makes it easy for anybody with internet access to 
        order his all natural, locally-sourced confections and have them delivered to their 
        door within 24 hours.
    </p>
</article>
<aside>
<!-- Twitter Widgets goes here -->
</aside>

That’s it for today. As you can see that writing meaningful and clean markup is not only a fun but also an art. In my next post, I will discuss about the Microsoft Webpage that are bundled in the WebMatrix and alternate frameworks that are available in the other platforms, so stay tuned.

Shout it
More Posts