How To: Lazy-load TabPanel's within the AjaxControlToolkit's TabContainer Control

I am currently working on a data-centric web application.  The page I most recently worked on allows the user to browse data from six different tables (a primary table, and five other tables that have 1-M relationships to the rows in the primary table).  A row from the primary table is displayed in a DetailsView.  Below the DetailsView is a TabContainer control with five tabs; each tab contains a GridView that displays the related rows in the other five tables.  Below is a screen shot of what the TabContainer portion of the page looks like. 

Live Demo | Download Sample Site

When I first developed the page, I bound the DetailsView and each of the GridViews to the corresponding DataSources like I usually do.  Everything worked great in our development environment.  The grids filled nicely and I could move around from tab to tab with no problems.  However, when we moved the page into our production environment loading the page was terribly slow.  In production, we have n-times more data and the queries that filled the various GridViews performed much slower than they were in our development environment.  And because just loading the page causes all 6 of the queries to execute, there were some serious performance issues with this page. 

To solve the problem I moved each of the GridViews in the TabContainer into its own UpdatePanel and delayed loading them until the user clicked on the tab they wanted to view.  I set the default tab to the one I felt that users were most often interested in.  This way only 2 queries were executed the first time the page loaded, and the other 4 queries would execute on-demand so only the users who were interested in the data for the tab would pay the price of waiting for the query.  The following are the steps I took to accomplish this ...

Define the DataSources

You can create your DataSources just like you normally would.  In my example I am using SqlDataSource's to populate the grids.  The example I created uses the Customer, Orders and Employees tables from the Northwind database and the DataSources are defined as follows.

<asp:SqlDataSource ID="sqldsCustomers" runat="server" />       
<asp:SqlDataSource ID="sqldsOrders" runat="server" />       
<asp:SqlDataSource ID="sqldsEmployees" runat="server" />       

Add the TabContainer to the Page

Next, you can add the markup for the TabContainer control to your page.  Also, add the TabPanel as children of the TabContainer.  For each of the TabPanels you can set the HeaderText to the text value you would like displayed for each of the tabs.  Because the three tabs in my sample are for Customers, Orders and Employees data, this is exactly what I named my tabs.

<ajaxToolkit:TabContainer ID="tabContainer" runat="server">
   <ajaxToolkit:TabPanel runat="server" HeaderText="Customers">
        <ContentTemplate>...</ContentTemplate>
    </ajaxToolkit:TabPanel>
    <ajaxToolkit:TabPanel runat="server" HeaderText="Orders">
        <ContentTemplate>...</ContentTemplate>
    </ajaxToolkit:TabPanel>
    <ajaxToolkit:TabPanel runat="server" HeaderText="Employees">
        <ContentTemplate>...</ContentTemplate>
    </ajaxToolkit:TabPanel>                                
</ajaxToolkit:TabContainer> 

Add the GidView to the TabPanel's ContentTemplate

Once you have created the TabPanels, you can go ahead and populate the ContentTemplates with the GridView's the tab will be displaying.  Also, since we already know we will be needing each of these GridViews to be loaded via async-postbacks we can go ahead and create them as children of UpdatePanels. 

<ajaxToolkit:TabContainer ID="tabContainer" runat="server">
   <ajaxToolkit:TabPanel runat="server" HeaderText="Customers">
        <ContentTemplate>
            <asp:UpdatePanel runat="server" UpdateMode="Conditional">
                <ContentTemplate>                    
                    <asp:GridView 
                        runat="server"
                        AllowPaging="true" AllowSorting="true" PageSize="10" DataSourceID="sqldsCustomers" />
                </ContentTemplate>
            </asp:UpdatePanel>                                    
        </ContentTemplate>
    </ajaxToolkit:TabPanel>
    <ajaxToolkit:TabPanel runat="server" HeaderText="Orders">
        <ContentTemplate>
            <asp:UpdatePanel runat="server" UpdateMode="Conditional">
                <ContentTemplate>
                    <asp:GridView  
                        runat="server"
                        AllowPaging="true" AllowSorting="true" PageSize="10" DataSourceID="sqldsOrders" />
                </ContentTemplate>
            </asp:UpdatePanel>
        </ContentTemplate>
    </ajaxToolkit:TabPanel>
    <ajaxToolkit:TabPanel runat="server" HeaderText="Employees">
        <ContentTemplate>
            <asp:UpdatePanel runat="server" UpdateMode="Conditional">
                <ContentTemplate>                    
                    <asp:GridView 
                        runat="server"
                        AllowPaging="true" AllowSorting="true" PageSize="10" DataSourceID="sqldsEmployees" />
                </ContentTemplate>
            </asp:UpdatePanel>                                    
        </ContentTemplate>
    </ajaxToolkit:TabPanel>                                
