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>