Bug Bash: Enabling/Disabling the ASP.NET AJAX Timer using the Control's Client Side API

I know most dev's prefer writing new code over fixing/maintaining the existing stuff.  And most days I feel the same way.  But every once in a while it is kind of fun signing up for that one lingering defect that no one else can seem to fix.  Maybe you know the one - its not reproducible, only occurs in production and brings the system to its knee's?  Sound familiar?

Well I thought I might try something new and blog about some of the juicy bugs we have found hanging out in our app.  This might turn into a blog series or something, or maybe not.  Who knows.

The Bug

The app I am currently working on has a page that uses ASP.NET AJAX's new Timer control to automatically refresh the page every 30 seconds.  The page is one of those status pages that lets you monitor the progress of some off-line processes.  Under normal usage a user would make a request to run some off-line process and then be taken to this page where they can monitor the progress.  The markup for the Timer looks something like this ...

<asp:Timer ID="timer" runat="server" Interval="30000" OnTick="Timer_Tick" />

When the server side Tick event fires we run a bit of code that gets the latest progress/status of the process and updates the UI.  If the process has completed or failed, the Timer is turned off and the page quits automatically refreshing.

   1:  protected void Timer_Tick(object sender, EventArgs args)
   2:  {
   3:      if (IsProcessComplete())
   4:      {
   5:          //  the work is done, turn off the timer
   6:          ((Timer)sender).Enabled = false;
   7:      }
   8:  }

The content on the page is mostly read-only, but there is also a single INPUT element and a button that performs a postback.  The problem is that sometimes the timer fires and causes the refresh to occur when the user is entering data into the INPUT box.  And of course this can be a little irritating and confusing for the user. 

This was the defect that rolled across my desk yesterday.  So I took a look at the page, reproduced the problem and starting working out how I was going to fix it.   

The Fix

I figured a sensible fix for this bug would be to disable the Timer when the INPUT's client side focus event fires and then re-enable it when blur fires.  I described the solution to a few other dev's and everyone seemed to think it made sense.  So I went off and started in on creating the fix.

The Implementation

I added a pageLoad event handler to my page and attached a bit of JavaScript to my INPUT element's focus and blur events that used the Timer component's client side API to enable and disable the timer.

   1:  function pageLoad(sender, args){
   2:      //  fetch the timer components
   3:      var timer = $find('<%= this.timer.ClientID %>');
   4:      //  fetch the INPUT element
   5:      var textbox = $get('<%= this.textbox.ClientID %>');
   6:      
   7:      $addHandler(textbox, 'focus', Function.createDelegate(this, function(){
   8:         //   disable the Timer so we don't refresh the page
   9:         //   while the user is entering the data
  10:         timer.set_enabled(false); 
  11:      }));
  12:      
  13:      $addHandler(textbox, 'blur', Function.createDelegate(this, function(){
  14:         //   re-enable the Timer
  15:         timer.set_enabled(true); 
  16:      }));                
  17:  }

The Bug in the Fix

And holy crap I was surprised when this didn't fix the bug.  I have worked with some of the other Timer controls before and unless I have misremembered, they all support this kind of thing - just in case you haven't noticed the .Net framework has no shortage of timer controls ;)

And I figured calling set_enabled(false) would do exactly what I want - turn off the timer.  I spent ~10 minutes or so setting breakpoints and making sure my code wasn't faulty before I took a peek at the client side code for the Timer control.  And guess what I found out - the Timer control doesn't check the enabled bit before it postback and raises the server side Tick event.  No wonder my fix wasn't working!

The Fix to the Bug in the Fix

Is anyone else surprised by this?  Well I was.  So I nosed around the Timer's behavior a little bit more and to see if there was anything else here I could use.  And it turns out there is.  After a partial postback completes, the server side portion of the Timer control sends the client side 2 bits of data - a value for the timers interval and a boolean value that is true when the timer should be enabled and false otherwise.  The client side timer component passes these 2 data items to it's _update function, which in turn either enables or disables the timer and updates the interval value.  Here is the code for this function (I added the comments)

   1:  function Sys$UI$_Timer$_update(enabled,interval) {
   2:      //  check to see if the timer is already disabled
   3:      var stopped = !this.get_enabled();
   4:      //  check to see of the interval value has changed
   5:      var intervalChanged = (this.get_interval() !== interval);
   6:      //  if we are not stopped already and either
   7:      //  the interval value has changed or we are now
   8:      //  disabling the timer, stop the timer
   9:      if ((!stopped) && ((!enabled)||(intervalChanged))){
  10:          this._stopTimer();
  11:          stopped = true;
  12:      } 
  13:      //  update the enabled bit and interval
  14:      this.set_enabled(enabled);
  15:      this.set_interval(interval);
  16:      //  if the timer needs to be enabled and it is
  17:      //  currently stopped, start the timer
  18:      if ((this.get_enabled()) && (stopped)){
  19:          this._startTimer();
  20:      }
  21:  }

