Tuesday, May 26, 2009

How to cancel Tab active changing with Validation

Sometimes, when you change the active Tab Panel, you want to do validation and check if it should go to next tabpanel. It will go to the new Tab Panel only if it is meets the validation.
For example, there is a TextBox in TabPanel. If TextBox is empty, I don't want to let it go to another TabPanel.
For this scenario, the validation is based on the client side. The first confirm is we have to use JavaScript to do validation or catch the validation result if you used Validation control. There is a client event "ActiveTabChanged"(add_activeTabChanged method in behavior) of TabContainer you can make use of to do validation in this event. If it is valid, you can let it free to go. Overwise, you need use javascript to let it back to the previous tab. In this way, you need use a client validation to restore the history of the active tab index so that it can remeber which tab panel it can go back to in JavaScript .
But in this approach, it is go through the ActiveChanged client event. It means the active tab has been changed before we call this event. We would see the active tab goes to another one and go back again if it is invalid with validation. It looks too stupid and ugly.

So, I got two approaches to achieve this.

1. We can modify the source code of Tab Panel behavior as below. In _header_onclick method, it calls raiseClick and set the activeTab directly. We can insert an additional code line before setting active tab so that we can do something on validation.

AjaxControlToolkit.TabPanel.prototype._header_onclick = function(e) {
this.raiseClick();
if (isValidated()) // add this validation method
this.get_owner().set_activeTab(this);
};

It won't go to another tab unless it meets the validation. See the entire code below:



<body>
<form id="form1" runat="server">
<ajaxToolkit:ToolkitScriptManager runat="Server" EnablePartialRendering="true" ID="ScriptManager1" />
<ajaxToolkit:TabContainer runat="server" ID="TabContainer1">
<ajaxToolkit:TabPanel runat="server" ID="TabPanel1" HeaderText="TabPanel1">
<ContentTemplate>
TabPanel1
<input type="text" id="text1" />
</ContentTemplate>
</ajaxToolkit:TabPanel>
<ajaxToolkit:TabPanel runat="server" ID="TabPanel2" HeaderText="TabPanel2">
<ContentTemplate>
TabPanel2
</ContentTemplate>
</ajaxToolkit:TabPanel>
</ajaxToolkit:TabContainer>
</form>
</body>

<script type="text/javascript">

AjaxControlToolkit.TabPanel.prototype._header_onclick = function(e) {
this.raiseClick();
if (isValidated()) // add this additional code line to do validation
this.get_owner().set_activeTab(this);

};

function isValidated() {
if ($get("text1").value == "")
return false;
return true;
}

</script>


2. The above code looks like a workround purely. The directly approach is using ActiveTabChanging client event. In general ASP.Net Ajax behavior model, we can call e.set_cancel(true) to cancel the changing behavior after validation, so we can prevent the active tab changing before ActiveTabChanged called.
Unfortunately, it doesn't contain this event in tab.js. The only public client event is add_activeTabChanged. So what I wanna saying is Let's make an add_activeTabChanging client event in tab.js behavior.

1) Please open tab.js in VS.
2) Please append the following code in AjaxControlToolkit.TabContainer.prototype = {


///<extended for activeTabChanging>
add_activeTabChanging: function(handler) {
this.get_events().addHandler("activeTabChanging", handler);
},
remove_activeTabChanging: function(handler) {
this.get_events().removeHandler("activeTabChanging", handler);
},
raiseActiveTabChanging: function(eventArgs) {
var eh = this.get_events().getHandler("activeTabChanging");
if (eh) {
eh(this, eventArgs);
}

},


///</extended for activeTabChanging>

3) Please modify set_activeTabIndex method block (The blod font is new code we need to append):



set_activeTabIndex : function(value) {
if (!this.get_isInitialized()) {
this._cachedActiveTabIndex = value;
} else {
///<extended for activeTabChanging>
var eventArgs = new Sys.CancelEventArgs();
this.raiseActiveTabChanging(eventArgs);
if (eventArgs.get_cancel()) {
return false;
}
///</extended for activeTabChanging>
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");

}
return true;
},


