jqueryasp.netupdatepaneljpanelmenu

Wrapping ASP.NET web form with div causes double async postback, but only with jQuery, not normal DOM. Why?


Why does this happen?

  1. Take a plain ASP.NET Web Form with a single UpdatePanel and Button.
  2. In the ASP.NET client-side pageLoad event convenience function (use jQuery DOM ready if you prefer) wrap the form element in a newly created div element using jQuery.
  3. Click the button and notice that two asynchronous (xhr) post-backs are sent (use the Net tab in F12 browser developer tools to see them).

Here is some sample code to reproduce the issue:

<%@ Language="C#" %>
<!DOCTYPE html>
<html>
<head><title>Double async postback when form element wrapped in new div after page load</title></head>
<body>
    <form id="frmTest" runat="server">
        <asp:ScriptManager ID="smTest" runat="server" ScriptMode="Release" />
        <asp:UpdatePanel ID="upTest" runat="server">
        <ContentTemplate>
            <asp:Button ID="btnTest" runat="server" Text="Test Postback"/>
        </ContentTemplate>
        </asp:UpdatePanel>
    </form>
    <script src="//ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.js" type="text/javascript"></script>
    <script type="text/javascript">
        function pageLoad() {
            // jQuery:
            $("<div></div>").insertBefore("form").append($("form"));
            // DOM-only:
            //document.body.insertBefore(document.createElement("div"), document.forms[0]).appendChild(document.forms[0]);
        }
    </script>
</body>
</html>

(I am using a plain web.config file targeting .NET Framework 4.5.2 and jQuery 1.7.1. I was able to reproduce this in Firefox 40.0.3, Chrome 45.0.2454.85, and IE 11.)

Now notice that if you comment out the jQuery line and uncomment the DOM-only line, only one postback is sent.

Also, if you wrap the form tag in the div prior to page load (i.e. in static HTML) it works fine (no double-postback.)

Why does this matter/why do I care? Many jQuery plugins (e.g. jPanelMenu) wrap the entire contents of the body tag (or most of them) in a div to ensure a relatively-positioned ancestor.


Solution

  • So, as often happens, in the process of putting together a proper question with as complete and concise demonstration code as possible, I believe I found the answer.

    I found that if I upgrade jQuery to 1.9.0, the problem goes away. But according to their documentation, many changes happened at 1.9.0 that could potentially break old plugins (which my project has several of.) I have a feeling that this problem is related to this change in 1.9.0:

    Loading and running scripts inside HTML content

    Prior to 1.9, any HTML-accepting method (e.g., $(), .append(), or .wrap()) executed any scripts in the HTML and removed them from the document to prevent them from being executed again. This still broke in situations where a script might be removed and reinserted into the document using methods such as .wrap(). As of 1.9, scripts inserted into a document are executed, but left in the document and tagged as already executed so they won't be executed again even if they are removed and reinserted.

    Every ASP.NET Web Form with ScriptManager/UpdatePanel controls has those WebResource.axd and ScriptResource.axd <script> tags inserted near the bottom of the form element's contents. These deliver the client-side AJAX framework. I believe that jQuery was re-loading these scripts when it appended the form node to the new div node, and therefore duplicating the postback event handlers. My only question now is why this was so hard for me to detect. I didn't see any extra event handlers attached, or extra lookups for the scripts (even cached). But that is a separate question... :)