So I decided to update my focus and blur event handlers to call this method to enable/disable the Timer.  And guess what - now my page functions exactly how I want it to.  But of course there are few things that are untidy about this approach:

  1. The Timer's _update function is not meant to be invoked like this.  Typically the '_' prefix indicates the member is non-public.  I suppose this is a bit like using .Net's reflection to invoke a private method on some framework class - probably not a great idea.
  2. This implementation now allows me to interact with the Timer's client side behavior, but when the page postsback the new enabled and interval values that were set on the client are not sent back to the server.  And when the page reloads either from a full or partial postback these values will go back to what the server thinks they should be - essentially overwriting any of the changes you may have made.

But I can live with both of these items.  I am betting against Microsoft changing the _update function (if they do, maybe they will support this scenario) and I don't need the values I set on the client sent back to the server.  All I want is to be able to control when the Timer fires, and this approach does it.

   1:  function pageLoad(sender, args){
   2:      //  fetch the timer components
   3:      var timer = $find('<%= this.timer.ClientID %>');
   4:      //  fetch the INPUT element
   5:      var textbox = $get('<%= this.textbox.ClientID %>');
   6:      
   7:      $addHandler(textbox, 'focus', Function.createDelegate(this, function(){
   8:         //   disable the Timer so we don't refresh the page
   9:         //   while the user is entering the data
  10:         timer._update(false, timer.get_interval()); 
  11:      }));
  12:      
  13:      $addHandler(textbox, 'blur', Function.createDelegate(this, function(){
  14:         //   re-enable the Timer
  15:         timer._update(true, timer.get_interval()); 
  16:      }));                
  17:  }
 
 

Bug Fixed.  Enjoy!


TrackBack

TrackBack URL for this entry:
http://mattberseth.com/blog-mt/mt-tb.fcgi/120

Listed below are links to weblogs that reference Bug Bash: Enabling/Disabling the ASP.NET AJAX Timer using the Control's Client Side API:

» ASP.NET 2.0 AJAX Timer Hacks! from Moses's Blog
ASP.NET 2.0 AJAX Timer Hacks! How to Pause ASP.NET AJAX Timer [Read More]

» Popup Master-Detail using GridView, DetailsView from Moses's Blog
Popup Master-Detail using GridView, DetailsView and JQuery with jqModal [Read More]

Comments


Posted by: crino on May 8, 2008 12:00 AM

hi matt,
take a look to my TimerExtender: http://www.codeplex.com/MyAjaxControlToolkit
Could be a good solution for you ;)

cheers

Cool Matt, I faced this issue last year. And I figured about _stopTimer and _startTimer private client side methods But it is great to know about the _update method. Good tip I guess this is much better than just using _stopTimer and _startTimer methods.
Thank you

Posted by: Michael McGuire on May 8, 2008 12:00 AM

Hi Matt,

I posted a similiar question over on this posting (http://mattberseth.com/blog/2008/05/bulk_inserting_data_with_the_l.html) about your use of Function.createDelegate(). Im confused as to the purpose of creating the delegate, when you arent using the delegate at all for disposal in your code. Right now you have:

$addHandler(textbox, focus, Function.createDelegate(this, function(){ timer.set_enabled(false); }));

When it seems like you could get away with:

$addHandler(textbox, focus, function(){ timer.set_enabled(false); });

Is there a reason you use the overhead of a delegate, when you arent using it anywhere?

Hey Matt,

Great solution! Im still trying to "reintroduce" myself to Javascript via all the "AJAXifications" you can do to tweak your own stuff.

What happens if you start to enter data into that input box, then decide to wait, figuring that the refresh will occur any second now and you may not need to submit your input afterall? You just sit there, not unfocusing the textbox. Does the timer stay permanently disabled at this point until you click somewhere else in the page?

What about having a TextChanged event simply reset the timer so that you have an additional 30 seconds. That way you can start typing, decide to wait, and still have things refresh after 30 seconds. But at any point in the game, you can start typing again and have your time back?

Just a thought. Thanks for such a great blog!!!

Posted by: Kirk on May 8, 2008 12:00 AM

Wow. Talk about timeliness. You posted this last night and today, just this very minute I ran into the same issue. In my case I wanted to disable the timer while a modal popup dialog was activated.

I think Im gonna start using Google as my *second* source of .NET isues/fixes, with quality content like this. :) Thanks for this info, as well as all the other great stuff on your blog!

