Wednesday, July 11, 2007

An IE OnChange Textbox AutoPostback Twice RadAjaxManager Workaround!

I cannot personally justify the difficulty of finding this workaround, but I know someone will find it valuable. An ASP.NET box with AutoPostback="true" on my page was causing an ajax postback twice through the RadAjaxManager, and it took me a long time to figure out why. So, I post my findings here in the hope that Google will help another frustrated developer find the solution they need.



First, a description of the difficulty I was having in my ASP.NET application. I'd created several textbox controls that I wanted to have AutoPostback. Then, using the RadAjaxManager, I wanted to have the textbox controls perform their postback using an ajax request and update another portion of the screen where the results of the request would be displayed. When the request completed, I wanted the textbox control that caused the postback to have an empty value.



So, I set the AutoPostback property of the textboxes to True, created AjaxSettings to have the results panel and the search panel (where the textboxes live) update when one of the textboxes caused a postback. I entered some text into one of the ajaxified textboxes, hit ENTER, then, voilĂ , it performed an ajax postback updating the panels I specified...



Then, it did it again.



The ajax postback was performed twice. Every time. Great... So, I set about to determine why this was occurring. Along the way I discovered a few things about Internet Explorer, the ASP.NET JavaScript postback model, and how the Telerik RadAjaxManager works with them.



If you created a little test page that fires an alert in a textbox's onchange event, you'll notice a difference in behavior between IE and Firefox. For example, put this element into an otherwise blank page.


<input type="text" onchange="alert('fired');" />

In Firefox, entering some text and pressing the ENTER key will result in the alert being shown; in IE, however, this is not the case. IE doesn't fire the onchange event until the textbox loses focus.



This creates a browser incompatibility issue for libraries. Pertinently, consider setting the AutoPostback property of an ASP.NET textbox to true; when the textbox is changed in the browser, the client should perform a postback automatically. Obviously, the ASP.NET developer's must have worked around this behavioral discrepancy. Indeed they did:


<input type="text"
onkeypress="if (WebForm_TextBoxKeyHandler(event) == false) return false;"
onchange="javascript:setTimeout('__doPostBack(\'textbox1\',\'\')', 0)"
... />
Please note that some rendered properties of the textbox have been elided for clarity.

What is this WebForm_TextBoxKeyHandler? Some clever use of our Javascript debugging tools reveals the answer: it fires the onchange event for the textbox when the ENTER key is pressed and returns false.



That the WebForm_TextBoxKeyHandler returns false when the ENTER key is pressed (after firing onchange event handler) is significant. It means that the browser will cancel the keypress event. Thus, Firefox will not trigger the change event for the textbox.



We now have a few clues to the mystery of why our page performs the postback twice when we change our textbox:


  1. When we hit the ENTER key in both browsers, the onkeypress event handler executes the onchange handler. We changed this behavior.

  2. When we navigate out of the textbox, the change event is fired. This behavior remains unchanged.


Please note the added emphasis in the above two items. The ENTER key causes the onchange handler to be executed, while navigating out of the textbox causes the change event to be fired which executes the onchange event handler. I am not making a semantic distinction here; these are two entirely different behaviors.



Notice there isn't a problem so far, however. No matter how the onchange event handler gets called, it calls __dopostback which is going to cause our page to go away. The problem only presents itself when the page doesn't go away: when we peform AJAX postbacks.



With an AJAX postback (i.e. after we wire up the controls using the RadAjaxManager), the textbox that caused the postback stays in the page, and when we navigate out of it the change event is going to be fired. Here's what's happening:


  1. Navigate into textbox--current value is stored as original value.

  2. Enter text and press ENTER.

  3. onchange event handler is executed (via the onkeypress event handler)

  4. AJAX postback occurs

  5. navigate out textbox (e.g. loading screen displayed or user-initiated)

  6. change event is triggered

  7. onchange event handler is executed by change event

  8. AJAX postback occurs




The two postbacks can seem to occur concurrently in Firefox if you cause the textbox to lose focus when performing the postback, e.g. if you displayed the loading panel over it. The postbacks always occur serially in IE in the same situation; because, although the requests are both queued up by simultaneously by the scripting engine just as in Firefox, AJAX in IE relies on the a single-threaded COM component (XMLHttpRequest) to communicate with the server. So, IE can only execute one AJAX request at a time in this situation.



We have a problem here, gentle reader. We do not own any of the code in the scenario enumerated. How should we go about creating a solution, then? One's first instinct might be to clear the textbox in the onblur event handler. Unfortunately, browser divergences strike again. Whereas Firefox fires the user-supplied onblur event handler before checking if the textbox has changed, IE checks the change first.



What about clearing the textbox in the onchange event handler? This would only work if:


  1. we could ensure that the textbox would not lose focus before our code was run, and

  2. we clear the textbox only after all other onchange code has run to ensure that we actually send a value!


Thankfully, there is a solution.



The RadAjaxManager supplies a set of ClientEvents that we can use to get some of our code executed in the midst of its ajax postback. We will utilize the OnRequestSent client event of the manager to clear the value of the textbox. In the function assigned to the OnRequestSent event, we place the following code:


args.EventTargetElement.value = "";



Keep in mind that this event gets fired for every ajaxified control associated with your RadAjaxManager, so you may find it necessary to qualify the aforementioned line of code to only clear the appropriate textbox controls.



I sincerely hope this helps others.