Toolroom Tech Blog

Devlopers Digest

AJAX templating with JQuery and MVC

Example how to add AJAX templating automation with ASP MVC and JQuery.

AJAX Templating with ASP.net MVC and JQuery

In principle, when using dynamically created web pages, i.e. by Asp or PHP, the web server creates the HTML, sends it to the client, and the client renders the whole stuff.

Now you might want to load additional content without refreshing the whole page. You can do this with simple callbacks to the server, where the server again produces some HTML and sends it back to the server. This means, that lots of HTML is sent. Such asynchronous calls should need the shortest roundtrip time as possible, since users wait for content. And, believe me, users cannot wait.

Templating is a great way to gain performance in asynchronous server interaction with AJAX. Therefore the server produces a invisible template on the initial response. Then, instead of sending HTML, the server just provides us with plain data.

In the following example I load a table with data from a log file. When the user clicks a refresh button and/or a timer triggers a refresh function, we get new data.

Step One: Create base HTML

Based on the LogItem collection we create a simple table in the index file that may contain initial static data.

@model JQueryTemplating.Models.LogItemCollection
<table id="logItemData" style="width:100%;">
    <thead>
        <tr>
            <th>
                @Html.LabelFor(l => fakeLogItem.TimeStamp)
            </th>
            <th>
                @Html.LabelFor(l => fakeLogItem.Title)
            </th>
            <th>
                @Html.LabelFor(l => fakeLogItem.Type)
            </th>
            <th>
                @Html.LabelFor(l => fakeLogItem.Description)
            </th>
        </tr>
    </thead>
    <tbody>
        @Html.DisplayFor(l => l)
    </tbody>
</table>

For initial rows we need a DisplayTemplate, that goes into the DisplayTemplates folder.

@model JQueryTemplating.Models.LogItem
<tr class="line@(Model.Type)">
    <td>
        @Html.DisplayFor(i => i.TimeStampStr)
    </td>
    <td>
        @Html.DisplayFor(i => i.Title)
    </td>
    <td>
        @Html.DisplayFor(i => i.Type)
    </td>
    <td>
        @Html.DisplayFor(i => i.Description)
    </td>
</tr> 

Step Two: Add Attribute and Extension 

Now I do some Voodoo to allow model driven templating and Razor support. Therefore I first add an annotation to markup a models property for templating inclusion.

[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public class TemplateAttribute : Attribute
{
	public string Value { get; set; }

	private string _displayFormat = "{{{0}}}";
	public string DisplayFormat
	{
		get { return _displayFormat; }
		set { _displayFormat = value; }
	}
}

Then I add an extension method to the MvcHtmlString in order to access the template values i.e. with Razor.

public static MvcHtmlString TemplateValueFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression)
{
	var metaData = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);

	var prop = metaData.ContainerType.GetProperty(metaData.PropertyName);
	if (prop == null)
		return null;

	var templateAttribute = prop.GetCustomAttributes(typeof(TemplateAttribute), false).FirstOrDefault() as TemplateAttribute;
	if (templateAttribute == null)
		return null;

	var result = string.Format(templateAttribute.DisplayFormat, templateAttribute.Value jQuery1520833310639602945_1349033015193 metaData.PropertyName);
	return new MvcHtmlString(result);
} 

Step Three: Add HTML Template for AJAX

Now, let's add Ajax stuff. Therefore, in we create a template row within the document that contains the table. To have valid HTML, i put it into a separate table. In principle the row is equal to the DisplayTemplate.

<table id="logItemTemplate" style="display:none;">
    <tbody>
        <tr class="line@(Html.TemplateValueFor(i => i.Template.Type))">
            <td>
                @Html.TemplateValueFor(i => i.Template.TimeStampStr)
            </td>
            <td>
                @Html.TemplateValueFor(i => i.Template.Title)
            </td>
            <td>
                @Html.TemplateValueFor(i => i.Template.Type)
            </td>
            <td>
                @Html.TemplateValueFor(i => i.Template.Description)
            </td>
        </tr>
    </tbody>
</table>

 

Step Four: Add Action that provides data

Now we need an MVC Action to call for new data. Optionally we can provide it with a timestamp that defines the time of the latest item we have already received, so that we only get new log entries.

public class DataController : Controller
{
	//
	// GET: /Data/
	[HttpPost]
	public ActionResult Get(DateTime? timestamp)
	{
		var repository = new LogItemRepository();
		var items = repository.GetData(timestamp);

		return Json(items);
	}
}

Step Four: Add AJAX functionality

Finally, let's add the jquery part for asynchronous updates and a button to enable/disable those updates.

$(function () {
        //Attach to the click event of the start/stop button
        $('#start').click(function () { start(); });

        //Used for the delegate function for periodic calls
        var refreshDelegate = false;

        //AJAX function that fetches new data from the server
        var refresh = function () {
            $.ajax({
                url: '/Data/Get',
                type: 'POST',
                dataType: 'json',
                success: function (data) { handleData(data); },
                error: function () {
                    clearInterval(refreshDelegate);
                    if (confirm('Connection refused. Please click ok to try it again.')) { start(); };
                },
                cache: false
            });
        };

        //handles the JSON response
        function handleData(data) {
            //Iterate throught all items
            $.each(data, function () {
                //Clone the Template
                var html = $('#logItemTemplate>tbody').clone().html();
                //Iterate through the key/value pairs
                $.each(this, function (k, v) {
                    if (v != null && v.indexOf('/Date(') == 0) {
                        v = convertToDate(v);
                    }
                    //Replace the key by its corresponding value
                    var regex = new RegExp('{' + k + '}', "g");
                    html = html.replace(regex, v);
                });

                //Finally prepend the processed row to the table
                $('#logItemData>tbody').prepend(html);
            });
        }

        //Helper function to convert the serialized .net datetime format
        function convertToDate(str) {
            var re = /-?\d+/;
            var m = re.exec(str);
            return new Date(parseInt(m[0]));
        }

        //Sets or stops the periodic timer
        function start() {
            if (refreshDelegate == false) {
                refreshDelegate = setInterval(refresh, 1000);
            } else {
                clearInterval(refreshDelegate);
                refreshDelegate = false;
            }
        }
    });

Download sample solution 

JQueryTemplating.zip (2.35 mb)