Lazy Loading jQuery Tabs with ASP.NET

This article looks at efficient use of jQuery tabs when displaying data. Specifically, it covers how to lazy-load data, so that it is only accessed and displayed if the tab is clicked.

Lazy Loading is a well-known design pattern that is intended to prevent redundant processing within your application. In the case of tabbed data, there seems little point retrieving and binding data that appears in a tabbed area that no one looks at. So, this examples covers how to defer data access and display until the user wants it - which is defined by them clicking the relevant tab.

I'm going to use the same Web Service as was introduced in one of my previous jQuery articles, but I have added a new method to it which simply returns a collection of Cars that meet the Make criteria passed into the method argument:

 

[WebMethod]

public List<Car> GetCarsByMake(string make)

{

    var query = from c in Cars

                where c.Make == make

                select c;

    return query.ToList();

}

 

First we make sure that the correct Javascript files are referenced:

 

<script type="text/javascript" src="script/jquery-1.3.2.min.js"></script> 

<script type="text/javascript" src="script/ui.core.min.js"></script> 

<script type="text/javascript" src="script/ui.tabs.min.js"></script> 

 

Then the next thing to do is to add some HMTL to the page to act as tabs:

 

<form id="form1" runat="server"> 

<div> 

  <div id="content">

    <ul>

      <li><a href="#tab0"><span>Ford</span></a></li>

      <li><a href="#tab1"><span>Toyota</span></a></li>

      <li><a href="#tab2"><span>Honda</span></a></li>

      <li><a href="#tab3"><span>Audi</span></a></li>

    </ul>

    <div id="tab0"></div>

    <div id="tab1"></div>

    <div id="tab2"></div>

    <div id="tab3"></div>

  </div>

</div> 

</form> 

 

An essential part of the deal is CSS. I borrowed some for this example from Klaus Hartl, the originator of jQuery tabs who has used CSS Sprites, which he explains here. If you want to short cut like me, the CSS is available on the demo page which will be linked to later.

The HTML requires a container for the tabs, a bit like the ASP.NET AJAX TabPanel. In this example, it's the div with the id of content. The tabs themselves are represented by anchor tags within list items. The content for each tab is placed in a div. Once the CSS has been applied, creating a tabbed interface requires (as is usual with jQuery) just a line or two of Javascript:

 

$(function() {

  var $tabs = $("#content").tabs();

});

 

Running the page as it is gives this result:

However, at the moment, nothing happens when the tabs are clicked. Some more Javascript is needed, and this will be focused on one of the options that can be used when specifying the element to act as a tab container - the select event. When a tab is clicked, we need to retrieve data relating to the Make of car that the tab represents. The data retrieval will be performed by AJAX. The following code shows how the initial Javascript is embellished to register an event handler on the click of a tab, and the method that gets called to obtain the data:

 

var make;

$(function() {

  var $tabs = $("#content").tabs({

    select: function(e, ui) {

      var thistab = ui.index;

      $("#tab" + thistab).html(getCars(thistab));

    }

  });

});

 

function getCars(thistab) {

  switch (thistab) {

    case 0:

      make = "Ford";

      break;

    case 1:

      make = "Toyota";

      break;

    case 2:

      make = "Honda";

      break;

    case 3:

      make = "Audi";

      break;

  }

  $.ajax({

    type: "POST",

    url: "Services/CarService.asmx/GetCarsByMake",

    data: "{make: '" + make + "'}",

    contentType: "application/json; charset=utf-8",

    dataType: "json",

    success: function(response) {

      var cars = (typeof response.d) == 'string' ? eval('(' + response.d + ')') : response.d;

      for (var i = 0; i < cars.length; i++) {

        $('#tab' + thistab).append('<p><strong>' + cars[i].Make + ' ' +

                              cars[i].Model + '</strong><br /> Year: ' +

                              cars[i].Year + '<br />Doors: ' +

                              cars[i].Doors + '<br />Colour: ' +

                              cars[i].Colour + '<br />Mileage: ' +

                              cars[i].Mileage + '<br />Price: £' +

                              cars[i].Price + '</p>');

      }

    }

  });

}

 

Once the tabs are created, a callback is applied to the select (or tab click) event, which takes two arguments - the event object and a ui object. The ui object exposes a property called index, which holds the zero-based index of the tab that was clicked. This is used t0 identify the tab. It's also passed into the function that gets the data so that the right Make can be passed to the web service - once it has been run through a switch statement. Otherwise the getCars() method follows an almost identical pattern to previous calls to web services that I have covered.

Running the page and clicking on the tabs shows that this works just fine, except that every time that a tab is clicked, the data is fetched anew.

