3

In a simple ASP page, TextBox AutoPostBack events will prevent Button click events (except where button is tapped very quickly) and AutoPostBack events for other controls (like ListBox).

There's a similar question here, but I wasn't happy with being forced to use client side or AJAX solutions: Have to click button twice in asp.net (after autopostback textbox)

Example ASPX page:

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="temp.aspx.cs" Inherits="temp" %>
    <!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" >
    <head runat="server">
        <title>Untitled Page</title>
    </head>
    <body>
        <form id="form1" runat="server">
        <div>
            <asp:TextBox ID="TextBox1" runat="server" AutoPostBack="True" OnTextChanged="PostBack"></asp:TextBox>
            <asp:Button ID="Button1" runat="server" OnClick="PostBack" Text="Button" /><br />
            <asp:ListBox ID="ListBox1" runat="server" AutoPostBack="True" OnSelectedIndexChanged="PostBack">
                <asp:ListItem>value1</asp:ListItem>
                <asp:ListItem>value2</asp:ListItem>
            </asp:ListBox><br />
            <br />
            Events Fired:<br />
            <asp:TextBox ID="TextBox2" runat="server" Height="159px" TextMode="MultiLine" Width="338px"></asp:TextBox></div>
        </form>
    </body>
    </html>

C# code behind:

public partial class temp : System.Web.UI.Page
{
    protected void PostBack(object sender, System.EventArgs e)
    {
        this.TextBox2.Text += string.Format("PostBack for - {0}\n", ((System.Web.UI.Control)sender).ID);
    }
}

I've been able to partially solve this problem for buttons by using mousedown instead of click events to submit the form (I also blocked extra AutoPostBack events client-side and handled any extra field changes during button click events server side)

However, this means my buttons aren't quite behaving in the standard (click on release) way.

Is there a better solution to this problem that doesn't require trying to do everything in javascript client-side? (I'm writing a lot of code that reads server data during these postbacks, so javascript isn't an ideal solution.)

I'm also trying to avoid switching to an AJAX library for these pages since every new library I add has to go through security auditing etc.

Note: I'm currently working with ASP.Net 2.0/VS 2005, but if this type of problem is fixed in a later release that would be a compelling argument to upgrade. (As far as I understand it, the same problem seems to happen in ASP.Net 4/VS 2010)

Community
  • 1
  • 1
  • Are you purposely trying to postback automatically when any input changes? This seems archaic? If you want the button to be the method of submitting the form take out the autopostback properties on the input controls. – KP. Jul 08 '11 at 19:20
  • 1
    Perhaps describing your desired functionality would help here. What are you trying to accomplish with all of the autopostbacks and manipulating the normal clientside functionality? – KP. Jul 08 '11 at 19:22
  • Search criteria with "Search" button. I've got say a million records being searched, there are several indices with maybe 500 - 10,000 unique values each along with a date range. The AutoPostBacks trim down the listed unique index values based on date range. –  Jul 08 '11 at 19:25
  • 1
    narrowing the results based on criteria entered sounds like a valid plan, but in terms of web based functionality you're going to want to look at ajax approaches - either the lazy way (update panels) or efficient way through clientside ajax requests to the server. – KP. Jul 08 '11 at 19:28
  • I'm looking at AJAX update panels and it seems like they simply limit the portion of the page that gets updated. In this case the whole page is a search criteria and in many cases I'm only leaving one control unmodified in the postback. Ya, it's slightly less efficient but I have to "sell" AJAX to the company for this site if I want to use it. (and so far DB performance is the main bottleneck for this page...) –  Jul 08 '11 at 20:40

2 Answers2

6

The reason to set AutoPostBack="true" on a field (or other input control) is because you want the page to postback when that control's data changes - without requiring that the user click a button. It sounds like that is exactly what is happening: when the field loses focus, the page does a postback.

Perhaps I'm misunderstanding the question? Can you provide some more information about how you need the page/form to behave?

Edit: more info, based on comment from OP.

I think I understand: the "normal" case is they select something from a DropDownList1, and you autopostback to set the values of DropDownList2, based on the selected item in DropDownList1. However, the user may not care about the second list; if they click "search", you want the button-click to essentially abort the autopostback (already in progress), and initiate a new postback.

