1

TL;DR: If the Edit button is clicked without a ListBox selection, triggering a validation failure, why does the Delete link need to be clicked twice before the browser responds?

Using the source code below for a very simple menu, I have three links:

  1. New - does not perform validation and redirects to the "create" form
  2. Edit - checks that a ListItem is selected, and if so, redirects to the "edit" form with an ID value supplied as a parameter
  3. Delete - redirects to the "delete" page, regardless of whether a selection is made or not

I have noticed a strange behaviour, and have reproduce in this simple one-page solution (download link from OneDrive):

  1. Load page
  2. Click on Edit without making a selection from the ListBox (validation error appears)
  3. Click on Delete - nothing happens - why?
  4. Click on Delete - server-side redirect occurs successfully, but this should happen on step 3 above

Here is what happens in my browser (tested in Chrome, FF and Edge) - notice that two clicks are required on the Delete link: enter image description here

Update

The problem appears to be within ASP.Net's form submit code:

function __doPostBack(eventTarget, eventArgument) {
    if (!theForm.onsubmit || (theForm.onsubmit() != false)) {

    // On first click - this logic path is never reach, but on the second click, it is reached

        theForm.__EVENTTARGET.value = eventTarget;
        theForm.__EVENTARGUMENT.value = eventArgument;
        theForm.submit();
    }
}

I am trying to figure out why the if statement returns false, after I have manually called Page_ClientValidate().

Code

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="WebForm1.aspx.cs" Inherits="WebApplication1.WebForm1" %>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>Test Page</title>
    <script>
        function RedirectToPageWithSelectedId() {
            if (Page_ClientValidate("mustSelect")) { // Asp.Net client-side validation, passing ValidationGroup as parameter
                var li = $("#<%=ListBox1.ClientID%> option:selected");
                var i = li.val();
                if (parseInt(i) != NaN) {
                    window.location.href = "/edit/" + i;
                }
            }
        }
    </script>
</head>
<body>
    <form id="form1" runat="server">
        <div>
            <p>
                <asp:ListBox ID="ListBox1" runat="server" />
                <asp:RequiredFieldValidator ID="Rfv1" runat="server" Text="Selection required" ValidationGroup="mustSelect" ControlToValidate="ListBox1" Display="Dynamic" />
            </p>
            <ul>
                <li>
                    <asp:HyperLink ID="lnk_new" runat="server" Text="New" NavigateUrl="~/new" />
                </li>
                <li>
                    <asp:HyperLink ID="lnk_edit" runat="server" Text="Edit Selected" onclick="RedirectToPageWithSelectedId(); return false;" href="#" />
                </li>
                <li>
                    <asp:LinkButton ID="lnk_delete" runat="server" Text="Delete" />
                </li>
            </ul>
        </div>
    </form>
</body>
</html>

I can update to provide the page's code-behind if required; just ask.

EvilDr
  • 8,943
  • 14
  • 73
  • 133
  • I'm unable to reproduce the problem. It all works as it should. The Delete click works on the first try. – VDWWD Jan 31 '18 at 12:22
  • Are you definitely clicking "Edit" first, to trigger the validation failure, and *then* Delete while the validation error is visible? I can repro this in FF, Chrome and Edge. – EvilDr Jan 31 '18 at 12:34
  • Ah yes. Then I get the same. But why the weirdly complex validation just to check if the ListBox has a value? You use a RequiredFieldValidator with a validation group that does not correspond with anything else. A link that triggers validation that is meant to prevent a PostBack, not a simple opening of a link. And you are messing around with `Page_ClientValidate`, why? – VDWWD Jan 31 '18 at 15:15
  • I am migrating away from LinkButtons to HyperLinks that perform client-side redirects, to save large postbacks, but wanted to use the convenient plumbing of the existing .Net validation that was already present. I don't want to have to re-write my own custom validation just to check that a ListItem is selected, and am struggling to think of an alternative approach. – EvilDr Jan 31 '18 at 15:18
  • I get that, but the validators are meant to be used in that way. And why not create a DropDownList that triggers a redirect on change instead of a ListBox of which you could select multiple values that will not be used anyway since only the first item will get a redirect – VDWWD Jan 31 '18 at 15:24
  • That's a valid question. It's because on the actual page, there are around 20 links, some of which require an item selection, and some that don't. Also the client preference is to see many items in a larger listbox. I guess its looking like I'll have to roll my own validation, unless you can inspire me otherwise? :-/ – EvilDr Jan 31 '18 at 15:27