@crino -
Thanks for the link. I will check it out.

@Muhammad Mosa -
I wish I would have come across your page when I was debugging. I swear I thought I was going crazy when set_enabled(false); wasnt working ...

@Michael McGuire -
I often get pretty behind on comments so sorry about not responding to your previous one. I have got into the habbit of using Function.createDelegate (even when it isnt needed). You are right, for this example, and probably more than a few others this is just extra work. Thanks for pointing it out.

@Sean Patterson -
Good suggestion. Yep, with the changes I made to the page, the refresh will not occur as long as the textbox has focus. I think I like your approach better though. Its kind of a best of both worlds - reset the timer after every keystroke, and if 30 secs pass without a keystroke go ahead and refresh the page. Thanks!

@Kirk -
Yea. It seems like kindof standard timer behavior right? I was so surprised by the behavior I figured other people would run into it too.

Posted by: Deepak Chawla on May 12, 2008 12:00 AM

I have not tried the timer control but implement the same open ended continuous talk back to the server in a different way.

I added a call back to the web service as the last line of OnComplete, OnError and OnTimeout functions of the call.

Simple

Posted by: Bassel on May 12, 2008 12:00 AM

what about ?
timer._startTimer();
timer._stopTimer();

?

Posted by: Bassel on May 12, 2008 12:00 AM

Hi,
The Timer stopped calling its server side Tick method

my timer is inisde an updatepanel and the Tick method should update a Label with some calculated figures.
I Enabled the Timer from clinet-side API and i noticed that the timer starts working because the progressbar started to show on every 10 seconds (based on the timer interval) but Lable is not getting updated.

Posted by: Jonx on June 2, 2008 12:00 AM

Thank you Matt for the article. Once aain exactly when I needed it ;)

Just for people wondering how to add a pageLoad event handler to the page like I did, there is nothing to do except pasting the code to the page script, pageLoad is a special ajax keyword. I didnt know ;)

here are more details:http://www.asp.net/ajax/documentation/live/overview/AJAXClientEvents.aspx

Thanks Matt I need this solution very clear too.

Hi. I just had the same problem, but I simply could NOT stop the timer.
I used it to countdown the remaining time for a student to finish his test (minutes:seconds).

If he simply held the cursor in the answer textbox, the time wouldn't run ... cheater :)

I fixed the update problem by simply moving the answer Textbox OUT of the UpdatePanel and any other UpdatePanels, so the refresh didn't touch it. :)

Post a comment

(If you haven't left a comment here before, you may need to be approved by the site owner before your comment will appear. Until then, it won't appear on the entry. Thanks for waiting.)

Consulting Services

Yep - I also offer consulting services. And heck, I'll do just about anything. If you enjoy my blog just drop me an email describing the work you need done.

Recent Comments

  • empee wrote: Hi. I just had the same problem, but I simply could NOT stop the timer. I used it to countdown the r...
  • Alquma wrote: Thanks Matt I need this solution very clear too. ...
  • Jonx wrote: Thank you Matt for the article. Once aain exactly when I needed it ;) Just for people wondering how...
  • Deepak Chawla wrote: I have not tried the timer control but implement the same open ended continuous talk back to the ser...
  • Bassel wrote: what about ? timer._startTimer(); timer._stopTimer(); ? ...
  • Bassel wrote: Hi, The Timer stopped calling its server side Tick method my timer is inisde an updatepanel and the...
  • crino wrote: hi matt, take a look to my TimerExtender: <a href="http://www.codeplex.com/MyAjaxControlToolkit" rel...
  • Muhammad Mosa wrote: Cool Matt, I faced this issue last year. And I figured about _stopTimer and _startTimer private clie...