What's need is a way to keep track of which tabs have been clicked to prevent unnecessary requests being made when they are clicked again. The modifications that achieve this are shown in bold:

 

var make;

var clicked = new Array();

$(function() {

  var $tabs = $("#content").tabs({

    select: function(e, ui) {

      var thistab = ui.index;

      $("#tab" + thistab).html(getCars(thistab));

    }

  });

});

 

function getCars(thistab) {

  for (var x in clicked) {
    if (clicked[x] == thistab)
      return;
  }

  switch (thistab) {

    case 0:

      make = "Ford";

      break;

    case 1:

      make = "Toyota";

      break;

    case 2:

      make = "Honda";

      break;

    case 3:

      make = "Audi";

      break;

  }

  $.ajax({

    type: "POST",

    url: "Services/CarService.asmx/GetCarsByMake",

    data: "{make: '" + make + "'}",

    contentType: "application/json; charset=utf-8",

    dataType: "json",

    success: function(response) {

      var cars = (typeof response.d) == 'string' ? eval('(' + response.d + ')') : response.d;

      for (var i = 0; i < cars.length; i++) {

        $('#tab' + thistab).append('<p><strong>' + cars[i].Make + ' ' +

                              cars[i].Model + '</strong><br /> Year: ' +

                              cars[i].Year + '<br />Doors: ' +

                              cars[i].Doors + '<br />Colour: ' +

                              cars[i].Colour + '<br />Mileage: ' +

                              cars[i].Mileage + '<br />Price: £' +

                              cars[i].Price + '</p>');

      }

      clicked.push(thistab);

    }

  });

}

 

An array is created to store the index of the tab that has been clicked. At the beginning of the getCars() method, a check is made to see if the tab that has been clicked is already in the array. If it is, the request is aborted by the use of the return statement. If not, the data is obtained, and finally the tab index is added to the array, thus ensuring that when it is clicked again, data is not retrieved.

 

Date Posted: Monday, March 2, 2009 7:30 AM
Last Updated: Tuesday, July 14, 2009 9:59 PM
Posted by: Mikesdotnetting
Total Views to date: 118303

28 Comments

Tuesday, March 10, 2009 9:52 AM - Sin

Mike, why it is not getting an Aborted error when running in IE with this jquery-1.3.2.min.js?

Wednesday, March 11, 2009 7:31 AM - Mike

@Sin

Why should it? Sorry, but I've got no idea what you are asking.

Friday, April 3, 2009 4:51 AM - Jack

Quite cool, it will improve the performance showing in YSlow

Friday, April 3, 2009 6:00 PM - Colm

Well done Mike, excellent article. I've been meaning to look into something like this - namely to lazy load on collapsible widgets, i.e. load the widgets as collapsed initially and only load data when the widget is expanded. Looks like it should be easy to extend what you've done to achieve this. Thanks!

Tuesday, April 21, 2009 7:44 PM - Vijay

Very good article. I like the last part of the article to prevent multiple requests when clicked on the same tab again and again. Thanks Mike.

Tuesday, April 21, 2009 7:48 PM - Boris

Mike it's very nice! (Borat Accent)
Is there a place I can D/L the code?

Thanks,
Boris

Tuesday, April 21, 2009 8:36 PM - Mike

@Boris

All the code you need is in the article. I included every line. Ctrl + C, then Ctrl + V.

:o)

Wednesday, April 22, 2009 1:33 AM - Ashish

Mike, wouldn't it be slightly easier and efficient, if you would just check if the Tab contents are null or empty. If you have a waiting gif in your tab, you could simply check if the gif exists. This would get away from creating an array and looping through the array.

Wednesday, April 22, 2009 6:54 AM - Schooltje

Nice code, but I have another issue for tabs.... can you also directly navigate to tabx.

For example if you paste #tab3 into the querystring automatically tab3 is shown?

Thanks

Wednesday, April 22, 2009 8:20 AM - Ashokan

Your code not working for me
Send me your source files of demo project

Wednesday, April 22, 2009 9:01 AM - Mike

@Ashish

Easier and more efficient? Don't know about that, but otherwise your suggestions are good ones.

Wednesday, April 22, 2009 9:06 AM - Mike

@Schooltje

Here's an easy sample for what you need: http://getrentwiz.com/blog/jquery-tab-select-from-url/

Wednesday, April 22, 2009 9:07 AM - Mike

@Ashokan

My code works for me. See it working in the Demo page? The code in the article is identical and complete.

Wednesday, April 22, 2009 12:51 PM - Hans Kruse

