Saturday, March 24, 2012

UpdatePanel and dynamic image button events

First let me say that I'm not new to .NET programming but I am relatively new to Atlas. I've downloaded all the stuff, installers, help, watched the videos, and tried to consume as much as I could about it before I got started. I've done a few really simple examples and at the surface understand what it does and why it's a good thing...

But, as is typical, I may have bitten off a little more than I can chew for a starter project. I decided to convert an existing (working) app I wrote to partially utilize Atlas. In short, and app uses a number of user controls that are turned on and off (visibility) depending on actions the user takes. One user control exists of an ASP:TABLE that is dynamically created on the fly. The table is populated with items that can be edited, deleted, or modified in various ways via dynamically created image buttons. The image button is created with a CommandArgument and CommandEventHandler so that when it is clicked, it can fire an event to let the parent know that we want to hide the user control we're using and instead unhide the one that will take the necessary action on the object.

So I wrapped the ASP:TABLE (this is within a usercontrol, the ScriptManager is on the parent page) in an UpdatePanel and set some triggers:

<atlas:UpdatePanelID="atlDisp"runat="server"Mode="Conditional">
<ContentTemplate>
<asp:TableID="tblDisp"runat="server"Width="100%"CellPadding="5"CellSpacing="0" TabIndex="1">
</asp:Table>
</ContentTemplate>
<Triggers>
<atlas:ControlValueTriggerControlID="ddlVendors"PropertyName="SelectedValue"/>
<atlas:ControlValueTriggerControlID="ddlSubjectList"PropertyName="SelectedValue"/>
<atlas:ControlValueTriggerControlID="rbTopTen"PropertyName="SelectedValue"/>
<atlas:ControlValueTriggerControlID="rbAtoZ"PropertyName="SelectedValue"/>
<atlas:ControlValueTriggerControlID="rbOrderBy"PropertyName="SelectedValue"/>
</Triggers>
</atlas:UpdatePanel>

That works beautifully. When any of the dropdowns or radios are clicked, the table is updated exactly as expected without an obvious postback. I also put in an UpdateProgress control that lets the user know something is happening on the pages with high latency. I was (am) astounded as to how simple this was to do.

Then I clicked one of my edit buttons. Nada. The UpdateProgress fires, but nothing happens. I traced the process via the debugger. The image button event fires, the parent catches the event and runs the correct method. One part of that method is to hide the user control that's listing the items, and unhide the control that will be filled with the item being edited. The actions are executed, but the page isn't updated to show the correctly loaded user control.

The click event (and the event declaration itself) of the image edit button is as follows:

publicdelegatevoidItemIDEventHandler (object sender, resadmin.ItemID e);
publiceventItemIDEventHandler _edititem;privatevoid EditItemHandler (object sender,CommandEventArgs e)
{
if (this._edititem !=null)
{
_id =newItemID();
_id.ID =e.CommandArgument.ToString();
this._edititem(this, _id);
}
}

Here is the method (and the event handler) that is run by the parent when it catches the event:

this.ucList._edititem +=new resadmin.controls.list.ItemIDEventHandler (ModItem);private void ModItem (object sender, ItemID e)
{
if (IsRegistered)
{
//this section of code executes when I run the process thru the debugger:
this.Session["ItemID"] = e.ID;
FlipOff (); //turn off the visible user control
this.ucMod.Visible =true; //turn on the control we want
//fill up the mod screen with the ID of the item being requested for edit
this.ucMod.FillItem (e.ID);
//all of these statements were executed, but I'm still seeing the list control, not the mod control
}
else
DisableAll ();
}

I realize I've only shown part of the code here but to show all of it would be very extensive. The dilemma is that the correct firing of events and the correct process is happening, but the usercontrols aren't being turned on and off correctly. And now I'm at a loss. I *think* what I want it to do is a "real" postback when I click an image button (which is contained in the dynamic table, and wrapped in an UpdatePanel) but not when I change a declared dropdown or radio button as defined by the triggers on the UpdatePanel.

Thanks in advance for any responses. I realize this is somewhat confusing but it's a relatively simple example of something that works correctly when an ASP table (with dynamic controls) is *not* wrapped in an UpdatePanel. I'm also willing to concede that this is not the place for or the intent of Atlas. I'd be more than willing to hear someone explain simply when to consider using Atlas, and when not to.

...BillH

Can you run Fiddler and show us what the response is that's coming back from the server?

Well, yes -- and no. I just downloaded and installed Fiddler, never used it before. The problem is that this app is rather huge, and the amount of data that's displayed via Fiddler is mostly irrelevant to the issue at hand - and I have to admit that I'm not exactly sure what of Fiddler's data you're asking for anyway. I think what I'm going to do is create a really simple app that demonstrates the problem (or, not, which might tell me a lot) that would be a lot easier to trace and debug. I think all I need is a couple of user controls and a dynamically created button (constructed within a dynamically created table and wrapped in an UpdatePanel) and I can recreate the problem with something that could easily be pasted into a new Atlas web site for review.