Unfortunately, I don't think there's any functionality in any version of ASP.NET to "abort" a postback already in progress (not from the client-side code, anyway). Therefore, in order to implement the above behavior, you're going to have to do something outside the standard ASP.NET postback behavior. Here's a few ideas, though by no means is it an exhaustive list:

  • Use AJAX and JS to retrieve the contents of DropDownList2. If the user clicks search while that ajax call is in progress, the page should postback right away.
  • Store all possible DropDownList2 data in JSON format in your page; use purely client-side JS to populate List2 when List1 changes. Again, if the user clicks "search", the page will postback right away. Depending on how big the pool of possible List2 entries is, this may bloat the page size too much to be workable.
  • Use client-side JS to disable your search button when List1 changes selection. The user won't be able to click "search" until the autopostback (to fill List2) completes.

Hope this helps!

mikemanne
  • 3,535
  • 1
  • 22
  • 30
  • 2
    I agree here. @ebyrob's code is a bit confusing as to what it is they are trying to do. I'd highly suggest ripping out all those autopostback's if you expect the user to click a button. – NotMe Jul 08 '11 at 19:09
  • One main use has been a search criteria with a "Search" button. The search criteria is rather complicated, and several fields modify options in other fields based on database relationships between the fields. I've used AutoPostBack to update the criteria as the user modifies it before hitting the search button. –  Jul 08 '11 at 19:18
  • @ebyrob sounds like you need to consider ajax. A full postback multiple times as people fill out a form is not an elegant solution. – KP. Jul 08 '11 at 19:23
  • Are you suggesting pushing all the indices down on initial page load? (say 20,000 records each containing a date-time stamp?) –  Jul 08 '11 at 19:28
  • 1
    No I'm suggesting that where you are currently causing an entire round trip to the server and reload of the page (postback), you consider requesting and sending only the data you need via ajax. Cleaner approach, easier on the eyes for your users. – KP. Jul 08 '11 at 19:30
  • > It sounds like that is exactly what is happening: when the field loses focus, the page does a postback. Except it isn't. Add one autopostback textbox and one regular listbox to a page, change the text box, click the list box. The click to the listbox is lost. (ie: selection doesn't change) Actually, I don't want to abort any events. Ideally I'd like to get all the events in a single postback to the server. (which is what I emulate in the mousedown solution I mentioned for Buttons) –  Jul 08 '11 at 20:22
  • If you define the textbox as autopostback="true", it will postback when it loses focus. If it doesn't, then that's a separate problem to be debugged. However, based on what you're describing, I think it's working as-expected: change text box value; click on selection list - this causes postback because the textbox just lost focus; select something in the list (however postback is already in progress, so it's too late to select something in the list); postback completes, and page is re-drawn with the previously-selected item selected in the dropdown list. – mikemanne Jul 08 '11 at 20:32
  • If you want multiple events to be "bundled" into a single postback, then you can't set AutoPostBack=true. Setting it to true tells the page: "postback as soon as this happens". In other words, you're specifically telling the page NOT to bundle up multiple events into a single postback. – mikemanne Jul 08 '11 at 21:17
  • Disabling other controls won't work at all unless you disable them after the textbox changes but before the textbox loses focus. Except if you disable the other controls, how will the text-box lose focus? –  Jul 11 '11 at 13:28
  • There might be a race condition there that I haven't accounted for (as I noted, the list was just a quick brainstorm, not something I've exhaustively researched). I believe simply tabbing out of a control will trigger its lose-focus events, even if there are no other active controls on the page. Also, I wasn't necessarily advocating that EVERY control on the page be disabled - just the search button. – mikemanne Jul 11 '11 at 14:15
  • It's not a race condition. Tab to where? the browser url? Yes, another textbox would work, but practically every other kind of control would have to be disabled. (and I've really already solved the problem for buttons using mousedown instead of onclick...) At this point switching to AJAX is still a pretty big leap. I might as well consider Silverlight and Java applets in addition. –  Jul 11 '11 at 18:34
  • I feel like your responses are becoming increasingly argumentative, so this will be my last reply on this thread. I'm sorry if the ideas I've given haven't resolved your problem, or weren't as detailed / fully-tested as you would like. I think you're hitting a fundamental limitation of the way AutoPostBack works in relation to other controls on the page. I can understand your frustration, and your wish that there was a workaround that was less invasive to your current design (though I don't think use of Ajax is all THAT invasive). I hope you can find a solution you're happy with. – mikemanne Jul 11 '11 at 18:49
3