</ajaxToolkit:TabContainer> 

Add Triggers for each of the UpdatePanels

Next, we will wire the triggers for each UpdatePanel's.  In my example, the Tab containing the Customers is displayed first.  I want this content bound when the page is first loaded, and this UpdatePanel should only be refreshed when a postback is triggered from a child control.  For the other non-default tabs, I also want them to refresh when a postback is triggered from a child control, but I also want to define an explicit trigger that I can fire when the user clicks on the Tab.  To accomplish this I add a couple of hidden HTML input elements to the page and register these as explicit AsyncPostBackTrigger's for the other two UpdatePanels.  These elements will be used to cause the postback via __doPostBack from a javascript function created in the next step.  This is a similar technique that I used in a previous post.

<input id="btnOrdersTrigger" runat="server" type="button" style="display:none" onserverclick="BtnOrdersTrigger_Click" />

<asp:UpdatePanel runat="server" UpdateMode="Conditional">
    <Triggers>
        <asp:AsyncPostBackTrigger ControlID="btnOrdersTrigger" />
    </Triggers>
    <ContentTemplate>
        <asp:GridView  ID="gvOrders" runat="server" />
    </ContentTemplate>
</asp:UpdatePanel>  

Handle the OnClientActiveTabChanged Event

The TabContainer control exposes a OnClientActiveTabChanged event that is fired on the client when the active tab index is changed.  We are going to hook into this event in order to determine when to force a Tab's content to update.  First, you can set this attribute to the name of the javascript function you want to trigger the load.  In my sample this function is called clientActiveTabChanged.

<ajaxToolkit:TabContainer ID="tabContainer" runat="server" ActiveTabIndex="0" OnClientActiveTabChanged="clientActiveTabChanged" />

Next, we can go ahead and implement this function as follows.  First, we need to determine what the active tab is, once we know that we can decide if we need to trigger the loading of the Tab's content.  The TabContainer javascript object exposes a get_activeTabIndex() property that returns the index of the currently active tab.  In my example, if the index is 1 I need to load the Orders tab and if it is 2 I need to load the Employees tab.  Then, we can use the $get function to see if the DOM element for the tabs GridView is already loaded, if it is we don't need to do anything, otherwise we need to start an asyncpostback so the GridView can be properly loaded.  The complete javascript function that does this is as follows. 

function clientActiveTabChanged(sender, args) {
    
    //  see if the table elements for the grids exist yet
    var isTab1Loaded = $get('<%= this.gvOrders.ClientID %>');
    var isTab2Loaded = $get('<%= this.gvEmployees.ClientID %>');

    //  if the tab does not exist and it is the active tab, 
    //  trigger the async-postback
    if(!isTab1Loaded && sender.get_activeTabIndex() == 1){
        // load tab1
        __doPostBack('btnOrdersTrigger', '');
    }
    else if(!isTab2Loaded && sender.get_activeTabIndex() == 2){
        // load tab2
        __doPostBack('btnEmployeesTrigger', '');
    }            
}

Handle the ServerClick events of the UpdatePanel triggers

Next, we need to manually force the binding of data to the GridViews.  This is done by handling the ServerClick event of the input buttons for both the Orders and Employees GridViews.  This is done as follows:

/// <summary>
/// 
/// </summary>
/// <param name="sender"></param>
/// <param name="args"></param>
protected void BtnOrdersTrigger_Click(object sender, EventArgs args)
{
    this.gvOrders.Visible = true;
    this.gvOrders.DataBind();
}

/// <summary>
/// 
/// </summary>
/// <param name="sender"></param>
/// <param name="args"></param>
protected void BtnEmployeesTrigger_Click(object sender, EventArgs args)
{
    this.gvEmployees.Visible = true;
    this.gvEmployees.DataBind();            
}

