Introduction
The TabContainer
control in the AJAX Control Toolkit is a great way of displaying a lot of data in a very limited space on your web page. However, one problem with it is that when the user leaves the page and returns, it defaults back to the first tab - causing, in the best scenario, an extra mouse click, and in the worst-scenario, a confused or angry user (trust me, they're out there).
This article refers to and is, in fact, partly embedded in another article I wrote on maintaining the state of controls between visits to a page. If you haven't read it, I suggest you do so.
Using the code
One of the main reasons we can't easily maintain the clicked state of a tab is, in fact, one of the benefits of the tab control - switching between tabs doesn't cause a post back. However, in order to maintain any kind of state, at some point, we need to communicate back to our server, and this is the essence of my approach below. (Actually, does anybody know any client-side only methods for maintaining state? Managing cookies within JavaScript, for instance?)
The OnActiveTabChanged() event
[Aside: this section deals with JavaScript code, but because I have encapsulated everything in re-usable classes, the code examples below use VB.NET to render the JavaScript to the page. It is slightly harder to follow, but all part of the bigger project in the end.]
As the name implies, this property of the TabContainer()
control allows you to specify the JavaScript code to call when the user switches between tabs:
If C.GetType Is GetType(AjaxControlToolkit.TabContainer) Then
Dim JS As New StringBuilder(vbCrLf)
JS.Append("function SaveTabState_" & C.ID & "(){")
JS.Append(vbCrLf & "_SaveTabState('" & C.ClientID & "','" & C.UniqueID & "');")
JS.Append(vbCrLf & "}")
Page.ClientScript.RegisterClientScriptBlock(Me.GetType, _
"SaveTabState_" & C.ID, JS.ToString, True)
DirectCast(C, AjaxControlToolkit.TabContainer).OnClientActiveTabChanged = _
"SaveTabState_" & C.ID
Within that function, what we do is create a callback to the server using a standard web service call from JavaScript:
If Not HasRegisteredCommon Then
JS = New StringBuilder(vbCrLf)
JS.Append("function _SaveTabState(clientID, uniqueID){")
JS.Append(vbCrLf & "var tabBehavior = $get(clientID).control;")
JS.Append(vbCrLf & "var index = tabBehavior.get_activeTabIndex();")
JS.Append(vbCrLf & "LookupService.SaveTabIndex('" & _
Me.Page.Request.Path & "_' + uniqueID,index);")
JS.Append(vbCrLf & "}")
Page.ClientScript.RegisterClientScriptBlock(Me.GetType, _
"SaveTabState", JS.ToString, True)
And of course, with the new Microsoft AJAX Extensions framework, putting all this together is very simple using the ScriptManager()
class. Below, we hook into the existing ScriptManager
using the ScriptManagerProxy()
class:
Dim SMP As New ScriptManagerProxy()
SMP.Services.Add(New ServiceReference("~/Services/LookupService.asmx"))
Page.Controls.Add(SMP)
HasRegisteredCommon = True
End If
End If
The Lookup Web Service
The web service called in JavaScript is extremely simple:
Public Sub SaveTabIndex(ByVal UniqueTabID As String, ByVal Index As Integer)
System.Web.HttpContext.Current.Session(UniqueTabID) = Index
End Sub
Restoring state
We now have the current tab index stored in a session, which we can later access from the server - namely, when we are re-instantiating the page upon return. Simple, huh? If you are happy with that, it is just a case of setting TabContainer.ActiveTabIndex = CInt(Session(UniqueTabID))
in the Page.Load()
event of your page.
However, we programmers like things tidy and lazy, so I have worked out a solution which means you don't even have to do that. See my other article on maintaining control state upon page revisits, for more information.