I have always considered myself to be quite client-side challenged. Having been soured to the thought of JavaScript by all the browser specific code I used to have to write years ago I swore off it and took to server-side development and never looked back. After a year’s hiatus doing WPF programming, I’ve recently found myself back in the thick of web development with new patterns and technologies at the forefront, namely the MVC framework and the ubiquity of jQuery. Before I begin this post I’d like to say that jQuery is, well, everything that is good about client-side programming in my opinion.

So recently I found the need to roll my own dirty form warning mechanism on a site I was working on. These forms contain quite a lot of fields and so there is gratuitous use of the TabControl from the Microsoft AJAX Control Toolkit. One of the requirements was to alert the user to changes made on one of the tabs and offer the option to save the changes before allowing the user to change tabs. Initially I thought that this task would be a breeze, what with the OnClientActiveTabChanged handler on the TabContainer control and all. Then the sad reality dawned on me. There is no way to cancel the changing of tabs from the client side. Like a freight train full of explosives it barrels on, destroying my workflow and with it any chance I’ll get sleep. But all is not lost, because with a few JavaScript ninja moves, some nifty jQuery (optional) and no personal life there is a clean solution to this problem, So what’s the answer? Read on for the details.

What’s The Issue

The problem with the existing implementation is two-fold. Taking a dive into the JavaScript for the control yields the first problem (lines 6 - 11):

```javascript linenos=table AjaxControlToolkit.TabContainer.prototype = { add_activeTabChanged: function (handler) { this.get_events().addHandler(“activeTabChanged”, handler); }, remove_activeTabChanged: function (handler) { this.get_events().removeHandler(“activeTabChanged”, handler); }, raiseActiveTabChanged: function () { var eh = this.get_events().getHandler(“activeTabChanged”); if (eh) { eh(this, Sys.EventArgs.Empty); } if (this._autoPostBackId) { __doPostBack( this._autoPostBackId, “activeTabChanged:” + this.get_activeTabIndex() ); } }, };


I included more code here than is required because it’s important to see the context of where the problem lies. We’re looking at the prototype object definition for the TabContainer control, the root object of the TabControl. The problem method here is the **raiseActiveTabChanged **definition.  What this brilliant little snippet does is pretty much say, if there’s a client side handler for when the tabs change, fire it off. Then there’s the postback. What’s problematic here is that the result of the user handler for tab changes is completely left out of the decision to postback. I suppose someone could be sneaky and just set the autopostbackid to null but that seems like an important piece. When I started out, I thought maybe this was the place to make my attack, but then there was the second issue, illustrated below:

```javascript linenos=table
set_activeTabIndex : function(value) {
  if (!this.get_isInitialized()) {
    this._cachedActiveTabIndex = value;} else {
    if (value < -1 || value >= this.get_tabs().length) {
      throw Error.argumentOutOfRange("value"); }
    if (this._activeTabIndex != -1) {
      this.get_tabs()[this._activeTabIndex]._set_active(false);}
    this._activeTabIndex = value;if (this._activeTabIndex != -1) {
    this.get_tabs()[this._activeTabIndex]._set_active(true);}
    if (this._loaded) {
      this.raiseActiveTabChanged();}
    this.raisePropertyChanged("activeTabIndex");}
}

That’s right, when a tab is clicked, the first thing that happens (even before the postback) is that the new tab’s panel is shown and the current panel hidden via the tabControl’s set_activeTabIndex() function. The key parts of this function are on lines 7 and/or 9, which call the _set_active() method of the tab, which sets it’s visibility to shown. This means that even if you were able to avoid the postback and give a user some sort of confirmation dialog, the user would still be looking at the new tab, and only after confirming, would be brought back to the original tab. I don’t know about you, but that doesn’t scream good UX to me.

There’s Gold in Them There Hills

After a bit of searching, I did eventually find my diamond in the rough. The following event handler is the earliest place where the set_activeTab() method is called, and it’s defined on the TabPanel prototype itself. This will be where I perform my ninja skills:

_header_onclick : function(e) {
this.raiseClick();this.get_owner().set_activeTab(this);}

As you can see in the snippet, this is what controls how the TabControl does it’s thing here, so if we could just augment this code with our own handler and put some additional logic around the call to the owner’s set_ActiveTab method then we’d be cooking with gas in no time. Well, it turns out there is a way to do this.

Secret, Secret, I’ve Got a Secret!

While I wish every problem could be solved by listening to Mr. Roboto by Styx, alas we live in reality. A long time ago a JavaScript master, who just so happens to be the other person that posts on this blog, told me long ago that you can pretty much override any default JavaScript object’s method declarations, property declarations, actually, pretty much anything you want. Now, thanks to a bit of a refresher course and my new target function, the following function is what I used to replace the above _header_onclick method:

AjaxControlToolkit.TabPanel.prototype._header_onclick = function (e) {
  this.raiseClick();

  if (
    confirm(
      "Tabs are changing! Click Ok to proceed, or Cancel to remain current tab."
    )
  )
    this.get_owner().set_activeTab(this);
  else return false;
};

As you can see, it’s so simple. The key line is the first one. I’m telling the TabPanel object that I’m replacing its current definition with my own. Then I just put in a confirm dialog (line 5) and depending on the result I either let the tab control work as expected or I can cancel and do nothing. Basically, this is all there is to it. So how do we make it work? That’s also simple. You can use a little bit of jQuery, or you can use the existing Ajax client side lifecycle events. First, let’s see how we can do this with Ajax:

function pageLoad(sender, args) {
  AjaxControlToolkit.TabPanel.prototype._header_onclick = function (e) {
    this.raiseClick();

    if (
      confirm(
        "Tabs are changing! Click OK to proceed, or click Cancel to remain on the current tab."
      )
    )
      this.get_owner().set_activeTab(this);
    else return false;
  };
}

In Ajax, any function called pageLoad found in the page scripts will automatically be run when the page is loaded. You could also be explicit and use the Sys.Application.add_load() method. Now let’s take a look at the jQuery implementation:

$(function () {
  AjaxControlToolkit.TabPanel.prototype._header_onclick = function (e) {
    this.raiseClick();

    if (
      confirm(
        "Tabs are changing! Click OK to proceed, or click Cancel to remain on the current tab."
      )
    )
      this.get_owner().set_activeTab(this);
    else return false;
  };
});

Oh the Places You’ll Go

So what can you do from here?  well, the possibilities are endless. Personally, I added a bit more meat to the function I augmented to support checking if the current tab content was dirty and asking the user if they wanted to save before switching tabs. What could be done is to create another method declaration on the TabPanel prototype called OnActiveTabChanging. You can then change the _header_onclick method above to check for and call any user defined handlers for OnActiveTabChanging and use the return value to either allow or reject the switching of the active tab. This approach gives you a bit more pluggability in the case that you plan to use this feature in conjunction with the TabControl on more than one occasion. In any event, I hope that you benefit from this post if you’ve been frustrated by the lack of a comparable feature but just don’t have the time or desire to switch to jQuery Tabs.