4) Then we can use add_activeTabChanging client event now. As the same HTML sample, you can just call this event on client to cancel the process if it doesn't meet the validation.


<body>
<form id="form1" runat="server">
<ajaxToolkit:ToolkitScriptManager runat="Server" EnablePartialRendering="true" ID="ScriptManager1" />
<ajaxToolkit:TabContainer runat="server" ID="TabContainer1">
<ajaxToolkit:TabPanel runat="server" ID="TabPanel1" HeaderText="TabPanel1">
<ContentTemplate>
TabPanel1
<input type="text" id="text1" />
</ContentTemplate>
</ajaxToolkit:TabPanel>
<ajaxToolkit:TabPanel runat="server" ID="TabPanel2" HeaderText="TabPanel2">
<ContentTemplate>
TabPanel2
</ContentTemplate>
</ajaxToolkit:TabPanel>
</ajaxToolkit:TabContainer>
</form>
</body>

<script type="text/javascript">

function pageLoad() {

$find('TabContainer1').add_activeTabChanging(aa);

}
function aa(se, e) {

if ($get('text1').value == "")
e.set_cancel(true);
}


</script>

12 comments:

Anonymous said...

Thank u for post..
___________________

Vince

Your movies on demand

Ami Solomon said...

Great!

Anonymous said...

Hi there,

KUDOS for you !!!! :-)
You simply are a genius. You cannot imagina how many countless hours I spent looking for a solution for this insane problem.
I got my application almost ready to go to production and I started to get complaints of people that could accidentally loose data when changing tabs.
I saw some nasty solutions, like keeping an array of all the tabs and marking them as "Dirty" as users would enter data and even worst: going back to the server using Ajax just to update the property !
THANKS SO MUCH FOR SHARING THIS (sorry for the caps, I could not help it !!)
You just made my day here (and my night because I am doing extra hours ! ha !)

Javier
Montreal, CA

Anonymous said...

Hi Vince,
I tried your example 1 with no success (modify the source code of Tab Panel behavior) :(((((

I want to implement the easiest and quickest solution but I keep getting javascript errors in the internals of the Ajax framework (not on the page where I put the script) when I try to modify the behavior of the header_click.

What else do I need ? A special version of the Ajax toolkit ? the .NET SP 1 ? I tried changing the scripts to Release, debug, auto, etc with no success :-((

I also added a reference to the Tabs.Tabs.js file with no luck.

please send me an email to jlagos@sympatico.ca, I need to have a solution for this problem that already took 2 days of my time :((

thanks and best regards !

Javier

Faraz Ahmed said...

AjaxControlToolkit.TabPanel.prototype._header_onclick


My question is regarding this line. Can you explain this a bit. I cannot understand what you did Thanks

Faraz Ahmed said...

AjaxControlToolkit.TabPanel.prototype._header_onclick


My question is regarding this line. Can you explain this a bit. I cannot understand what you did Thanks

Anonymous said...

For those who come to this post via Google:

The first solution proposed by the author works perfectly, you should just make sure you insert this javascript snippet AFTER the declaration of ScriptManager in your *.aspx file

Anonymous said...

How you can use a webservice in 【
function isValidated】?We want to check something in server side and then with the check result to decide whether to show new tab panel or not !

SedulousTurtle said...

These are good solutions to a very tricky problem; thank you, Vince.

Another note for users: keep in mind that, at least when using the first solution, the tab-change triggered javascript will likely fire BEFORE any asynchronous post-back is completed. You must take this into account if you're doing server-side validation before tab changes.

Alcides said...

You need change AjaxControlToolkit.TabPanel.prototype._header_onclick
for Sys.Extended.UI.TabPanel.prototype.header_onclick

Anonymous said...

Thanks to Alcides for pointing out the need to use "Sys.Extended.UI.TabPanel.prototype.header_onclick" in place of "AjaxControlToolkit.TabPanel.prototype._header_onclick" for the current version of the AjaxToolkit.

Thiv said...

where to find the tabs.js in VS?