Thanks for your response. I'll reply later with my findings and sample app.

...BillH


hello.

creating a simple app is a great idea! regarding fiddler, you should check the headers and the viewtext area.


OK, here's my simple app, with all the code that demonstrates my problem. The problem happens when the EnablePartialRendering="true" is added to the ScriptManager attributes. I have three files in an atlas project, default.aspx, first.ascx, and second.ascx. First and second are both loaded on default.ascx, and the visible property of first is set to true by default while the visible property of second is set to false. On the first user control, I have a table and I dynamically create a button to add to it in the code-behind. When one clicks the button, it should hide the first control and unhide the second. If I move the table outside the UpdatePanel, works like a champ. So here are the files and code-behind for each:

default.aspx:

<%@. Register src="http://pics.10026.com/?src=second.ascx" TagName="second" TagPrefix="uc2" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title>Atlas Problem Example</title>
</head>
<body>
<form id="form1" runat="server">
<atlas:ScriptManager ID="ScriptManager1" EnablePartialRendering="true" runat="server" />
This is the default.aspx page. It contains two user controls, one called first with the default
visible property set to true, and one called second.ascx with its visible property set to false.
<br /><br />
<div>

<uc1:first ID="First1" runat="server" EnableViewState="true" Visible="true" />
<uc2:second ID="Second1" runat="server" EnableViewState="true" Visible="false" />

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

default.aspx.cs:

using System;
using System.Data;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;

public partial class _Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
this.Second1._return += new EventHandler (LoadFirst);
this.First1._return += new EventHandler (LoadSecond);
}

protected void LoadFirst (object sender, EventArgs e)
{
this.First1.Visible = true;
this.Second1.Visible = false;
}

protected void LoadSecond (object sender, EventArgs e)
{
this.First1.Visible = false;
this.Second1.Visible = true;
}
}

first.ascx:

<%@. Control Language="C#" AutoEventWireup="true" CodeFile="first.ascx.cs" Inherits="first" %>
This is a user control called named first.ascx. It contains an ASP:TABLE with a
dynamically created button. When the button is pressed, an event is sent to the
parent. The parent will catch the event, and set the visible property of this control
to false, and the visible property of the second control to true. However, if you
click the button it won't work. Move the table outside of the UpdatePanel and it
works.<br />
<br />
<div style="color: red">
*ALSO*, if you remove EnablePartialRendering="true" from the ScriptManager, it will
also work, but that to me seems to defeat the purpose of the technology.</div>
<br />
<br />
<atlas:UpdatePanel ID="UpdatePanel1" runat="server" Mode="Conditional">
<ContentTemplate>
<asp:Table ID="Table1" runat="server">
</asp:Table>
</ContentTemplate>
</atlas:UpdatePanel>

first.ascx.cs:

using System;
using System.Data;
using System.Configuration;
using System.Collections;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;

public partial class first : System.Web.UI.UserControl
{
public event System.EventHandler _return;

protected void Page_Load(object sender, EventArgs e)
{
Button b = new Button ();
b.ID = "btnSubmit";
b.Text = "Go to Second user control";
b.Command += new CommandEventHandler (ButtonHandler);
TableRow tr = new TableRow ();
TableCell tc = new TableCell ();
tc.Controls.Add (b);
tr.Cells.Add (tc);
this.Table1.Rows.Add (tr);
}

private void ButtonHandler (object sender, CommandEventArgs e)
{
if (this._return != null)
this._return (this, new EventArgs ());
}
}

second.ascx:

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

This is the second user control. You can click the button to return back to the first user control.
<br /><br />
<asp:Button ID="Button1" runat="server" OnClick="Button1_Click" Text="Go Back to the First Control" />

second.ascx.cs:

using System;
using System.Data;
using System.Configuration;
using System.Collections;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;

public partial class second : System.Web.UI.UserControl
{
public event System.EventHandler _return;

protected void Page_Load(object sender, EventArgs e)
{

}
protected void Button1_Click (object sender, EventArgs e)
{
if (this._return != null)
this._return (this, new EventArgs ());
}
}


hello.

well, i'm not sure on waht you're trying to accomplish here...i think that you'll allways have a complete postabck with this. if in your page you wrap your user controls on an updatepanel, you'll have partial postbacks...is this what you need?


All I want to do is turn off one user control and turn on another. I'm doing that by sending an event up to the parent (default.aspx) when my dynamic button on first.ascx is clicked. But I never see second.ascx even tho the code executes like it's supposed to. Visually:

default.aspx

first.ascx (visible)

UpdatePanel

table1 (rows, cells dynamically generated)

button (dynamically generated)

clicked - tell default to hide this uc and unhide second uc

second.ascx (not visible)