2 Answers2

3

After a ton of investigating with Chrome's debugger, I found the following logic and workaround. Please let me know if I am mistaken or if there is a better way to interact with ASP.Net's validation controls on the client-side?

When a LinkButton is clicked, ASP.Net works through it's validation logic, even if that control is not causing validation. This was the first thing that surprised me. The form is not actually submitted until the validation logic has been processed.

ASP.Net webforms client-side validation

During validation, there are two page-level variables that get set:

Page_IsValid

This is an overall boolean value that dictates that all validators are valid.

Page_BlockSubmit

This generally holds the opposite value of Page_IsValid, and prevents the form submitting if true.

The problem in the code

When my Edit link is clicked, my code fired client-side validation for a specific validation group:

Page_ClientValidate("mustSelect");

During that method, Page_IsValid becomes false, and therefore Page_BlockSubmit becomes true. This prevents the form doing anything after the validation errors are shown, and is expected.

The problem arises with the LinkButton's click event. When pressed, regardless of whether that LinkButton is performing validation, the following JavaScript is executed:

function __doPostBack(eventTarget, eventArgument) {
    if (!theForm.onsubmit || (theForm.onsubmit() != false)) {

        // This is never reached if Page_BlockSubmit = true

        theForm.__EVENTTARGET.value = eventTarget;
        theForm.__EVENTARGUMENT.value = eventArgument;
        theForm.submit();
    }
}

The kicker is that the previous validation results are still held in the browser, so Page_BlockSubmit is still true. This causes the above method to jump over the inner code that submits the form, meaning the form never gets submitted.

Strangely, after the method completes, both Page_IsValid and Page_BlockSubmit are reset to their default values. This is why the page submits on the second Edit click.

Workaround (or dirty hack?)

This feels like a complete hack, but by forcing the value of Page_BlockSubmit to be false when the LinkButton is clicked, everything works as expected, and there's no need to manually start resetting validation.isvalid properties, or hiding messages. All other validation groups/logic continue to work as normal, but the Edit link now works as the user would expect:

<asp:LinkButton ID="lnk_delete" runat="server" Text="Delete" OnClientClick="Page_BlockSubmit = false;" />

Again, I have no idea if that unhinges any workings of ASP.Net, and would appreciate any input.

EvilDr
  • 8,943
  • 14
  • 73
  • 133
1

I would do something like this. Give the <div> containing the element a class name and bind the click of lnk_edit also by class. Now you can find if an element is selected without having to use ClientID. This also works with as many myContainer copies as you want without having to add more jQuery code.

<div class="myContainer">
    <p>
        <asp:ListBox ID="ListBox1" runat="server" />
    </p>
    <ul>
        <li>
            <asp:HyperLink ID="lnk_new" runat="server" Text="New" NavigateUrl="~/new" />
        </li>
        <li>
            <asp:HyperLink ID="lnk_edit" runat="server" Text="Edit Selected" href="#" CssClass="myEditLink" />
            <span class="myEditLinkWarning"></span>
        </li>
        <li>
            <asp:LinkButton ID="lnk_delete" runat="server" Text="Delete" />
        </li>
    </ul>
</div>

<script type="text/javascript">
    $('.myContainer .myEditLink').click(function () {
        $(this).closest('div').find('select option').each(function () {
            if ($(this).prop('selected')) {
                window.location.href = "/edit/" + $(this).val();
            }
        });
        $(this).closest('div').find('.myEditLinkWarning').html('Please select');
    });
</script>
VDWWD
  • 35,079
  • 22
  • 62
  • 79
  • Thanks for this. This is what I will likely end up doing, although I did find a workaround without having to roll my own validation. There's a lot of documentation online stating that client-side validation using the built-in validators is fine, so I don't know how much of a "hack" my workaround is... – EvilDr Feb 01 '18 at 13:23