smarx.com

developing for the new web

The case of the RadioButtonList half-trigger

Sorry for the cheesy title, but this was such a mysterious bug to track down that it seems appropriate to give it a dramatic title. Consider the following code:

<%@ Page Language="C#" %> 
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 
<html xmlns="http://www.w3.org/1999/xhtml" > 
<body> 
    <form id="form1" runat="server"> 
    <asp:ScriptManager ID="sm" runat="server" /> 
    <asp:UpdatePanel ID="up" runat="server" UpdateMode="Conditional"> 
        <ContentTemplate> 
            <asp:RadioButtonList ID="rbl" runat="server" AutoPostBack="true"> 
                <asp:ListItem Value="0" Selected="true">Zero</asp:ListItem> 
                <asp:ListItem Value="1">One</asp:ListItem> 
            </asp:RadioButtonList> 
            <%= DateTime.Now %> 
        </ContentTemplate> 
    </asp:UpdatePanel> 
    </form> 
</body> 
</html>

Nothing very scary looking in there, just a RadioButtonList with a couple options, and my old favorite DateTime.Now to tell me when the UpdatePanel was last updated. This code does exactly what you think it should: when the page loads, "Zero" is selected, and when you switch to "One," the timestamp refreshes. You can then switch back to "Zero" (and back to "One" again, etc.), and each time you change the radio button selection, the timestamp gets updated.

That code is a little inefficient. The RadioButtonList doesn't change during the async postback, so we don't really need it to be refreshed as part of the UpdatePanel. Let's move it outside the UpdatePanel and use a trigger instead. Here's the new code:

<%@ Page Language="C#" %> 
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 
<html xmlns="http://www.w3.org/1999/xhtml" > 
<body> 
    <form id="form1" runat="server"> 
    <asp:ScriptManager ID="sm" runat="server" /> 
    <asp:RadioButtonList ID="rbl" runat="server" AutoPostBack="true"> 
        <asp:ListItem Value="0" Selected="true">Zero</asp:ListItem> 
        <asp:ListItem Value="1">One</asp:ListItem> 
    </asp:RadioButtonList> 
    <asp:UpdatePanel ID="up" runat="server" UpdateMode="Conditional"> 
        <Triggers> 
            <asp:AsyncPostBackTrigger ControlID="rbl" /> 
        </Triggers> 
        <ContentTemplate> 
            <%= DateTime.Now %> 
        </ContentTemplate> 
    </asp:UpdatePanel> 
    </form> 
</body> 
</html>

If you load this page, you'll see the same thing as before. "Zero" is selected, and if you click "One," the timestamp updates to the current time, just as before. Here's where it gets mysterious, though. If you click "Zero" again, nothing happens! If, after that, you click "One" again, the timestamp will update as expected. But click back to "Zero," and it again does nothing. Keep clicking back and forth, and you'll find that every time you click "One," the UpdatePanel is refreshing, but when you click "Zero," it never does. Hence the "half-trigger" in the title of this post.

Well, we have our mystery. Let's get a-sleuthin'.

Step one is to figure out where things are going wrong. What we expect to happen is:

  1. On the client, I click "Zero," which fires off an async postback trigger.
  2. On the server, the page does a postback, which updates the timestamp, and it sends back the contents of our UpdatePanel.
  3. On the client, the UpdatePanel contents get replaced by the response from the server.

I was working in parallel on the real-world version of this code with Rupesh Patric, one of our support engineers. He and I attacked the problem in two different ways but came to the same conclusion. He set a breakpoint on the server in the event handler for the RadioButtonList's SelectedIndexChanged event. (I've left that event out here to make the code simpler.) He found that his breakpoint was only being hit when he clicked on "One." I attacked the problem from the client instead and fired up Nikhil's Web Development Helper to see what the HTTP traffic was like. There I found that there was an async postback any time I clicked on "One" (as is expected), but there was no traffic at all when I clicked "Zero."

Either approach is a fine way to determine where the unexpected behavior is occurring. In this case, we learned that the problem was that clicking on the button wasn't causing an async postback.

To me, that meant it was time to look at the actual HTML source of the page in the browser to see if something looked amiss, and that's where I found the problem. Here's what the HTML looks like for the RadioButtonList:

<table id="rbl" border="0"> 
    <tr> 
        <td><input id="rbl_0" type="radio" name="rbl" value="0" checked="checked" /><label for="rbl_0">Zero</label></td> 
    </tr> 
    <tr> 
        <td><input id="rbl_1" type="radio" name="rbl" value="1" onclick="javascript:setTimeout('__doPostBack(\'rbl$1\',\'\')', 0)" /><label for="rbl_1">One</label></td> 
    </tr> 
