Tuesday, February 21, 2012

MVC, jQuery UI, and the DataTable plugin

I hate starting with disclaimers, but let’s just get this out of the way. There aren’t any grid plugins, or free ones anyway, that I’m really enamored of. Maybe, it’s just not possible, but all of the ones I’ve tried have WAY too many options, are hard to configure, and even hard to style properly. That seems especially true if you want to use jQuery UI themes for styling. I’ve tried jqGrid, Flexigrid, and several others, but the one that I’ve had the most overall luck with is DataTables. Probably the biggest reason is that I think it best works with jQuery themes. That probably reflects my bent as a developer instead of a designer. It’s simply easier to bend the code to my will than the CSS. Because DataTables requires the least lifting with respect to adding in jQuery UI themes, I’ve settled on it.
Before I go any further, I want to say that I hate (or, maybe HATE) the use of Hungarian notation in the DataTables configuration. I understand it, I just dislike it with a vengeance. It causes enough readability problems that it almost disqualifies it in my mind. Worse, I feel compelled to adapt to it in my models rather than take on the somewhat daunting task of modifying it to my preferences since I don’t feel like maintaining those changes across versions and I don’t want to introduce more confusion by changing the names of the bound properties.


What will you need?

Before we get started, I’m going to assume that you know how to create or have an existing ASP.NET MVC project. This tutorial won’t make much sense if you don’t. If you’re not somewhat familiar with ASP.NET MVC, I suggest you start with "The Book" and its example, the Nerd Dinner. You should also be familiar with jQuery and jQuery UI. We’re actually going to be interested in using jQuery UI themes (http://jqueryui.com/themeroller/), but it really only makes sense to do so if you’re already using jQuery UI for other widgets (buttons, tabs, accordions, dialogs, etc.).  Some familiarity with writing pure JavaScript will eventually come in handy, too. Not everything is a plugin! Lastly, you’ll need to download a copy of the DataTables code, http://www.datatables.net/download/. Extract the ZIP file into a folder. I will be using version 1.9.0 in this tutorial.

Let’s Start

Before we go any further, copy jquery.dataTables.js and jquery.dataTables.min.js from the folder you created when you extracted the ZIP file to the Scripts folder in your MVC project. You can find these in the media\js folder. Copy the jquery.dataTables_themeroller.css from media\css to your Content folder. You will also need to add the following two CSS definitions to your Site.css file (or whatever you use for global styles). The css_right and css_left classes are used by the DataTables plugin to position the sorting icons, but the classes aren’t included in the CSS file.   There are several other CSS and JS files.  These were the only ones that I found I needed and should be all that are required for this tutorial. These instructions don’t address any DataTable plugins that you may need; I’m not using any.
.right, .css_right {
    float: right;
}

.left, .css_left {
    float: left;
}

Now, let’s create your _Layout.cshtml file.  The important parts are including the relevant scripts and CSS.  Generally , you want your CSS in the header and your scripts right before the end of the body tag to make sure that your page loads as fast as it can.  Most page elements that require additional requests can load in parallel, but scripts generally don’t (some browsers support extensions that allow this, but you shouldn’t count on that yet).  Here’s a sample layout for a simple project using DataTables.  Note that the header and footer elements are provided via partial views, which I haven’t included.

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>@ViewBag.Title</title>
    <link href="https://ajax.googleapis.com/ajax/libs/jqueryui/1.8/themes/redmond/jquery-ui.css" rel="stylesheet" type="text/css" />
    <link href="@Url.Content( "~/content/jquery.dataTables_themeroller.css" )" type="text/css" rel="stylesheet" media="screen" />
    <link href="@Url.Content( "~/content/site.css" )" rel="stylesheet" type="text/css" />
</head>
<body>
    <div class="page">
        <header>
            @Html.Partial( "_Header" )
        </header>
        <section id="main">
            @RenderBody()
        </section>
        <footer>
            @Html.Partial( "_Footer" );
        </footer>
    </div>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7/jquery.min.js" type="text/javascript"></script>
    <script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.8/jquery-ui.min.js" type="text/javascript"></script>
    @RenderSection("scripts",false)
</body>
</html>


A Couple of Notes


Note that this example uses jQuery 1.7 and jQuery UI 1.8.  These, as well as the jQuery UI theme (Redmond), are being downloaded from the Google Code CDN.  To choose a different theme, simply substitute the theme name for redmond in the theme’s CSS link.  All of the themes I’ve tried from the ThemeRollery gallery seem to be hosted there, but I can’t guarantee it.  If you are using a custom theme, which you’ve downloaded locally, replace the theme link with a reference to your custom theme, typically located in Content\themes\<theme-name>.  Note also, I’ve got an optional section named, scripts, that is being included AFTER the jQuery and jQuery UI scripts.  This will allow me to add a scripts section to a view so that I can add the DataTables script to just the pages that need it as well as any custom JavaScript that applies to just that view, while at the same time ensuring that those scripts aren’t executed until jQuery is loaded.

A Non-AJAX Solution


You have a choice: AJAX or non-AJAX.  Which you choose will determine how you structure your view and your controller actions.  If you choose a non-AJAX solution, you can live with a single view and a single controller action to render that view.  Your model will contain the data to render a table and you will simply apply the DataTables plugin to the table.  For small tables that’s fine; I’ve used it that way many times and it works pretty easily.  My next article will expand on this and create an AJAX solution.

Here’s a simple view that uses the non-AJAX approach.  We’re displaying a list of web site members.  The things we want to display are  a link to the member’s details and the member’s display name, job title, the institution they work for, a list of their interests, and the date when they registered for our site. I like to start with the view since it tells me what data I’ll need in my model:  View –> Model –> Controller works pretty well for this particular application.

@{
    ViewBag.Title = "Members";
}
@model IEnumerable<MemberModel>
<h2>
    Members</h2>

    <table class="grid">
        <thead>
            <tr>
                <th></th> @* place holder for link to details *@
                <th>Name</th>
                <th>Job Title</th>
                <th>Institution</th>
                <th>Interests</th>
                <th>Registered</th>
            </tr>
        </thead>
        <tbody>
            @foreach (var member in Model)
            {
                <tr>
                    <td>@Html.ActionLink( "Details", "detail", "member", new { id = member.Id }, null ) </td>
                    <td>@member.Identity</td>
                    <td>@member.JobTitle</td>
                    <td>@member.Institution</td>
                    <td>@member.Interests</td>
                    <td>@member.MemberSince</td>
                </tr>
            }
        </tbody>
    </table>

@section scripts
{
    <script type="text/javascript" src="@Url.Content( "~/scripts/jquery.dataTables.min.js" )"></script>
    <script type="text/javascript">
        $('table.grid').dataTable({
            "bJQueryUI": true, // render using jQuery UI theme
            "aaSorting": [[1,"asc"]], // default sort on column 1 (second column)
            "sPagination": "full_numbers"
        });
    </script>
}

Note how we're including the DataTables javascript and our table setup in the scripts section in this view. The framework will place these in the scripts placeholder in our layout when the view is rendered. This way the code need only be present on this page where it is used, not everywhere. We could do something similar with the CSS, but I wanted to always have an exact ordering for the CSS and placed it all in the layout directly.


The key setting in our DataTable options is "bJQueryUI": true. This tells the DataTable plugin to add in jQuery UI classes when it is rendering and use the theme we've included for styling. Since we've included a link in our first column, we also tell the plugin to order the table by the second column, the user's display name.

The Model


I’ll be using a view-specific model for this view. Typically, you’ll want to provide isolation between your entity models and your views. A view model accomplishes this, providing you with a way to deliver just the data needed in the way that the view requires. My model is relatively simple.If you needed other properties on the page, you might have a model class that contains the collection of this model along with the additional properties. In our case, the view is strongly-typed to the collection itself.


public class MemberModel
{
    public int Id { get; set; }
    public string Identity { get; set; }
    public string JobTitle { get; set; }
    public string Institution { get; set; }
    public string Interests { get; set; }
    public string MemberSince { get; set; }
}

The Action


And, now, we need a way to get the data to the view.  For this example, we’ll simply construct a fixed collection of model elements, though normally you would perform a query against your data store to get a collection of entities (and related objects) that would be flattened into our view model.

[HttpGet]
[Authorize]
public ActionResult Members()
{
    var memberList = new List<MemberModel>
    {
        new MemberModel
        {
            Id = 201, Identity = "Joe Developer", Institution = "University of Iowa",
            Interests = "MVC, jQuery, DataTables", JobTitle = "Developer"
        },
        new MemberModel
        {
            Id = 287, Identity = "Jane Manager", Institution = "Kirkwood Community College",
            Interests = "Agile Methods", JobTitle = "Manager"
        },
        new MemberModel
        {
            Id = 562, Identity = "Daisy Designer", Institution = "University of Iowa",
            Interests = "CSS, HTML5", JobTitle = "Designer"
        }
        // ...
    };
    return View( memberList );
}

Conclusion


With the exception of the Hungarian notation, using DataTables with jQuery UI in a non-AJAX MVC setting is pretty simple as you’ve seen. The drawback to this approach becomes evident when you have lots of data that needs to be rendered in your table. In a non-AJAX mode, the entire table is sent to the client and the DataTable plugin handles paging and sorting entirely on the client-side. For large datasets this can be very slow both in the client and it can bog down the server as well while the request is being prepared.  A better approach is to hook the DataTables plugin up to the server using AJAX.  We’ll cover that in my next article.