In addition to this we need to make sure that databinding does not occur when the page is first loaded.  This can be done by setting the Visible property of the GridViews in the Orders and Employees tabs to false.  Controls that have a Visible value of false are not rendered and will not be databound.

<asp:GridView ID="gvEmployees" runat="server" Visible="false" />

Handle the pageLoad, beginRequest and endRequest Events for Displaying a Progress Indicator

Finally, the last step is to wire up some sort of progress indicator so the user can see that their action has caused the content within the tab to update.  In my example I am doing this by displaying an animated gif as a progress indicator.  I have a few other posts (here and here) on this blog describing how this can be done so I will not explain it here.  You can also download the sample site to view the source for the complete page.

That's it, Enjoy!


TrackBack

TrackBack URL for this entry:
http://mattberseth.com/blog-mt/mt-tb.fcgi/49

Listed below are links to weblogs that reference How To: Lazy-load TabPanel's within the AjaxControlToolkit's TabContainer Control:

» How To: Using the Ajax TabContainer control from help.net
Matt Berseth work on a data-centric application and show how to use properly the TabContainer control [Read More]

» How To: Lazy-load TabPanel's within the AjaxControlToolkit's TabContai from DotNetKicks.com
You've been kicked (a good thing) - Trackback from DotNetKicks.com [Read More]

Comments


Posted by: Lior on August 9, 2007 12:00 AM

Hi Matt,
i tried to implement it to extensible panel extender, but without much success.
can you tell me what needs to be changed for it to work?
thanks

Posted by: Tarun on August 12, 2007 12:00 AM

Great Article you solved 1 of my big problem

Posted by: :-/ on August 13, 2007 12:00 AM

How to with master page?

Posted by: Mark Strawmyer on August 26, 2007 12:00 AM

Great Article!

I kept having it post back the form, but not fire the other supporting server side events. I finally figured out adding .UniqueID to the __doPostBack got it to work.

__doPostBack(, );

Posted by: App Wrangler on October 3, 2007 12:00 AM

Matt,
Great post, this is exactly what Im looking for. However, everytime the I click on a tab it seems to post back but looks like it reloads the page and does not load the other gridview. Any ideas?
Thanks!

Posted by: Col on October 8, 2007 12:00 AM

Nice, just what I need!

I second the comment about using the postback buttons ClientID or UniqueID if your server event isnt being fired. Had me puzzled all morning...

Posted by: Mesut on October 11, 2007 12:00 AM

Hi Matt,