</table>

Now the difference becomes fairly obvious. Because the first radio button is checked, it doesn't get the onclick="..." script, so it doesn't call __doPostBack() and doesn't cause an async postback. When it was inside the UpdatePanel, it was getting replaced each time with the opposite code ("Zero" has the onclick="..." and "One" doesn't). Outside the UpdatePanel, it's never getting replaced. The bottom line is that when I said above, "The RadioButtonList doesn't change during the async postback," I was actually wrong. The RadioButtonList does change when the selection changes.

To hit this issue, your code has to meet three conditions:

  1. A RadioButtonList is an async postback control. (Regular postbacks obviously update the control.)
  2. It's not in an UpdatePanel that gets updated during the async postback it causes. (It's either not inside an UpdatePanel or that UpdatePanel has ChildrenAsTriggers="false".)
  3. One of the ListItems is set with Selected="true". (Otherwise all the radio buttons get the onclick="..." script.)

The really simple fix? Just put your RadioButtonList inside an UpdatePanel (either the one it's triggering or one of its own).

Mystery solved! It all looks so simple once it's been debugged...

Rupesh Patric said:
Thanks a lot Steve with all the help on this.. That too, you had to stay up so late to work on this..
Long live AJAX!!!
said:
said:
said:
said:
Sahand said:
salam
Tobbe said:
Hi, I just started using aspnet ajax and im wondering if you could explain why the following occurs.

I have two radio button lists within the same update panel. whenever i change the selected item on one of the lists both event handlers are triggered on the server side. Even though only one of them actually changed.

I tried changing my code to reflect the solution to your problem but it had no effect on this issue.

I tried removing all code in the two selectedindexchanged handlers (since they updated the index of each others lists) but that did not make a difference.

For now I'll just add the lists to separate update panels and use triggers whenever I need to but I really hate workarounds and would appreciate any help I can get.

Kenneth Siewers said:
Well, that does not resolve the issue with two different radiobuttons within the same GroupName (not a RadioButtonList).
The problem I'm facing is the fact that a RadioButton, which state is Checked on render, never gets the OnClick client side event attached (much as yours).
I have two table cells, with a RadioButton in each.
Since I can't span a RadioButtonList across multiple cells, I have to use single RadioButtons, which still have the problem you describe.

I'm still looking into how to solve the problem, but your solution was unfortunately not the one :(
Kenneth Siewers said:
Okay, I think I have found a solution to the problem.

If you set your RadioButton to "Checked = true" you won't get a trigger for the control at runtime.
You can, however, add an InputAttribute at runtime like so:

rbtnRadioButton.InputAttributes.Add("checked", "checked");

This will set the RadioButton checked state to checked, but does not instruct the ASP.NET parser to ignore the OnCheckedChanged event, hence adding the client side script to the control.

Very nice!
Mun said:
Thanks for this. I'm getting some weird behaviour - wrapping the radiobuttonlist in an updatepanel causes the panel to not retain it's status.

I've got two radio buttons - yes, no. The no listitem has selected=true. When clicking 'yes', the ajax postback happens, and 'yes' is selected. But then, when 'no' is clicked again, the postback happens and re-selects yes...
Mun said:
Rightio, thing I've fixed this.

It was being caused by the form tag not being straight after the body tag in the master page (it was inside a table tag, inside the master page).

Moved it to to the top of the page, so that it was the first tag after body, and that seems to have solved the problem :-)
bucketofsquid said:
The reason that there is no onclick event for a selected radio button is because they are based on the old physical radio buttons. With the old radios these push buttons were generally an either/or situation. Things like on/off or AM/FM. The only way to unselect one was to push the other. If you tried to push in a button that is already pushed in you can only break the radio. You can't un-turn it off with out turning it on.

If you are needing an onclick event for a radio button that is already selected, I would suggest that the problem is your thought process and not the correct, as designed functioning of a radio button. What you need is a reset or clear button to unselect the clicked radio button without selecting the other radio button(s).

Of course, it would be nice if some of you clever types would come up with new types of controls like the AJAX slider control I just ran across. One of the great limiters of innovation is the inability of most people to accept change. My company has had 3 failures in a row of our electronic forms project because the users just can't accept that an electronic form does not need to look exactly like the old paper form it is replacing.

I once had need of a group of radio buttons where clicking one did not automatically deselect all of the others, but instead the group had a rather complex algorithm of interactions. Sometimes up to three could be selected and at others only one. I ended up having to use check boxes and doing a lot of ugly code. A nice poly-select radio button group would be usefull. Something where each radio button could be part of multiple groups.

Anyway, I really don't view this as a bug. It is inconvenient as all get out, but does work like real radio buttons.

