This post is over 6 months old. Some details, especially technical, may have changed.

Practical jQuery Mobile with ASP.NET MVC

That's a bit of a mouthful.  I wanted to write a post about creating a basic jQuery Mobile app but as I started putting the code together MVC became more and more involved so I combined the 2.

The end solution?  Lets create a phone directory with 2 main views,

  1. A filterable list of all people with quick info (telephone extension and name) that is grouped and sorted alphabetically, and,
  2. A disclosure view of a selected person showing more details including a photo

[[posterous-content:FddxCHuIgArIuezHEbcn]]

MVC (The Server Side)

jQuery Mobile works by progressive enhancement and uses Ajax to load and parse external links so it has more control over page transitions and Ajax history.  This means that we create a plain old website that will work without jQuery Mobile, Ajax or any JavaScript.  So I started with the ASP.NET MVC 2 Web Application Visual Studio Template and ripped out everything bar the Home Controller and the 2 views.  I also stripped the Site.Master down to the bare bones.

Next I created my model with data access methods,

public class Entry
{
    public string Id { get; set; }
    public string Title { get; set; }
    public string FirstName { get; set; }
    public string Surname { get; set; }
    public string Email { get; set; }
    public string InternalNo { get; set; }
    public string Room { get; set; }
    public string ExternalNo { get; set; }
    public string PhotoLocation { get; set; }

    public static IEnumerable<Entry> GetAll()
    {
        // get all entries
    }

    public static Entry GetById(string id)
    {
        // get a specific entry
    }
}

You can implement your own data access there or just hard code some values.  Next I updated the HomeController to return the right models  to the views

public ActionResult Index()
{
    return View(from e in Entry.GetAll()
                orderby e.Surname
                group e by e.Surname.Substring(0,1) into g
                select g);
}

public ActionResult About(string id)
{
    return View(Entry.GetById(id));
}

I updated the views to display the information in a straightforward way. 

Index.aspx

<%@ Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<IEnumerable<IGrouping<string,Kas.JQueryMobile.WhosWho.Models.Entry>>>" %>
<%@ Import Namespace="Kainos.JQueryMobile.WhosWho.Models" %>
<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">Whos Who</asp:Content>

<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
    <div>
        <div>
            <h1>Whos Who</h1>
        </div>
        <div>
            <ul>
                <% foreach (IGrouping<string,Entry> group in Model){%>
                    <li><%=group.Key%></li>
                    <% foreach (Entry item in group){%>
                        <li>
                            <a href="<%=Url.Action("About", "Home", new { id = item.Id }) %>">
                                <%=item.Title %>
                            </a>
                            <p>
                                <strong><%=item.InternalNo %></strong>
                            </p>
                        </li>
                    <%} %>
                <%} %>
            </ul>
        </div>
    </div>
</asp:Content>

About.aspx

<%@ Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<Kas.JQueryMobile.WhosWho.Models.Entry>" %>

<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
    <%=Model.Title%>
</asp:Content>

<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
    <div>
        <div>
            <h1><%=Model.Title%></h1>
        </div>
        <div style="background: url(<%=Url.Content("~/Images/" + Model.PhotoLocation) %>) no-repeat top right">
            <p><%=Model.Title%></p>
            <p><a href="mailto:<%=Model.Email%>"><%=Model.Email%></a></p>
            <p><%=Model.InternalNo%></p>
            <p><%=Model.ExternalNo%></p>
            <p><%=Model.Room%></p>
        </div>
    </div>
</asp:Content>

This leaves us with a normal, not so pretty but fully functioning site. 

[[posterous-content:DblJukiumuFlvvqlHokq]]

Now lets inject some mobile niceness into it,

jQuery Mobile

First things first lets put reference to jQuery and jQuery Mobile  (script and css) into our site master and update our DOCTYPE to the HTML5 DOCTYPE leaving us with,

<%@ Master Language="C#" Inherits="System.Web.Mvc.ViewMasterPage" %>
<!DOCTYPE html>
<html>
    <head runat="server">
        <title><asp:ContentPlaceHolder ID="TitleContent" runat="server" /></title>
        <link href="<%=Url.Content("~/ClientBin/jquery.mobile-1.0a1.min.css") %>" rel="stylesheet" type="text/css" />
        <script src="<%=Url.Content("~/ClientBin/jquery-1.4.3.min.js")%>" type="text/javascript"></script>
        <script src="<%=Url.Content("~/ClientBin/jquery.mobile-1.0a1.min.js")%>" type="text/javascript"></script>
    </head>
    <body>
        <asp:ContentPlaceHolder ID="MainContent" runat="server" />
    </body>
</html>

Now we need to tell jQuery Mobile how to layout the pages and mobilise.  This isn't necessarily done in script as you might think.  jQuery Mobile makes use of HTML5's data- attributes to identify how the page should be laid out and mark areas to specific roles.

Index.aspx

<%@ Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<IEnumerable<IGrouping<string,Kas.JQueryMobile.WhosWho.Models.Entry>>>" %>
<%@ Import Namespace="Kainos.JQueryMobile.WhosWho.Models" %>
<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
    Whos Who
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
    <div data-role="page">
        <div data-role="header">
            <h1>Whos Who</h1>
        </div>
        <div data-role="content">
            <ul data-role="listview" data-filter="true">
                <% foreach (IGrouping<string,Entry> group in Model){%>
                    <li data-role="list-divider"><%=group.Key%></li>
                    <% foreach (Entry item in group){%>
                        <li>
                            <a href="<%=Url.Action("About", "Home", new { id = item.Id }) %>">
                                <%=item.Title %>
                            </a>
                            <p class="ui-li-aside">
                                <strong><%=item.InternalNo %></strong>
                            </p>
                        </li>
                    <%} %>
                <%} %>
            </ul>
        </div>
    </div>
</asp:Content>

About.aspx

<%@ Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<Kas.JQueryMobile.WhosWho.Models.Entry>" %>

<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
    <%=Model.Title%>
</asp:Content>

<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
    <div data-role="page">
        <div data-role="header">
            <h1><%=Model.Title%></h1>
        </div>
        <div data-role="content" style="background: url(<%=Url.Content("~/Images/" + Model.PhotoLocation) %>) no-repeat top right">
            <p><%=Model.Title%></p>
            <p><a href="mailto:<%=Model.Email%>"><%=Model.Email%></a></p>
            <p><%=Model.InternalNo%></p>
            <p><%=Model.ExternalNo%></p>
            <p><%=Model.Room%></p>

        </div>
    </div>
</asp:Content>

So what have we added here? 

  • data-roles - telling jQuery Mobile what each div actually represents e.g.
    • page - a single view (a single html page can have multiple views)
    • header - the header of a page
    • content - the pages content
    • footer - the footer of a page
    • list-view - a special role to specifying  that the content is a list
    • list-divider - a divide for a list that doesn't do anything but look different
  • data-filter - telling jQuery Mobile that it should provide filtering on this view
  • ui-li-aside class identifies that this is some aside information for this list item

And that is it.  We have taken a static site and without writing any code (bar markup) created a mobile app.  Probably might be a good idea to include an application cache manifest file as well so there is some semblance of offline capability provided.  But that's for another day.

[[posterous-content:pJHwwsHHmrfAkJIJFkrp]]

Published in JavaScript .NET on October 21, 2010