Search code examples
asp.netvalidationrequiredfieldvalidator

RequiredFieldValidator focusing the last radiobutton on a RadioButtonList


I have a radio button list in asp.net like

 <asp:RadioButtonList ID="rblChoose" runat="server" RepeatDirection="Horizontal" 
 RepeatLayout="Flow">
   <asp:ListItem Text="a" Value="a" />
   <asp:ListItem Text="b" Value="b" />
</asp:RadioButtonList>
<asp:RequiredFieldValidator ID="rfvChoose" runat="server" 
ControlToValidate="rblChoose" ErrorMessage="Choose registration option." 
ForeColor="red" SetFocusOnError="true"></asp:RequiredFieldValidator>

When the RequiredFieldValidator shows the client message on error it is focusing the last RadioButton in the list. Why is it setting the focus on the last RadioButton in list. I would like to focus on the first RadioButton on error.


Solution

  • The reason why this is happening as the generated HTML for the radio button will look similar to this. (Note the names \ ids will be changed to suit the application and heirchy).

    <span id="MainContent_rblChoose"><span name="rblChoosea">
          <input id="MainContent_rblChoose_0" type="radio" name="ctl00$MainContent$rblChoose" value="a">
          <label for="MainContent_rblChoose_0">a</label></span><span name="rblChooseb">
          <input id="MainContent_rblChoose_1" type="radio" name="ctl00$MainContent$rblChoose" value="b">
          <label for="MainContent_rblChoose_1">b</label>
    </span>
    

    Now what is important is if you look at the name attribute. The names are the same as (for radiobuttons) so they correctly toggle between each other. This also sets which value is posted in the form event.

    Now the default validator (if you dig through the MS javascript) eventually calls the following snippet.

    var inputElements = ctrl.getElementsByTagName("input");
    var lastInputElement  = inputElements[inputElements.length -1];
    if (lastInputElement != null) {
            ctrl = lastInputElement;
    }
    

    If you review the code above you will notice that it actually gets the lastInputElement from the array of elements that match the element name and have a tag name of input. Therefore when the code executes to focus the ctrl it is focusing on the last element (as you see in your validation).

    Now we know thats the default behavior, what we also need to know is how do we overcome this problem. Well thats where you will have to stay away from the RequiredFieldValidator control and write your own CustomValidator with a custom javascript function. Now this is quite simple and is only really valid for this case but can be done with a few lines of code.

    First off you define your CustomValidator as such (example only).

    <asp:CustomValidator runat="server" 
                         ID="rfvChoose" 
                         ErrorMessage="Choose registration option."
                         EnableClientScript="true" 
                         ForeColor="Red" 
                         ClientValidationFunction="rfvChooseValidator" />
    

    Noticing another property enabled of ClientValidationFunction this is a javascript function that will set the IsValid property to true \ false based on a set of conditions. The javascript is then quite easy to implement. Note in my example I have added special events to hide the validation message after a radio button is selected. I put alot of comments in the javascript so you can understand exactly what each stage does.

    <script type="text/javascript">
        //function called when the rfvChoose validator is called
        function rfvChooseValidator(sender, args) {
            //set the object id for the radio button list
            var objID = '#<%= rblChoose.ClientID %>';
    
            //set the is valid flag by evaluating if any radio buttons in the radiobutton list are selected (using the psudo selector :checked)
            args.IsValid = $(objID + ' input[type="radio"]:checked').size() > 0;
    
            if (args.IsValid) {
                //if the validation is succesful remove the eventValidation custom click handler
                $(objID + ' input[type="radio"]').off('click.eventValidator');
    
                //hide the validation message
                $('#<%= rfvChoose.ClientID %>').css('visibility', 'hidden');
            }
            else {
                //focus the first radiobutton in the list
                $(objID + ' input[type="radio"]:first').focus();
    
                //if the arguments are not valid set an event handler to trigger when any of the radio buttons are selected
                // the custom click event handler uses the .eventValidator namespace so we can on\off just the click handler for event validation
                // thus leaving all other click events enabled
                $(objID + ' input[type="radio"]').off('click.eventValidator')
                    .on('click.eventValidator', function () {
                        //this simply recalls the validation function with a null sender and a blank object for the args 
                        // note the blank object is set using {} as opposed to null so in the callback we can set a property
                        // of this blank object. Passing null will cause an exception when trying to set a value.
                        rfvChooseValidator(null, {});
                    });
            }
        }
    </script>
    

    Simply put the javascript function above will be executed every time the CustomValidator client validation events are raised.

    Now I realize in the bounty statement that you only want to use a RequiredFieldValidator however this wont be possible if you would like to focus the first control

    UPDATE

    If you wish to change the DisplayType to Dynamic you must change the line

    $('#<%= rfvChoose.ClientID %>').css('visibility', 'hidden');

    TO

    $('#<%= rfvChoose.ClientID %>').css('display', 'none');

    Or you can replace the code with the following snippet to auto-detect the display type and set the display\ visibility css accordingly.

    //identify the display type
    var displayType = $('#<%= rfvChoose.ClientID %>').data('val-display');
    if (displayType === undefined || displayType == null || displayType == 'Static')
        displayType = { 'visibility': 'hidden' };
    else
        displayType = { 'display': 'none' };
    
    //hide the validation message
    $('#<%= rfvChoose.ClientID %>').css(displayType);