Lokesh said:
Thanks..

i was facing that problem with the use of Ajax, because of your article, i have solved it out..

Thanks

Keep in Touch..
Bikos said:
interesting
Socrates said:
Nice!
Kimon said:
Nice!
Lambro said:
Nice
Pericles said:
Nice...
Kyriakos said:
Cool!
Platon said:
Interesting...
Nathanael said:
Nice!
Doxiadis said:
Nice...
Ivan said:
Cool.
Kymon said:
Interesting...
Costas said:
Cool...
Herakles said:
Cool.
Epaminondas said:
Cool!
Kyriacos said:
Cool.
Xenophon said:
Cool...
Eleftherios said:
Cool...
Dmitris said:
Cool!
Anastassios said:
Cool...
Metrophanes said:
Cool.
Odysseas said:
Cool.
Periklis said:
Interesting...
Photios said:
Cool.
Georgios said:
Cool!
Photios said:
Cool.
Thanasis said:
Sorry :(
Roger Weiss said:
I had the same mysterious problem and after trawling the web for an answer, yours is easiest to understand and solves it nicely. Thanks! I've been trying to convince half my team that server side controls are the way to go, and not builing pages by hand using the old way. Stupid ASP.NET nuances like this really give me an uphill battle for the cause.
Stratis said:
Nice...
Yiannis said:
Cool.
Myron said:
Cool...
Damianos said:
Sorry :(
Pavlos said:
Cool.
Agias said:
Nice...
Costa said:
interesting
Alexandros said:
Sorry :(
Martinos said:
interesting
Andreas said:
Cool...
Theophanis said:
Nice!
Angelos said:
Cool!
Kyriacos said:
Nice
Lambros said:
Cool.
Isaakios said:
interesting
Savvas said:
Cool...
Panayotis said:
Cool...
Prokopios said:
interesting
Yanni said:
Cool!
Giorgos said:
Nice
Antonis said:
Nice...
Adamantios said:
Cool.
Theologos said:
Cool.
Doxiadis said:
Nice!
Nikodemos said:
Cool...
Stamatis said:
Nice...
Martinos said:
Nice!
srice said:
Thank you. I determined rather quickly that the original status of the selected radio button hadn't changed (most probably because I had 3 options) but I didn't know why. You saved me hours of frustration
Thomas said:
Silly bug, but thank you for the solution. Saved me a lot of google time!
kif said:
gr8 man. thanks a lot.
chris said:
Awesome. I spent so much time trying to fix this - even searching for a solution took forever! Thankfully you experienced it first and posted it!
Marcello said:
That helped a LOT. Although I had this problem with RadioButton's and not RadioButtonList.

A hint for the guys/gals in the same swamp: set the AutoPostBack parameter to true in the RadioButtons. This combined with Steve's solution got me out of trouble.


Cheers!
said:
said:
gn said:
I also had this problem with RadioButtons and discovered the problem but did not like the solution of having to put my RadioButtons into an UpdatePanel. But after reading Kenneth Siewers comment about adding the InputAttributes when you want a default button set it gave me the idea to set my RadioButton.Checked = false and then add RadioButton.InputAttributes.Add("checked", "checked"); in the RadioButton_CheckedChanged event code. And this works perfectly for both the full postback and the async postback without adding the RadioButton to an UpdatePanel.
said:
Alex K said:
Thanks!!! Saved me a bunch of time =)
DotNetNuke modules said:
Great post. I already figured putting the control in the same updatepanel solved the issue but didn´t think just putting it in it´s own panel would also do the trick. Thanks!
kurt schroeder said:
Appreciated this was i bummer. I'm just glad there was a quick work around.
IOU1
KES
Tony said:
Thanks a lot, you saved us a great deal of time!
Diego said:
Nice!
Greg said:
There is a better solution that works for RadioButtonList. Look at 3-18-08 post from rwoodslap on forms.asp.net in the article http://forums.asp.net/t/1068927.aspx?PageIndex=2 ("RadioButton CheckedChanged not firing back with AJAX v1.0?").

You should also insert the "setTimeout('(postback-script)',0)" into the string. But that's trivial.

(Snipped)
1) Set the radio button ASP.NET to Checked="true"
2) Add the following code to the Page_Load

if (!Page.IsPostBack)
rdbDomestic.Attributes.Add("onclick", Page.ClientScript.GetPostBackClientHyperlink(rdbDomestic, string.Empty));
Nora said:
Fantastic. Such a simple solution for a problem that's been a real pain. Thank you!
Echilon said:
Cheers, I thought I was going to have to write some nasty clientside code. =)

Add your own comment

Your name:
Your website/mailto:
Your comment: