Posting this even though I found the answer and a workaround, in the hopes that it helps someone else.
Basically, when using IE8 and changing an element's value that has both jQuery.change hooked to it and an inline onchange defined, the code in onchange executes twice.
We ran into this in an ASP.NET (3.5 or 4.0 does not matter) project with jQuery 1.4.2 and AutoPostBack=true controls, which render out with onchange="__doPostBack(...)" attributes, causing the postback to happen twice for a single user-initiated control-state change. (Microsoft really needs to get out of the browser business -- they're awful at it.)
To demo the problem, I created an empty website project and added a single aspx page, complete code+markup below. The important elements are the SCRIPT reference to jQuery, the $document.ready that wires up the control.change handler, and the lone autopostback control (I've verified that it happens with both TextBox and DropDownList). Note that no other controls exist on the page -- no AJAXToolkit controls, like UpdatePanels, and no IMGs with empty SRC attribs, which is a long-standing bug that also triggers double postback.
<%@ Page Language="C#" AutoEventWireup="true" EnableEventValidation="false"
EnableSessionState="False" EnableViewState="false" %>
<script runat="server">
protected void Page_Init(object sender, EventArgs e)
{
Trace.Write(String.Format("IsPostBack = {0}; PostbackControl = {1}",
IsPostBack.ToString(), Page.Request.Params.Get("__EVENTTARGET")));
}
protected void txtTest_TextChanged(object sender, EventArgs e)
{
Trace.Write(String.Format("Name = {0}", txtTest.Text));
}
</script>
<html>
<head>
<title>Demo of jQuery + AutoPostBack double postback problem</title>
</head>
<body>
<form id="Form1" runat="server">
<script src="jquery-1.4.2.js" type="text/javascript"></script>
<script type="text/javascript">
$(document).ready(function () {
$('#<%= txtTest.ClientID %>').change(function () {
alert('Double postback coming up!');
})
});
</script>
<asp:TextBox ID="txtTest" runat="server" AutoPostBack="true"
OnTextChanged="txtTest_TextChanged" />
</form>
</body>
</html>
You can observe the double postback either by setting a breakpoint in Page_Init, enabling IE script debugging and stepping through javascript to see onchange execute twice, or by enabling trace and viewing the resulting two requests in trace.axd.
It does not matter what you put inside the jQuery.change handler; removing the alert or having it return a bool or even making the handler completely empty has no impact on the double onchange/postback behavior.
It also happens regardless of whether you wireup the server-side TextChanged eventhandler declaratively in markup or imperatively in code (obviously you shouldn't do both or you would be causing the double postback yourself).
Even though the ASP.NET-generated onchange="__doPostBack(...)" is executing twice, the jQuery.change handler is only executing once because the alert only displays once. Occasionally (maybe 1 out of 5 or 6 passes), onchange only fires a single time, posting back once, so the bug doesn't seem to manifest consistently.
Even worse, in the actual production project where we first encountered this behavior, sometimes when it double posts, IsPostBack=true for one request and false for the other, causing initialization code to execute again. But I have not been able to reproduce that behavior in the demo project, so it may be a separate issue interacting with this one.
If you comment out the jQuery.change, it reverts to the normal desired single postback behavior.
I googled like crazy but was only ever able to find a single forum post that MIGHT have been facing the same issue. Only after I determined that the problem did not occur in Firefox and added IE8 to my list of search terms did I run across the jQuery bug reports. http://dev.jquery.com/ticket/6310 http://dev.jquery.com/ticket/6593
The workaround (in ASP.NET) is to disable AutoPostBack and to explicitly add __doPostBack at the end of the jQuery eventhandlers for all controls that must run both client- and server-side code.
EDIT: Fixed the jQuery selector which, as meep pointed out, was missing the #.