4
votes

I am trying to understand a bit about partial postbacks using updatepanels and while lots of things are stumping me, this one has me baffled.

I am using tabs inside of a UpdatePanel and becuase this is an data entry application, I am putting the content into user controls, so each tab has its own user control. In addition, I am adding custom javascript to each of the user controls to handle specific client side things for the stuff in that control.

In doing this though I have noticed that the javascript in the user control does not fire on partial page postbacks.

To simulate this I created a very simple app (using Master Pages with ScriptManager control on the master page) in VS2010.

The content panel of the default page has the following

<asp:UpdatePanel runat="server" UpdateMode="Conditional" ChildrenAsTriggers="true">
<ContentTemplate>
   <asp:Button runat="server" ID="Button1" Text="Partial postback" />
   <br />
   <asp:Panel runat="server" ID="panel"></asp:Panel>
</ContentTemplate>
</asp:UpdatePanel>

Then I created a user control and simply added some plain text and a javascript alert

<%@ Control Language="vb" AutoEventWireup="false" CodeBehind="UC1.ascx.vb" Inherits="TestWebApp_VB.UC1" %>
This is user control 1
<script> alert('control 1'); </script>

Then in the page load event of the Content page, I load the control into the panel.

   Panel.Controls.Add(LoadControl("UC1.ascx"))

In addition on the click handler of the button I also load the control (or a different control when toggling, as described further in)

Button_Click
 Panel.Controls.Clear()
 Panel.Controls.Add(LoadControl("SomeControl.ascx"))

So when the page is first loaded the alert fires, however when you click the button and the page is partially posted back, the alert never fires.

I took this a step forward and created a second user control and had the button toggle loading the controls and also added a normal button outside the UpdatePanel. Any time I did the full postback the javascript in the loaded user control fires, but it never fires on the partial postbacks.

I then went one step forward and moved my javascript in the controls into a function so I could call the function from the PageRequestManager, or from document.ready.

So now my two controls contain this script (with the alert containing a different message for each control)

<script type="text/javascript">
  function userLoad() {
    alert("Identify control");
  }
</script>

And thusly I added this to the end of my content panel

<script type="text/javascript">
   var prm = Sys.WebForms.PageRequestManager.getInstance().add_endRequest(userLoad);
</script>

Well now the javascript fires on every partial postback, however it fires the javascript for the control that last loaded by a full postback.

Example:

  1. Page intially loads UserControl1 - the alert for UserControl1 fires (not shown here - the userLoad function is being called in jquery document.ready)
  2. User clicks button - which should load UserControl2
  3. UserControl2 is loaded on the screen, but the alert for UserControl1 fires

A bit wordy, apologies for that, I found no easy way to explain this.

---Edit----

One of the things I have tried, working with Scotts suggestions. Keep the javascript for each control in a seperate file and put all of it into a function named something like function UC1Load() {} (maybe sticking a namespace around it, if I am feeling frisky), add a reference to the script file in the content page via a

<script src='UC1.js'> 

then register startup script to call this function

 ScriptManager.RegisterStartupScript(ctrl, ctrl.GetType(), "UserJS", "UC1Load()", True)

The biggest problem with this is that I am now having to download a bunch of script that wont ever be used, which is why I was putting the script in the user controls in the first place.

3
Are UserControl1 and UserControl2 both on the page at the same time or does UesrControl2 replace UserControl1?Code Maverick
The code is set up so only one usercontrol is loaded on any given postback.Kiemo
If your js functions pertain to the control they are in and you have moved them inside each respective control (and hopefully at the very bottom after all of the content), why not just let them fire inline?Code Maverick
The inline script does not fire on partial postbacks. See the initial test case in the question - a control with just an inline alert. The alert only fires on a full postback. Which confuses the snot out of me!Kiemo
This question already contained the solution to my problem. Well asked, Sir.Marcel

3 Answers

2
votes

Another method you could take is using jQuery's .on() method.

When a selector is provided, the event handler is referred to as delegated. The handler is not called when the event occurs directly on the bound element, but only for descendants (inner elements) that match the selector. jQuery bubbles the event from the event target up to the element where the handler is attached (i.e., innermost to outermost element) and runs the handler for any elements along that path matching the selector.

Event handlers are bound only to the currently selected elements; they must exist on the page at the time your code makes the call to .on().

...

Delegated events have the advantage that they can process events from descendant elements that are added to the document at a later time. By picking an element that is guaranteed to be present at the time the delegated event handler is attached, you can use delegated events to avoid the need to frequently attach and remove event handlers. This element could be the container element of a view in a Model-View-Controller design, for example, or document if the event handler wants to monitor all bubbling events in the document. The document element is available in the head of the document before loading any other HTML, so it is safe to attach events there without waiting for the document to be ready.

So, for example, you could wrap your UserControls with a div using the same class like so:

UserControl 1

<div class="userControl">
    <span>This is UserControl 1</span>
</div>

UserControl 2

<div class="userControl">
    <span>This is UserControl 2</span>
</div>

Then, in your MasterPage, at the very bottom before your </body> tag, you include your jQuery reference and your jQuery script. In my example, I print out the contents of the <span /> inside of the UserControl when the <span /> is clicked.

jQuery

    <script src="//ajax.aspnetcdn.com/ajax/jQuery/jquery-1.7.2.min.js"></script>
    <script>
        $("div[id$=UpdatePanel1]")
            .on("click", "div.userControl span", function() { 
                alert($(this).html().trim()); 
            });
    </script>
</body>
</html>
1
votes

Ok, after creating my own test website and tinkering around, here's what I got to work:

TestControl1

<%@ Control Language="C#" 
            AutoEventWireup="true" 
            CodeFile="TestControl1.ascx.cs" 
            Inherits="Controls_TestControl1" %>

This is Test Control 1

TestControl2

<%@ Control Language="C#" 
            AutoEventWireup="true" 
            CodeFile="TestControl2.ascx.cs" 
            Inherits="Controls_TestControl2" %>

This is Test Control 2

10049777.aspx

<%@ Page Language="C#" 
         MasterPageFile="~/Site.master" 
         AutoEventWireup="true" 
         CodeFile="10049777.aspx.cs" 
         Inherits="_10049777" %>

<asp:Content ID="Content1" ContentPlaceHolderID="HeadContent" runat="Server" />
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="Server">
    <asp:UpdatePanel ID="UpdatePanel1" runat="server" 
        UpdateMode="Conditional" 
        ChildrenAsTriggers="true">
        <ContentTemplate>
            <asp:Button ID="Button1" runat="server"
                Text="Partial postback" 
                OnClick="Button1_Click" />
            <br /><br />
            <asp:Panel runat="server" ID="Panel1" />
        </ContentTemplate>
    </asp:UpdatePanel>
</asp:Content>

10049777.aspx.cs

protected void Page_Load(object sender, EventArgs e)
{
    if (!IsPostBack)
    {
        var TestControl1 = LoadControl("Controls\\TestControl1.ascx");

        Panel1.Controls.Add(TestControl1);
        ScriptManager.RegisterStartupScript(TestControl1, 
                                            TestControl1.GetType(), 
                                            "TestControl1Script", 
                                            "alert(\"control 1\");", 
                                            true);
    }
}

protected void Button1_Click(object sender, EventArgs e)
{
    var TestControl2 = LoadControl("Controls\\TestControl2.ascx");

    Panel1.Controls.Clear();
    Panel1.Controls.Add(TestControl2);

    ScriptManager.RegisterStartupScript(TestControl2, 
                                        TestControl2.GetType(), 
                                        "TestControl2Script", 
                                        "alert(\"control 2\");", 
                                        true);
}
0
votes

Found a good explanation for the inline script issue

http://veskokolev.blogspot.com/2009/03/inline-scripts-in-updatepanel-doesnt.html

In short: partial postbacks updates the innerHtml of the update panel, thus it sees the blob of html as straight text and thus doesnt register any script tags within the html.

So my final solution was this.

Each control has a its own javascript file with a function named similar to

function controlNameLoad() { ..do your stuff }

Inside the content panel code (inside a case statement to determine which control to load)

dim ctrl = LoadControl("UCName.ascx")
panel.Controls.Add(ctrl)
ScriptManager.RegisterClientScriptInclude(ctrl, ctrl.GetType(), "UserJS", "UCName.js")
ScriptManager.RegisterStartupScript(ctrl, ctrl.GetType(), "UserJS", "controlNameLoad()", True)

This is working for my test project, will see later on if I can implement into my real code.