One of the most challenging things (for me at least) is dealing with JavaScript. That's why I love the AjaxToolKit from MS. Using these tools means I don't have to write a lot of javascript myself. The downside to this is that when the time does come to "get my hands dirty", I'm usually floundering around for a while until I hit on a nugget or two that I can use.
Such is the case with the subject of today's notes, so let's get to it, shall we?
The Scenario: Content page in a ASP.Net 3.5 Web Application that relies HEAVILY on the AjaxToolKit for much of it's functionality. (The fact that this is a Content Page is important here). This page has several UpdatePanels, most of which have triggers of some sort. the one that we will focus our attention on is nested inside the fourth TabPanel of a TabControl, and is triggered by an external Timer. This Timer updates the text inside our subject TextBox. I should also mention here that the TabControl itself is ALSO inside an UpdatePanel, so we have something like this (the sections in orange are the ones we will concern ourselves with):
<asp:Panel>
<asp:UpdatePanel>
<ContentTemplate>
<asp:TabContainer>
<asp:TabPanel ID="TabPanel1" />
<asp:TabPanel ID="TabPanel2" />
<asp:TabPanel ID="TabPanel3" >
<ContentTemplate>
<asp:Button ID="Button1" Text="Begin Long Process" OnClick="Button1_Click" />
</ContentTemplate>
</asp:TabPanel>
<asp:TabPanel ID="TabPanel4" >
<ContentTemplate>
<asp:Panel>
<asp:UpdatePanel>
<ContentTemplate>
<asp:TextBox TextMode="MultiLine" Width="550px" Height="455px" />
</ContentTemplate>
<Triggers>
<asp:AsyncPostBackTrigger ControlID="Timer1" EventName="Tick" />
</Triggers>
</asp:UpdatePanel>
</asp:Panel>
</ContentTemplate>
</asp:TabPanel>
</asp:TabContainer>
</ContentTemplate>
</asp:UpdatePanel>
</asp:Panel>
<asp:Timer ID="Timer1" runat="server" Interval="3000" OnTick="Timer1_Tick" Enabled="False">
</asp:Timer>
Dizzying to say the least, and I wrote it! OK let's move on.
The task, is to initiate a VERY long running process (NO WE ARE NOT THREADING AN ASP.NET APPLICATION!) using the Button1_Click event, and then update the status using TextBox1 on a different tab, with the Timer1_Click event. The timer interval is set at 3 sec, because we have no idea what the external system is going to do, how it will respond, or how long it will take.
As a side note here, the button that kicks this off is on another tab because there are some conditions on that tab that play into the external call. For this discussion, those conditions are not important.
Now that we have the UI set up, let's look at the server-side code. Remember, we are using the AjaxToolKit, and it's ability to handle most of the Javascript required to take care of switching tabs, and partial page refreshes, while still using the postback of the page to trigger the timer event (jQuery could do the same thing, but hooking to the endpoint of the external system was a bit more cumbersome in this scenario). Because this is a webApp, the external system is available server-side by adding a reference, and then hooking to the endpoint when we need to.
The Button Click event:
protected void Button1_Click(object sender, EventArgs e)
{
TextBox1.Text = string.Empty;
Timer1.Enabled = true; //this starts the timer
//change the ActiveTab property of the TabContainer to the tab that has the TextBox
try
{
//set the Text property of TextBox1 based on the conditions that have been previously met
}catch (Exception ex){
TextBox1.Text = ex.Message + ex.StackTrace;
Timer1.Enabled = false;
}
}
The Timer Tick event:
protected void Timer1_Tick(object sender, EventArgs e)
{
string result = <result of the cal to the external system>
bool done= <condition for process to exit>;
if( !done )
{
//put something in the textbox
}else{
//put something in the text box
//alert the user that the process is complete
}
}
So far so good. We've got UI, and server-side code, but that darn text box keeps going back to the top, no matter what we put in it. So we have to do something on the client-side to show some sort of progress. We are going to do this inside the ContentPlaceHolder that is handling the content for out page, to make sure that the ToolKitScriptManager on the MasterPage is already loaded. Otherwise, we get the dreaded 'Sys.Application is not defined' error, and we have a hard time (at least I did) figuring out why.
With the AjaxToolkit, there are all kinds of things that are registered with the page behind the scenes. One of them is the Sys.Application object. We are going to use this object to tell the page to load a JavaScript function every time this page is initialized. Because we are using a timer that is set to every three seconds, that is how often this action will happen, until the timer is turned off. So here we go, and I'll explain a bit as we go along:
<script type="text/javascript">
//first we tell the application that there is a javascript function that we need to add
Sys.Application.add_init(JavaScriptFunctionToRun);
function JavaScriptFunctionToRun(sender){
//next we tell the page that we need to do something after the HttpRequest is completed
Sys.WebForms.PageRequestManager.getInstance().add_endRequest(EndRequest);
}
function EndRequest(sender, args) {
//here we are telling the page WHAT to do at the end of the request
SetScrollPosition();
}
//and finally(!) the instructions to move the scrollbar to the bottom of the textbox
function SetScrollPosition(){
var textArea = document.getElementById('<%=TestBox1.ClientID%>');
textArea.scrollTop = textArea.scrollHeight;
}
</script>
If you've made it to this point in the post, I hope it was worth the effort. If your process is especially long, you can show the user that the page really is doing something by using the Session, a label and the TimeSpan object in the server-side code.
TaTa for now, and Happy Coding!