To make the client side be more interactive and reduce sending all that viewstate and redrawing the page, I add a little jquery into the mix. It makes things like what you are proposing possible. jquery even ships with the asp.net MVC framework so there is no shame in using it with asp.net.

Here is a simple example that uses jquery that demonstrates what I think you want.

First, in the aspx file, add in a reference to the jquery library. I use the Google content delivery network so you don't even have add this file to your VS project. Then take the auto postback references out of all your server controls except the button. I left that one to continue doing a postback because I suspect at some point you want a regular post back, all the other controls use ajax to get your server side response.

I started by using your example page with these modifications:

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="temp.aspx.cs" Inherits="temp" %>
<!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">
<head runat="server">
    <title>Untitled Page</title>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js" type="text/javascript"></script>
</head>
<body>
<script type="text/javascript">
    $(document).ready(function () {
        // Establish where the output goes.
        var outputObject = $("#<%=TextBox2.ClientID %>");

        // create a function to do an ajax postback
        function doAjaxPostback(sender, value) {
            $.ajax({
                type: "POST",
                url: "temp2.aspx",
                data: "id=" + sender.attr("id") + "&value=" + value,
                success: function (data) { outputObject.append("<br />" + data) }
            });
        }

        // Use jquery to wire up the event handler. We use the ClientID property in case these 
        // elements get embeded in some other server control container later.
        $("#<%=TextBox1.ClientID %>").keyup(function (event) { doAjaxPostback($(this), $(this).val()); });
        $("#<%=TextBox1.ClientID %>").change(function (event) { doAjaxPostback($(this), $(this).val()); });
        $("#<%=ListBox1.ClientID %>").change(function (event) { doAjaxPostback($(this), $(this).val()); });

        // Use a plain html button tag for ajax only. The server control button gets rendered as
        // a submit button which requires it to be handled a little differently.
        $("#PlainButton").click(function (event) { doAjaxPostback($(this), $(this).attr("value")); event.preventDefault(); });
    });
</script>
    <form id="form1" runat="server">
    <div>
        <asp:TextBox ID="TextBox1" runat="server" ></asp:TextBox>
        <asp:Button ID="Button1" runat="server" OnClick="PostBack" Text="Button" /><br />
        <button id="PlainButton" value="Plain Old Button">Ajax Only, No postback</button>
        <br />
        <asp:ListBox ID="ListBox1" runat="server" >
            <asp:ListItem>value1</asp:ListItem>
            <asp:ListItem>value2</asp:ListItem>
        </asp:ListBox>
        <br />
        <br />
        Events Fired:<br />
        <asp:TextBox ID="TextBox2" runat="server" Height="159px" TextMode="MultiLine" Width="438px"></asp:TextBox>

        </div>
    </form>
</body>
</html>

Then for the code behind I just made a tiny change so we can report when we get a regular postback versus the ajax kind:

        protected void PostBack(object sender, System.EventArgs e)
        {
            this.TextBox2.Text += "\n\nGot an asp.net postback\n\n"
                + string.Format("PostBack for - {0}\n", ((System.Web.UI.Control)sender).ID);
        }

Okay, so I was trying not to get too fancy but I wanted to demonstrate how easy this is so I made a second page, temp2.aspx but left the aspx file alone as i only needed what is in the code behind:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace WebApplication1
{
    public partial class temp2 : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            string id = string.Empty;
            string value = string.Empty;
            Response.Clear();

            if (Request.Form == null || Request.Form.Count < 1)
            {
                Response.Write("I got nothin'");
                Response.Flush();
                Response.End();
                return;
            }

            id = Request.Form["id"];
            value = Request.Form["value"];

            Response.Write(string.Format("\nevent from: {0}; value={1}",id,value));
            Response.Flush();
            Response.End();
        }
    }
}

Notice that what I did was clear, write, flush and end the response so only the text we want is sent back to the caller. We could have done some fancy stuff in the page_load of the original temp page to check if it is a call from the ajax function that will not clear or flush the response if the incoming Request.Form does not contain a certain field, etc. But by doing it as a separate page, I hoped to simplify the code. This also opens up possibilities.

Say you have a country drop down that has Canada and USA in it and when it changes, you want to sent back data to populate a State/Province dropdown with the appropriate values. By putting the lookup code on its own page the way I did with temp2.aspx, you can then call it from all the pages in your app that have a need for such a service.

Good luck, let me know if you have any trouble understanding my code.

Joseph White
  • 755
  • 5
  • 17