So, I've got a requirement for an asp.net application that collects data and makes use of Tabs.  The basic idea is, as each section of the forms are filled out, the tab will change colors to give a visual queue to the user that they have completed that individual tab section.

Standard Tab Styles:
tabstrip_standard

The goal of all the tabs being complete:
tabstrip_complete

In Process, some done, some not done (Blue means not done)
tabstrip_mixed

The TabContainer of the AjaxControlToolkit supports custom styles, and this process is very well documented in a number of places.  Once could easily change the look of all of the tabs.... but to get some tabs appear one way, and some another, is a totally different story.  It is possible to use a HeaderTemplate to change the way that the tab is rendered, but for my application, I didn't want to introduce the level of complexity of creating a separate header template for each tab for all possible states.  I wanted to use the simplicity of dropping a TabContainer on the page and just setting a modifier property on the individual tab that I wanted to display differently.  I also imposed the requirement that I couldn't change the default way that the TabContainer operated.

So that's what I did.

Working with the AjaxControlToolkit source can be a little undaunting at first.  The integration of server side web controls with client side JavaScript and embedded resources can get confusing if you don't approach it with a logical train of thought.

The first thing that came to my mind was I needed a way to set a property of the individual TabPanel to flag it as being "special" in some way.  I created a simple cssModifier property in the TabPanel object to accept a string.

[DefaultValue("")]
[Category("Appearance")]
[ExtenderControlProperty]
[ClientPropertyName("cssModifier")]
public string cssModifier
{
get { return (string)ViewState["cssModifier"] ?? string.Empty; }
set { ViewState["cssModifier"] = value; }
}

And the last modification to TabPanel.cs is to address the DescribeComponent method to announce our new property to the script handler library:

protected override void DescribeComponent(ScriptComponentDescriptor descriptor)
{
base.DescribeComponent(descriptor);
descriptor.AddProperty("cssModifier", this.cssModifier);
descriptor.AddElementProperty("headerTab", "__tab_" + ClientID);
if (_owner != null)
{
descriptor.AddComponentProperty("owner", _owner.ClientID);
}
}


Once that's complete, then it's time to move on to the JavaScript which is the one that's actually responsible for generating the look and feel of our tabs on the client side...

In the object declaration of our client TabPanel control, add a variable for the cssModifier:

AjaxControlToolkit.TabPanel = function(element) {
AjaxControlToolkit.TabPanel.initializeBase(this, [element]);
this._active = false;
this._tab = null;
this._headerOuter = null;
this._headerInner = null;
this._cssModifier = "";
this._header = null;
this._owner = null;


In the prototype, we need to establish our getter and setter methods for that property:

get_cssModifier : function() {
return this._cssModifier;
},
set_cssModifier : function(value) {
if (this._cssModifier != value) {
if (this.get_isInitialized()) {
throw Error.invalidOperation(String.format(AjaxControlToolkit.Resources.Tabs_PropertySetAfterInitialization, 'cssModifier'));
}
this._cssModifier = value;
this.raisePropertyChanged("cssModifier");
}
},


And lastly, we modify the initialize method to set the original value of _cssModifier and append that to our CSS classes that get injected into the DOM for our tabs..

initialize : function() {
AjaxControlToolkit.TabPanel.callBaseMethod(this, "initialize");

var owner = this.get_owner();
if (!owner) {
throw Error.invalidOperation(AjaxControlToolkit.Resources.Tabs_OwnerExpected);
}

this._tabIndex = owner.get_tabs().length;

Array.add(owner.get_tabs(), this);

this._headerOuterWrapper = document.createElement('span');
this._headerInnerWrapper = document.createElement('span');
this._tab = document.createElement('span');
this._tab.id = this.get_id() + "_tab";
this._cssModifier = this.get_cssModifier();
this._header.parentNode.replaceChild(this._tab, this._header);
this._tab.appendChild(this._headerOuterWrapper);
this._headerOuterWrapper.appendChild(this._headerInnerWrapper);
this._headerInnerWrapper.appendChild(this._header);
$addHandlers(this._header, {
click:this._header_onclick$delegate,
mouseover:this._header_onmouseover$delegate,
mouseout:this._header_onmouseout$delegate,
mousedown:this._header_onmousedown$delegate,
dragstart:this._oncancel$delegate,
selectstart:this._oncancel$delegate,
select:this._oncancel$delegate }); Sys.UI.DomElement.addCssClass(this._headerOuterWrapper, "ajax__tab_outer" + this._cssModifier);
Sys.UI.DomElement.addCssClass(this._headerInnerWrapper, "ajax__tab_inner" + this._cssModifier);
Sys.UI.DomElement.addCssClass(this._header, "ajax__tab_tab" + this._cssModifier);
Sys.UI.DomElement.addCssClass(this.get_element(), "ajax__tab_panel");

if (!this._enabled) {
this._hide();
}
},

Once all of that work is done with the AjaxControlToolkit, we can compile the assembly and reference it in our website.  The HTML markup in the aspx page remains unchanged from an unmodified TabContainer control... So we've met one of our original requirements of maintaining the default behavior.

<cc1:TabContainer ID="TabContainer1" runat="server" ActiveTabIndex="0" CssClass="ccInnerTabstrip">     <cc1:TabPanel runat="server" HeaderText="Tab One" ID="TabPanel1">         <ContentTemplate>             <h2>Tab Panel 1 Content</h2>         </ContentTemplate>     </cc1:TabPanel>     <cc1:TabPanel runat="server" HeaderText="Tab Two" ID="TabPanel2">         <ContentTemplate>             <h2>Tab Panel 2 Content</h2>         </ContentTemplate>     </cc1:TabPanel>     <cc1:TabPanel runat="server" HeaderText="Tab Three" ID="TabPanel3">         <ContentTemplate>             <h2>Tab Panel 3 Content</h2>         </ContentTemplate>     </cc1:TabPanel> </cc1:TabContainer> 

Now, in the code behind, we can change the styles emitted by the client side JavaScript by setting the cssModifier property of the tab panel in question..

TabContainer1.Tabs[0].cssModifier = "";
TabContainer1.Tabs[1].cssModifier = "-blue";
TabContainer1.Tabs[2].cssModifier = "-green";


What happens is... when the JavaScript builds the client control, a new set of spans is created with the CSS classes: ajax__tab_outer, ajax__tab_inner, ajax__tab_tab.  What setting the cssModifier on a tab does is change those emitted span tag css class to include the cssModifier value appended to the default CSS class names.  So if our modifer is "-blue" then ajax__tab_outer-blue, ajax__tab_inner-blue, ajax__tab_tab-blue get created in it's place.  So now all we have to do is fix our style sheet to define the new styles for the tabs:

/* blue tabs */ .ccInnerTabstrip .ajax__tab_outer-blue 
{
padding-right:0px; background:url('TabImages/tab-blue-right.gif') no-repeat right; height:21px; display:-moz-inline-box; display:inline-block; } .ccInnerTabstrip .ajax__tab_inner-blue
{
padding-left:3px; background:url('TabImages/tab-blue-left.gif') no-repeat; display:-moz-inline-box; display:inline-block; } .ccInnerTabstrip .ajax__tab_tab-blue
{
height:21px; padding:4px; margin:0;
background:url('TabImages/tab-blue.gif') repeat-x; margin-right:4px; overflow:hidden; text-align:center; cursor:pointer; display:-moz-inline-box; display:inline-block; } .ccInnerTabstrip .ajax__tab_hover .ajax__tab_outer-blue
{
background:url('TabImages/tab-blue-hover-right.gif') no-repeat right; } .ccInnerTabstrip .ajax__tab_hover .ajax__tab_inner-blue
{
background:url('TabImages/tab-blue-hover-left.gif') no-repeat; } .ccInnerTabstrip .ajax__tab_hover .ajax__tab_tab-blue
{
background:url('TabImages/tab-blue-hover.gif') repeat-x; } .ccInnerTabstrip .ajax__tab_active .ajax__tab_outer-blue
{
background:url('TabImages/tab-active-right.gif') no-repeat right; } .ccInnerTabstrip .ajax__tab_active .ajax__tab_inner-blue
{
background:url('TabImages/tab-active-left.gif') no-repeat; } .ccInnerTabstrip .ajax__tab_active .ajax__tab_tab-blue
{
background:url('TabImages/tab-active.gif') repeat-x; color: #3333CC; }


When we use firebug in FireFox to examine the DOM, this is the results of the output from the script with no CSS modifier:

<div style="visibility: visible;" class="ccInnerTabstrip ajax__tab_container ajax__tab_default" id="TabContainer1">     <div id="TabContainer1_header" class="ajax__tab_header">         <span id="TabContainer1_TabPanel1_tab" class="ajax__tab_active">             <span class="ajax__tab_outer">                 <span class="ajax__tab_inner">                     <span id="__tab_TabContainer1_TabPanel1" class="ajax__tab_tab">Tab One</span>                 </span>             </span>         </span>         <span id="TabContainer1_TabPanel2_tab">             <span class="ajax__tab_outer">                 <span class="ajax__tab_inner">                     <span id="__tab_TabContainer1_TabPanel2" class="ajax__tab_tab">Tab Two</span>                 </span>             </span>         </span>     </div>     <div id="TabContainer1_body" class="ajax__tab_body">         <div style="visibility: visible;" id="TabContainer1_TabPanel1" class="ajax__tab_panel"></div>         <div style="display: none; visibility: hidden;" id="TabContainer1_TabPanel2" class="ajax__tab_panel"></div>     </div> </div>

And when using the cssModifier Property:
<div id="TabContainer1_header" class="ajax__tab_header">     <span id="TabContainer1_TabPanel1_tab" class="ajax__tab_active">         <span class="ajax__tab_outer-blue">             <span class="ajax__tab_inner-blue">                 <span id="__tab_TabContainer1_TabPanel1" class="ajax__tab_tab-blue">Tab One</span>             </span>         </span>     </span>     <span id="TabContainer1_TabPanel2_tab" class="">         <span class="ajax__tab_outer-blue">             <span class="ajax__tab_inner-blue">                 <span id="__tab_TabContainer1_TabPanel2" class="ajax__tab_tab-blue">Tab Two</span>             </span>         </span>     </span> </div>


So, by making a few small changes to the AjaxControlToolkit, we've made it possible to control the look and feel of our individual tabs through the addition of a simple cssModifier property and control of our style sheets... and best of all, the changes that we've made are 100% backwards compatible with the current released version of the AjaxControlToolkit, so all existing code will continue to function as intended.

Download the Sample Project: Custom_AjaxControlToolkit_Tabs.zip
Download the SVN Patch File: TabContainer_20080509_CustomTabStyles.zip