If you wrap either first.ascx or table1 within an UpdatePanel, the button click doesn't appear to work. I've found that the events get passed correctly, and all the code executes as it should, but the user controls aren't turned on and off like they should be by the parent. If you remove the UpdatePanel and click the dynamically generated button, it'll switch to second.ascx, otherwise, not. Make sense?

...BillH


hello again.

i must be doing something wrong...i mean, i've copied your sample and the code you've sent changes the control with a complete postback. adding an updatepanel to wrap the user controls lets me perform the substitution without a complete postback


Thanks for all your help on this. Something must really be screwy with my setup. That simply doesn't work for me. When I click the button on the first page, not a dang thing happens.

...BillH


I have resolved this issue to some degree and thought that I'd share the results since the solution I finally arrived at is satisfactory for what I need it to do - although I will present another issue that in my opinion deserves some attention or at least a better explanation regarding behavior.

If one reads this thread again, what I am trying to do is switch user controls on a main page (without a visible postback) by setting their visible properties based on certain page events. I found that if I wrap *only one* of the user controls in an UpdatePanel, or, I initialize an UpdatePanel within one user control, that my visible properties were not being set. In my example app default page, I could have the following code:

<form id="form1" runat="server">
<atlas:ScriptManager ID="ScriptManager1" EnablePartialRendering="true" runat="server" />
<div>
<atlas:UpdatePanel ID="upanel" Mode="Conditional" runat="server">
<ContentTemplate>
<uc1:first ID="First1" runat="server" EnableViewState="true" Visible="true" />
<uc2:second ID="Second1" runat="server" EnableViewState="true" Visible="false" />
</ContentTemplate>
</atlas:UpdatePanel>
</div>
</form>

This block of code allows the app to behave like I'd expect. Note that both user controls are wrapped within the same UpdatePanel. However, if I move either of the user controls outside of the UpdatePanel or use two different UpdatePanels as such:

<form id="form1" runat="server">
<atlas:ScriptManager ID="ScriptManager1" EnablePartialRendering="true" runat="server" />
<div
<atlas:UpdatePanel ID="upanel" Mode="Conditional" runat="server">
<ContentTemplate>
<uc1:first ID="First1" runat="server" EnableViewState="true" Visible="true" />
</ContentTemplate>
</atlas:UpdatePanel>

<uc2:second ID="Second1" runat="server" EnableViewState="true" Visible="false" /
</div>
</form>

Or, if I want the user control is a second UpdatePanel:

<form id="form1" runat="server">
<atlas:ScriptManager ID="ScriptManager1" EnablePartialRendering="true" runat="server" />
<div>
<atlas:UpdatePanel ID="upanel" Mode="Conditional" runat="server">
<ContentTemplate>
<uc1:first ID="First1" runat="server" EnableViewState="true" Visible="true" />
</ContentTemplate>
</atlas:UpdatePanel>
<atlas:UpdatePanel ID="upanel2" Mode="Conditional" runat="server">
<ContentTemplate>
<uc2:second ID="Second1" runat="server" EnableViewState="true" Visible="false" />
</ContentTemplate>
</atlas:UpdatePanel>
</div>
</form>

The basic functionality of the code, being able to set the visible properties of the user controls from an evet caught at the parent, is broken. So obviously the answer to my question is that I need to wrap all of my user controls within the same UpdatePanel if they are meant to interact with one another.

But now my basic question is, why? If one can have unlimited UpdatePanels to be treated independently, or one doesn't want or need to have a user control within the same UpdatePanel, why should one be required to do so?

Just a thought. I have a workaround, I can use it, but it leads me to the above questions that I'm still unclear about.

...BillH


When you look into how an UpdatePanel functions, you'll notice that it can only update it's child controls through an AJAX call. The point to remember is that the panel is a container control for a reason. The basic concept is that if you are a container, you know how to update your contents. What you are trying to do, is have a container update its parents and/or siblings. This is a very difficult task, if not impossible.

To better illustrate, think of the UpdatePanel as a simple div element. When a postback occurs that was triggered from inside of the div element, the div simply sets its innerHTML to what the updated content should be. However, the div will have no idea that another control on the page must also be updated, nor does it know how to update that element. It's really a scope issue when you think about it. The UpdatePanel can only update its children, anything else is outside of its scope.

Hope this helps,

-Tony


Tony, thanks for that explanation, that does help a lot. I think the whole concept that generated this thread from me was a statement made by Scott Guthrie when I viewed the webcast where he constructed a Task List application. He made the statement as he was designing the basic page that one could have an unlimited number of UpdatePanels on a page. Under what circumstances, then, would one want to use more than one UpdatePanel on the same page?

I guess if one had an overly complex page with lots of things going on in different regions, I could see it - otherwise - it looks like it would almost be recommended to construct the whole body of the page between UpdatePanel tags.

...BillH

No comments:

Post a Comment