Creating an Ajax Content Switcher with jQuery and ASP.NET MVC

The last few days I have been digging into the Javascript history api and in the process built a jQuery plugin that I figured others might find useful. It is actually very simple. I will be blogging about the Javascript history api next week and this will be used in those, so you can consider this a teaser as well if you are interested in the topic.

The Goal

What is the goal? It is to change a regular anchor tag from a full page get request to an Ajax request that will fetch the content and replace the core of the page with the retrieved Html. And we'll do it by only adding an attribute to the anchor tag.

The Content

When we get to the Javascript history api, we will update the url as the content changes. Because the url will be unique for each bit of content, we need to create three separate pages. That way you get the best of both worlds, faster response by not doing a full page download but each of the pages can be navigated to separately. Here are our pages:


@{
    ViewBag.Title = "The First Page";
}

<h3>Home Page</h3>

<p>The original home page content. <a href="/home/content2" class="ajaxme">Now look at content #2</a></p>

@{
    ViewBag.Title = "Content 2";
}

<h2>Content 2</h2<

<p>This is the second chunk of content. <a href="/home/content3" class="ajaxme">Now look at content #3</a></p>

@{
    ViewBag.Title = "Content 3";
}

<h2>Content 3</h2>

<p>This is the third chunk of content.  <a href="/home/content2" class="ajaxme">Go gack to the first.</a></p>

Here is a chunk of the markup from the layout page. We'll just switch out the content in the div surrounding the RenderBody call.


<div id="body">
    @RenderSection("featured", false)
    <section class="content-wrapper main-content clear-fix">
        @RenderBody()
    </section>
</div>

The Javascript

Let's create a jQuery plugin to do all this work for us so we can easily apply our Javascript logic in any context we want. Here it is:


(function ($) {

    $.fn.ajaxme = function (replacementSelector) {
        $(this).click(function () {
            var url = $(this).attr('href');

            $.get(url, function (data) {
                $(replacementSelector).html(data);
            });

            return false;
        });
    }

})(jQuery);

The surrounding bits setup this as a jQuery plugin. On line 3 we create the ajaxme function, which we will invoke on the links we want to ajaxify. When this is applied to a set of elements, the $(this) reference will refer to the dom element being extended by the plugin. In our case we are applying this to anchor tags, so the click event on line 4 will fire when someone clicks on the link. On line 5 we get the url from the tag. On line 7 we make the ajax call to get the content. The callback, lines 7-9 will take the content and replace the inner Html of the dom element referenced by the passed in selector with the content retrieved by the Ajax call. Finally, line 11, we return false to cancel the default behavior of the link, which would be to send us off to the page at the specified url.

I reference that javascript file in the head of my layout page and put this chunk into each page to actually wire up things:


<script type="text/javascript">
    $(document).ready(function () {
        $('.ajaxme').ajaxme('section.content-wrapper');
    });
</script>

By putting this on each page, I can target a different set of links per page to ajaxify the content. In our simple example here nothing changes per page, but in a more complex page this may not be the case. Here are the final page skins and we are now done with our Javascript.


@{
    ViewBag.Title = "Home Page";
}

<h3>Home Page</h3>

<p>The original home page content. <a href="/home/content2" class="ajaxme">Now look at content #2</a></p>

<script type="text/javascript">
    $(document).ready(function () {
        $('.ajaxme').ajaxme('section.content-wrapper');
    });
</script>

@{
    ViewBag.Title = "Content 2";
}

<h2>Content 2</h2<

<p>This is the second chunk of content. <a href="/home/content3" class="ajaxme">Now look at content #3</a></p>

<script type="text/javascript">
    $(document).ready(function () {
        $('.ajaxme').ajaxme('section.content-wrapper');
    });
</script>

@{
    ViewBag.Title = "Content 3";
}

<h2>Content 3</h2>

<p>This is the third chunk of content. <a href="/home/index" class="ajaxme">Go gack to the first.</a></p>

<script type="text/javascript">
    $(document).ready(function () {
        $('.ajaxme').ajaxme('section.content-wrapper');
    });
</script>

Now With Some ASP.NET MVC

To set up the pages we are using, your controller might look something like this:


public class HomeController : Controller
{
    public ActionResult Index()
    {
        return View();
    }

    public ActionResult Content2()
    {
        return View();
    }

    public ActionResult Content3()
    {
        return View();
    }
}

This is almost what we want. If you were to run all this code as is, assuming you were on the index page, the fully rendered content of page 2 would get inserted into the index page, html, head, body tags and all. Well that just won't do, will it? The change is simple. Essentially, if an ajax call is being made to the page we'll return a PartialViewResult, which will just return the chunk of Html for that particular page skin. If a normal request, you return a normal ViewResult. But so we won't be repeating ourselves, we will create a helper method:


public class HomeController : Controller
{
    public ActionResult Index()
    {
        return AjaxableView("Index");
    }

    public ActionResult Content2()
    {
        return AjaxableView("Content2");
    }

    public ActionResult Content3()
    {
        return AjaxableView("Content3");
    }

    public ActionResult AjaxableView(string name)
    {
        if (Request.IsAjaxRequest())
            return PartialView(name);
        else
            return View(name);
    }
}

And we are done for the day. There are some improvements we could make, like by pushing this functionality to use data- attributes. This would be especially useful as you could get rid of the repeated scripts on each page. And since we are changing the full content of the page, we really should be changing the url...but that is for another day. You could use this without changing the url if you are just switching out smaller chunks of content. But in that case the pages may not need to be viewed individually, which would probably lead you to more changes above. But I'll leave that to you. I hope you found this useful.