What about using $('#'+tabid).load(...) and having the service returning partial html.

You then also eliminate both the case statement and the eval statement.

If you really must use json then the $('#'+tabid).getJSON(function(){/*parse json and stick in right tab*/})


IMHO using the $.ajax(...) call is a bit overkill and complicates your article.
[a bit off-topic]
We use $(...).load in production software to make a combobox options selectable depending on the selected item of another combobox. That works fine. As a "Service" we have an "aspx" page that depending on the path after the .aspx (in rest style) gives an html snippet or json. it defaults to JSON e.g:

MySvc.aspx/html/[guid]
MySvc.aspx/json/[guid]
MySvc.aspx/[guid] (defaults to json)

Using WCF from jquery would probably be the utimate solution but that is still a skill on my wishlist to master.
[/a bit off-topic]

Wednesday, April 22, 2009 8:55 PM - Mike

@Hans

All good suggestions - thanks. Using an aspx page to generate the html to be returned to an xmlhttprequest is something that I have done many times in the past. With classic ASP, using a .asp page was pretty much the only way to do this.

Friday, August 7, 2009 9:28 AM - msony

Why dont use ajax request build in ui tabs? :-/

http://jqueryui.com/demos/tabs/#ajax

Friday, August 7, 2009 5:48 PM - Mike

@msony

Huh? Did you read my article?

Saturday, October 17, 2009 7:45 PM - linxon

Great articel! I have very little JQuery knowledge but I was able to understand the code easily thanks to your clear and concise explanation thanks.

Monday, October 26, 2009 2:08 PM - greg


Great article. Question, this may be my great duh for the week, but what tool are you using to monitor the html requests. Its the screen shot w/ the bug in top left.

Monday, October 26, 2009 8:35 PM - Mike

@greg

That's a good question. I show screen shots of that tool in action so many times that I forget to mention it each time. It's the Firebug add-on that works with the FireFox browser.

https://addons.mozilla.org/en-US/firefox/addon/1843

Thursday, November 5, 2009 2:44 PM - Chris

I must be blind, but where is the link to your demo page?

Friday, November 20, 2009 4:54 AM - prads

Great article mike helped me learn how to call webservices using jquery .... I had 2 questions
Isn't Lazy Loading already existing in tabs UI ?
To prevent reloads can't we use the Cache Option ?

Saturday, November 21, 2009 9:37 AM - Mike

@prads

Yes, there is a cache option will does away with the need for the array. Thanks for pointing that out.

Tuesday, December 29, 2009 10:37 PM - Dan

Mike,

Great writeup.

Quick question : To make this work with ASP.NET MVC

Can I place an html.actionlink () into your code without
breaking something ?

<ul>

<li><a href="#tab0"><span>Ford</span></a></li>

* I would stick the actionlink in the above line which takes me to the FORD detail.aspx view

</ul>

Did you use the JQUERY UI for this example ?


Thanks in advance.

Wednesday, December 30, 2009 5:55 AM - Mike

@Dan

Yes, and yes.

Sunday, January 3, 2010 3:19 PM - Abhishek

I don't think this is a good example to be used in commercial apps. Binding data from client side and exposing the webservices in javascript is not something I would do. I am looking for an example where I can load a user control dynamically. ATM, I am loading the entire set of tabs and toggling through css on click which is not elegant either. I need a better solution which is viable commercially. However, a good article !

Sunday, January 3, 2010 4:11 PM - Mike

@Abhishek

It may not be useful to you in your commercial apps, but that doesn't mean it can't be used by others in their commercial apps. Why wouldn't you want to expose web services? And if you have a good reason not to, why not use handlers or page methods?

Thursday, June 24, 2010 7:51 AM - Krokonoster

This one got me looking into the right direction, thanks Mike.
However, it turns out even simpler than you wrote here, using jQuery UI Tabs.
My list are just a bunch of ActionLinks pointing to ActionMethods that return Partial Views.
And the only javascript I then really need is : $("#tabContainer").tabs();
(See http://jquery.krokonoster.com/ for a example, and download the source code there)
Add your comment

If you have any comments to make about this article, please use this form to do so. Make sure that your comment relates specifically to the article above. More general comments can be posted through the form on the Contact page.

Please note, all comments are moderated, and some may not be published. The kind of things that will ensure your comment is deleted without ever seeing the light of day are as follows:

  • Not relevant to the article
  • Gratuitous links to your own site or product
  • Anything abusive or libellous
  • Spam
  • Anything in a language I don't understand including gibberish.

I do not pass email addresses on to spammers, so a valid one will assist me in responding to you personally if required.