thank you its useful information you post. I have a question. What if I would like to use programmatically databinding. I mean drop the gridview in the panel and assign the member and dataset to datasource in code-behind. The intellisence doesnt show the gridview control (C#). Do you have any suggestions?

cheers, mesut

Posted by: Wei on November 7, 2007 12:00 AM

How can I add a dynamic tab using code behind?

here is my codes,

AjaxControlToolkit.TabPanel thePanel = new AjaxControlToolkit.TabPanel();
thePanel.HeaderText = "New Tab"; thePanel.ContentTemplate = Page.LoadTemplate("holeInfo.ascx");
tabHoleContainer.Controls.Add(thePanel);

but when I was trying to add the second tab, I received error message "Specified argument was out of the range of valid values. Parameter name:index"

Thanks.

Posted by: Mustafa Basgun on November 12, 2007 12:00 AM

@Wei
Try creating your TabPanels dynamically within the Page_Init method.

Your article helped me with my work today, sending my thanks.

Posted by: Karen Roslund on December 4, 2007 12:00 AM

Nice article.. just what i wanted to do.. but i was wondering if it possible to assign the header text.. based on the userId

Posted by: Aaron Schurg on December 6, 2007 12:00 AM

Thanks for posting. Anyone know what the "$get(" javascript function is all about? Ive never seen that before looking at the ajax tab control. Is it just a string parse function? Thanks.

Posted by: Karen Roslund on December 7, 2007 12:00 AM

its like document.getElementById(id)

Does any one know how can i load a user control (.ascx) in the content template of the Tab panel programatically.

Posted by: J Carneiro on December 27, 2007 12:00 AM

Berseth,
Good post! Im trying to use your idea in a web user control but i found some problems. Can you help me?

The first one is about the input control id. Since the moment we are in a user control context the code__doPostBack(btnOrdersTrigger, );

probably must be changed to

am i right?

The second one is about the onserverclick event handler name declared in the input controls. My web user control doesn´t call the handlers. Is it because i am in a user control context and so i need to make some changes?

thanks in advance!

Posted by: CD on February 5, 2008 12:00 AM

Just to reiterate the __doPostBack problem that a few people have highlighted.

To fix,

change:
__doPostBack(btnOrdersTrigger, );

to:
__doPostBack(LESSTHANSIGN%= btnOrdersTrigger.UniqueID %GREATERTHANSIGN,);

Posted by: Tu Dang Tuan on February 19, 2008 12:00 AM

Hi Matt,

I want to say thank you for your this article, it makes me understand the way to use Ajax control toolkit more clearly. Im looking for your new great Ajax articles.

Thank you and best regards

Tu

Posted by: Ve on February 28, 2008 12:00 AM

I did it in a slightly different way:
1. Put the tab container in an update panel with the default settings
and and make sure the tab container has
OnClientActiveTabChanged="ActiveTabChanged"

2. Add this only as a client-side function

function ActiveTabChanged(sender, e) {
__doPostBack(sender.get_id(), sender.get_activeTabIndex());
}


This is it. Now all you have to do is use a select..case statement int he server side activetabchanged method to grab the currently active tab and do the appropriate databinds

Posted by: Paul Chu on February 28, 2008 12:00 AM

Hey Tu Dang Tuan

I liked the "simpler" method of wrapping a update panel around the Tab Container.

This really helped me out.
Also thanks to Matt for discussing this very important topic.
I avoided the Tab Control until I can implemenent efficient data binding when a tab panel is clicked and not doing all the queries at page load time.

Happy day to all, Paul

Posted by: Chris Wigley on March 10, 2008 12:00 AM

Matt,

Thanks for the article. When you have time could you explain how to load .ascx controls on tab clicks? I have a situation that prevents me from statically declaring the tab panels and content templates; my tabs are built from a list of nav items, and I dont know how many nav items there will be (or which ones) at design time. So, I need to build the tabs on init, but then lazy load ascx controls into dynamically created tab panels each time a tab is clicked. If Ive been clear enough such that you understand what I am trying to do, would you be able to explain to me how I can do this?

Thanks,
Chris

Matt,

Your posts on the AjaxControlToolkit have been extremely helpful. I am using the Tab extender. On ActiveTabChanged I am evaluating information about the data on the old (leaving) tab. If certain conditions are not met, I want to stop the tab change and revert to the original tab. However, at the point of ActiveTabChanged, the tab has already changed, and I have not found a way to programmatically change the tab index back.

Is there a way to stop a tab change or to programmatically change a tab?

Thanks,

kw.

Posted by: kiran on April 1, 2008 12:00 AM

Hi

thanks for the tutorial...I had some issues both with modal pop up and lazy loading with respect to the UI in IE 7. Did anyone else face the same problem ? This is the tag i have in my master page :

Posted by: Thakkar on April 14, 2008 12:00 AM

Matt,

Excellent article. I am using your idea in one of my project..Everything works fine except one problem...I have ajax tab control with exact same functionality described in above article inside user control which is on master page and update panel inside content page...Everything works fine if i navigate first through all tabs in master page and then do some activity inside content page..but whenever I do some activity inside content page and then try to click one one of the tabpanel then it causes full postback...Further, I found out that after __dopostback("tab1",,) pagerequestmanager.beginrequest is never called..

Do you know how can i fix this problem? Any help will be greatly appreciated..

Thanks.

Thanks... its very helpfull

Posted by: Dave on May 15, 2008 12:00 AM

Anyone figure out the masterpage issue?

Posted by: praveent on May 21, 2008 12:00 AM

thanx,its wonderfull

Posted by: Wesley on June 2, 2008 12:00 AM

Hi,
Im using almost the same thing but Im loading the ASCX controls dynamic. The problem is that on the production server sometimes my responses from ajax are not put into the update panel. What i mean is that sometimes I got not or another or a wrong ajax response back on my updatepanel on a tabpage. It was working on casini but not on my server with IIS 6.0 and win2k3.

Posted by: mreinsmith on June 4, 2008 12:00 AM

I am creating my controls dynamically, including the htmlinputbuttons and the triggers. Everything works except it will not trigger the sub in the "code-behind" to set the gridview to databind and be visable. Anyone have any ideas? Really beating my head against the wall on this one, so any suggestions would be appreciated. Thanks.

Posted by: Sepideh on June 13, 2008 12:00 AM

I have a tab container with 5 tabs, but I want the first tab just load, the rest of the tabs can be loaded but befor that user click on tab and after loading the first tab, I mean I dont went user be waiting for loading other tabs, how ever cab see the first tab fast.
Thansx

Posted by: getstoopid on June 16, 2008 12:00 AM

i tried to use matts code, but instead of gridview i try to lazyload reportviewer-controls. it all seems to work well... the data is rendered correctly and in firefox all is ok but in ie there is an "object expected"-exception after all data is rendered (visualstudio shows the exception in MicrosoftAjaxWebForms.js at scrollingsection).
does anyone know this problem and knows what to do?

Posted by: Kevin on July 21, 2008 12:00 AM

I wrote this in .Net 2.0 and everthing worked fine, but the project was recently converted to 3.5 and the tabcontainer went berzerk. It seems that the postback called triggered by the tab index changing gets fired many times, causing lots of weird problems. Any suggestions?

Posted by: Vu Trung Dung on August 6, 2008 04:34 AM

hi,every body
i want to help.
if i use Tab inside MasterPage then when i click to Tab 2 it's load full page.
Example my content tab inside ContentPlaceHolder .
Best regard.

Posted by: Sax G on August 6, 2008 04:12 PM

@Vu Trung Dung:
This solution originally posted by CD solved the exact same problem that you are having.

Hope it helped!

Sax

[Quote]
Just to reiterate the __doPostBack problem that a few people have highlighted.

To fix,

change:
__doPostBack(btnOrdersTrigger, );

to:
__doPostBack(LESSTHANSIGN%= btnOrdersTrigger.UniqueID %GREATERTHANSIGN,);
[/Quote]

Posted by: Sax G on August 6, 2008 04:13 PM

And, oh yes Matt... Thanks a million for sharing the article.

Best,
Sax

Posted by: c2e on August 8, 2008 06:39 PM

hello Matt,

On your LazyLoad demo, how did you make the tabcontainer white out while the loader runs?

Thanks much!

Posted by: Anonymous on August 13, 2008 03:47 PM

Sounds like a lot of hell just to manage postbacks, bindings, etc. for the tab control as a whole. To me this is a nightmare.

Thanks again. You've provided another solution for me. Thanks also to those commenters that provided the .UniqueID solution as I had to use that as well.

Posted by: vipin on September 12, 2008 03:13 AM

Does supports ADA and section 508 guidelines?
Is there any way to acheieve this?

Hi,

when I load tabs dynamically, i still have problems with ...AjaxControlToolkit.TabContainer.set_ActiveTabIndex: ...out of the range of valid values... exception
I initialize all tabs inside the Page_Init event.
When declarative added tab is active, everything works perfect.
When I do postback with active dynamically added tab, works too, but every next postback throws this exception, whichever tab is active.
Please Help!

Otherview great article. Thanks!

Post a comment

(If you haven't left a comment here before, you may need to be approved by the site owner before your comment will appear. Until then, it won't appear on the entry. Thanks for waiting.)

Consulting Services

Yep - I also offer consulting services. And heck, I'll do just about anything. If you enjoy my blog just drop me an email describing the work you need done.

Recent Comments

  • Wue wrote: Hi, when I load tabs dynamically, i still have problems with ...AjaxControlToolkit.TabContainer.set...
  • vipin wrote: Does supports ADA and section 508 guidelines? Is there any way to acheieve this?...
  • Michael Paladino wrote: Thanks again. You've provided another solution for me. Thanks also to those commenters that provid...
  • Anonymous wrote: Sounds like a lot of hell just to manage postbacks, bindings, etc. for the tab control as a whole. ...
  • c2e wrote: hello Matt, On your LazyLoad demo, how did you make the tabcontainer white out while the loader run...
  • Sax G wrote: And, oh yes Matt... Thanks a million for sharing the article. Best, Sax...
  • Sax G wrote: @Vu Trung Dung: This solution originally posted by CD solved the exact same problem that you are hav...
  • Vu Trung Dung wrote: hi,every body i want to help. if i use Tab inside MasterPage then when i click to Tab 2 it's load fu...