UpdatePanel Not Displaying Dynamically Added UserControl - c#

I have an UpdatePanel with two HiddenField elements to determine which UserControl to swap into the PlaceHolder element. On initial load, one is placed in (this works fine). There are two buttons, and depending on which one is clicked, a PostBack occurs and is supposed to be swapping in the selected control.
When a button is clicked, the PostBack occurs, but in the network call, I do not see any HTML being returned, and nothing renders on the page, obviously. I don't know too much about ASP.NET and search results have yet to lead me to a solution, or a proper how-to that works for me.
Update Panel
<!-- Update Panel -->
<asp:UpdatePanel ID="PendingApprovalList" UpdateMode="Conditional" runat="server">
<ContentTemplate>
<!-- Page Navigation Bar -->
<div style="margin-top: 100px;">
<!-- Secret Sauce -->
<asp:HiddenField ID="hf_onChartsPage" runat="server" value="true" />
<asp:HiddenField ID="hf_onTablesPage" runat="server" value="false" />
<!-- Navigation Clickables -->
<a ID="ChartsTab" class="googleAnalyticsNav" runat="server" onserverclick="ChartsClicked">Chart View</a>
<a ID="TablesTab" class="googleAnalyticsNav" runat="server" onserverclick="TablesClicked">Table View</a>
</div>
<!-- Page Content PlaceHolder -->
<asp:PlaceHolder ID="AnalyticsContent" runat="server"></asp:PlaceHolder>
</ContentTemplate>
</asp:UpdatePanel>
Main Page Code Behind
public partial class LocalGoogleReports : System.Web.UI.UserControl {
//Constants
private const string CHARTS_PATH = #"~/userctrls/Admin/Dashboard/Local/AnalyticsCharts.ascx";
private const string TABLES_PATH = #"~/userctrls/Admin/Dashboard/Local/AnalyticsTables.ascx";
//Properties
public int CENTER_NUM;
public string CENTER_NAME;
public bool GoToChartsPage {
get {
return bool.Parse(hf_onChartsPage.Value);
}
set {
hf_onChartsPage.Value = value.ToString();
}
}
public bool GoToTablesPage {
get {
return bool.Parse(hf_onTablesPage.Value);
}
set {
hf_onTablesPage.Value = value.ToString();
}
}
protected void Page_Load(object sender, EventArgs e) {
if (!IsPostBack) {
CENTER_NUM = SessionManager.Center.CenterNumber;
CENTER_NAME = SessionManager.Center.CenterName.ToLower();
AnalyticsContent.Controls.Clear();
UserControl chartsControl = (UserControl)LoadControl(CHARTS_PATH);
AnalyticsContent.Controls.Add(chartsControl);
} else {
if (GoToChartsPage) {
LoadChartsUserControl();
} else {
LoadTablesUserControl();
}
}
}
protected void ChartsClicked(object sender, EventArgs e) {
GoToChartsPage = true;
GoToTablesPage = false;
}
protected void TablesClicked(object sender, EventArgs e) {
GoToTablesPage = true;
GoToChartsPage = false;
}
private void LoadChartsUserControl() {
AnalyticsContent.Controls.Clear();
UserControl chartsControl = (UserControl)LoadControl(CHARTS_PATH);
AnalyticsContent.Controls.Add(chartsControl);
ScriptManager.RegisterStartupScript(PendingApprovalList, GetType(), Page.UniqueID,
"getAnalyticData();", true);
}
private void LoadTablesUserControl() {
AnalyticsContent.Controls.Clear();
UserControl tablesControl = (UserControl)LoadControl(TABLES_PATH);
AnalyticsContent.Controls.Add(tablesControl);
}
}
Thanks in advance for any help!

Try to use PendingApprovalList.UpdatePanel(); after loading Chart or Table

Related

How to create a custom control with INamingContainer?

I am trying the following:
[PersistenceMode(PersistenceMode.InnerProperty)]
[TemplateContainer(typeof(TemplateContainer))]
public virtual ITemplate LayoutTemplate { get; set; }
protected void Page_Init(object sender, EventArgs e)
{
this.Controls.Clear();
if (LayoutTemplate != null)
{
var data = Enumerable.Range(0, 10);
foreach (int index in data)
{
TemplateContainer container = new TemplateContainer(index);
LayoutTemplate.InstantiateIn(container);
this.Controls.Add(container);
}
}
}
My container class:
public class TemplateContainer : Control, INamingContainer
{
public int Index { get; set; }
internal TemplateContainer(int index)
{
this.Index = index;
}
}
And my markup:
<uc:TemplateControl ID="ucTemplateControl" runat="server">
<LayoutTemplate>
<b>Index:</b>
<asp:TextBox ID="Literal1" runat="server"
Text='<%# Container.Index %>'
ReadOnly="true" />
<br />
</LayoutTemplate>
</uc:TemplateControl>
But for some reason Container.Index is not rendering any value, just empty. 10 controls are being created, but none shows a value.
What did I do wrong? How can I fix it so it will show the Index value?
I tried something similar to MSDN example:
How to: Create Templated ASP.NET User Controls
To bind the value you need to call the DataBind method of the custom control.
Calling
ucTemplateControl.DataBind();
from the page made the data to be bound on the template.

Custom WebControl is listed in an UpdatePanel's list of asyncpostback triggers, but still causes a full, synchronous page refresh when clicked

I'm trying to build a UserControl; a visually customizable LinkButton. All was well until UpdatePanels came into the mix: my FancyButton usercontrol causes a page refresh. For comparison I'm also using a traditional LinkButton, which works as intended.
//Doesn't work: Causes whole page to refresh/reload.
<as:FancyButton ID="fbUpload"
runat="server" Text="FancyButton"/>
//Works as intended: Causes ajax refresh of Update Panel.
<asp:LinkButton ID="btnUpload" runat="server" Text="LinkButton" />
Here's my updatePanel code:
<asp:UpdatePanel ID="upNewUpdatePanel" UpdateMode="Always" ChildrenAsTriggers="true"
runat="server">
<Triggers>
<asp:AsyncPostBackTrigger ControlID="fbUpload" EventName="Click" />
<asp:AsyncPostBackTrigger ControlID="btnUpload" EventName="Click" />
</Triggers>
<ContentTemplate>
<asp:PlaceHolder ID="detailPlaceHolder" runat="server" />
</ContentTemplate>
</asp:UpdatePanel>
And here is the code for the FancyButton UserControl:
I am pretty sure the problem is in here:
using System;
using System.Web.UI.WebControls;
using System.Web.UI;
namespace Jake
{
public class FancyButton : WebControl, INamingContainer
{
private LinkButton _btn;
public string Text
{
get
{
EnsureChildControls();
return _btn.Text;
}
set
{
EnsureChildControls();
_btn.Text = value;
}
}
public string OnClientClick
{
get
{
EnsureChildControls();
return _btn.OnClientClick;
}
set
{
EnsureChildControls();
_btn.OnClientClick = value;
}
}
public delegate void ClickEventHandler(object sender, EventArgs e);
public event ClickEventHandler Click = delegate { };
protected void _btn_Click(object sender, EventArgs e)
{
Click(this, e);
}
protected override void OnInit(EventArgs e)
{
base.OnInit(e);
EnsureChildControls();
}
protected override void CreateChildControls()
{
base.CreateChildControls();
_btn = new LinkButton { ID = "btn" };
Controls.Add(_btn);
}
protected override void OnPreRender(EventArgs e)
{
base.OnPreRender(e);
}
protected override void Render(HtmlTextWriter writer)
{
//<a class="btn {Color} btn-{Color}{CssClass?}{hasImage?}">
writer.AddAttribute(HtmlTextWriterAttribute.Class, "fancyButton");
_btn.RenderBeginTag(writer);
if (Text != null)
{
writer.Write(Text);
}
_btn.RenderEndTag(writer);
}
}
}
TL;DR: The normal linkbutton works as an async trigger; my custom, UserControl button does not. What am I doing wrong?
Solution from accepted answer
By inheriting LinkButton instead of WebControl, the Async panel update works as intended. Also, all of those pesky override methods became unnecessary.
namespace Jake
{
public class FancyButton : LinkButton
{
public string Color
{
get
{
if ( ViewState["Color"] != null && ((string)ViewState["Color"]).Length > 0)
{
return ((string)ViewState["Color"]).ToLower();
}
else return "white";
}
set { ViewState["Color"] = value; }
}
public string Icon
{
get { return (string)ViewState["Icon"]; }
set { ViewState["Icon"] = value; }
}
protected override void Render(HtmlTextWriter writer)
{
//<a class="btn {Color} btn-{Color}{CssClass?}{hasImage?}">
writer.AddAttribute(HtmlTextWriterAttribute.Class, string.Format("fancyButton {0}{1}{2}",
this.Color,
CssClass.Length > 0 ? " " + CssClass : string.Empty,
Icon != null && Icon.Length > 0 ? " hasIcon" : String.Empty));
this.RenderBeginTag(writer);
// <span>
writer.RenderBeginTag(HtmlTextWriterTag.Span);
// <div class="icon {IconClass}"></div>
if (Icon != null)
{
writer.AddAttribute(HtmlTextWriterAttribute.Class, string.Format("icon {0}{1}",
Icon,
Text != null ? " btnIconPadding" : ""));
writer.RenderBeginTag(HtmlTextWriterTag.Div);
writer.RenderEndTag();
}
if (Text != null)
{
writer.Write(Text);
}
// </span>
writer.RenderEndTag();
this.RenderEndTag(writer);
}
}
}
Not 100% sure on this, but I'm guessing that the problem is that the button causing postback is a private one from inside your UserControl, which you can't add to your triggers so easily.
The cleanest workaround I can think of is to make a class that inherits from LinkButton for your customizable control rather than a UserControl. Disadvantage: no visual designer or markup page.
A quick Google on the issue brought me indirectly to a Microsoft Connect:
https://connect.microsoft.com/VisualStudio/feedback/details/524827/dynamicdataentities-site-linkbutton-in-updatepanel#details
(Don't bother reading the thread he links from ASP.NET forums unless you like banging your head on the wall)

EventHandlers of dynamic controls created in a ListView are not fired, if the ListView is bound in Page_Load

In our intranet ASP .NET application we use an architecture approach which is similar to the MVVM pattern.
Our viewmodel, which describes the state of the view, is either created or loaded in Page_Load from the ViewState.
Depending on the viewmodel content the page then is dynamically created.
Right now I am facing a problem where I cannot find an elegant solution.
The problem is that we have to handle events that are fired by the controls, which are dynamically created in the ListView. However, those registered events do only fire, if the listView is databound in Page_Init. If the databinding takes place in Page_Load those events are not fired.
The thing now is, that we need our ViewModel in order to databind the ListView to it. This ViewModel is cached in the ViewState, which - to my knowledge - only comes accessible in Page_Load. That means that in Page_Init we cannot access the ViewModel.
I created the following example:
ASP:
<form id="form1" runat="server">
<asp:ListView runat="server" ID="lstView" ItemPlaceholderID="itemPlaceHolder">
<LayoutTemplate>
<asp:PlaceHolder runat="server" ID="itemPlaceHolder" />
</LayoutTemplate>
<ItemTemplate>
<asp:DropDownList runat="server" ID="dropDown" SelectedValue='<%# Bind("SelectedValue") %>' DataSource='<%# Eval("Choice") %>' AutoPostBack="true" />
</ItemTemplate>
</asp:ListView>
</form>
Code Behind:
namespace WebApplication1
{
public partial class _Default : System.Web.UI.Page
{
protected void Page_Init(object sender, EventArgs e)
{
//if i put the Initialization logic here, the event gets fired, however the Model is not accessible from the viewstate
}
protected void Page_Load(object sender, EventArgs e)
{
InitializeModel();
lstView.DataSource = _model.NestedModels;
lstView.ItemCreated += (k, args) =>
{
var dropDown = (DropDownList)args.Item.FindControl("dropDown");
dropDown.SelectedIndexChanged += new EventHandler(dropDown_SelectedIndexChanged);
};
lstView.DataBind();
}
void dropDown_SelectedIndexChanged(object sender, EventArgs e)
{
Response.Write("Selected Index Changed fired");
}
private MyModel _model;
void InitializeModel()
{
if (ViewState["model"] == null)
ViewState["model"] = new MyModel();
_model = (MyModel) ViewState["model"];
}
}
[Serializable]
public class MyModel
{
[Serializable]
public class NestedModel
{
public NestedModel()
{
Choice = new List(new[] { "Value1", "Value2", "Value3", "Value4", "Value5" });
}
public string SelectedValue { get; set; }
public List Choice { get; set; }
}
private List _nestedModels = new List();
public MyModel()
{
Init();
}
public List NestedModels
{
get { return _nestedModels; }
}
void Init()
{
NestedModels.Add(new NestedModel() {SelectedValue = "Value1"});
NestedModels.Add(new NestedModel() { SelectedValue = "Value2" });
NestedModels.Add(new NestedModel() { SelectedValue = "Value3" });
NestedModels.Add(new NestedModel() { SelectedValue = "Value4" });
NestedModels.Add(new NestedModel() { SelectedValue = "Value5" });
}
}
}
I hope one of you guys knows a way either to preserve my event to get fired or to retrieve my cached ViewModel in Page_Init.
Best Regards,
TH
You have to override the CreateChildControls event and dynamically add the control there on page postback. This will be called first and then your dynamically event handler is called.
protected override void CreateChildControls()
{
PopulateControls();//populate your controls here
}

Web Controls within UserControl null?

I've built a small User Control which is essentially a DropDownList with some preset Values based on what the Target-Property is set on.
Here's the Code:
public partial class Selector : System.Web.UI.UserControl
{
public string SelectedValue { get {return this.ddl.SelectedValue; } }
public int SelectedIndex { get { return this.ddl.SelectedIndex; } }
public ListItem SelectedItem { get { return this.ddl.SelectedItem; } }
private string target;
public string Target { get { return this.target; } set { this.target = value; } }
protected void Page_Load(object sender, EventArgs e)
{
ddl.DataSource = target=="Group"?Util.GetAllGroups(Session["sessionId"].ToString()):Util.GetAllUsers(Session["sessionId"].ToString());
ddl.DataBind();
}
}
ASP-Markup:
<%# Control Language="C#" AutoEventWireup="true" CodeBehind="Selector.ascx.cs" Inherits="InspireClient.CustomControls.Selector" %>
<asp:DropDownList runat="server" ID="ddl">
</asp:DropDownList>
If I insert my Selector into an aspx-Page it works just fine.
Example:
<SCL:Selector Target="Group" runat="server" />
However, If I programmatically add it like this
ctrl = new Selector();
ctrl.Target = "User";
the DropDownList "ddl" is null and the application (logically) throws an error. Is Page_Load the wrong Method to do such a thing? What am I doing wrong?
I should add, "ctrl" is of type dynamic, not sure if this has anything to do with it.
Thanks in advance!
Dennis
Since you're dynamically adding a user control and not a "simple" web control, you should use the LoadControl() method to instantiate it:
protected void Page_Load(object sender, EventArgs e)
{
Selector yourControl = (Selector) LoadControl("Selector.ascx");
yourControl.Target = "User";
Controls.Add(yourControl);
}

User Control Postback

I am working on rendering a set of cart items using a user control. Each cart item can be removed via a button in the user control. When a cart item is removed I need to visually show it's removal. However, since the cart item existed during the loading of the page it remains until the page is refreshed again. What I am after is a means to refresh the page after the work to remove the cartitem has been completed.
The code behind cart.aspx.cs looks like:
protected void Page_Init(object sender, EventArgs e)
{
CreateCartItemControls();
}
private void CreateCartItemControls()
{
foreach (CartItem ci in Profile.Cart.Items)
{
ASP.CartItemControl cic = new ASP.CartItemControl();
cic.ItemName = ci.Name;
cic.CartID = ci.ID;
cic.Cost = ci.BaseCost.ToString("c");
cic.ItemComponents = ci.Components;
cic.CartItemRemoved += new EventHandler(CartItemRemoved);
Content.Controls.Add(cic);
}
}
void CartItemRemoved(object sender, EventArgs e)
{
Master.UpdateCartItemCount();
}
Markup for CartItemControl.ascx
<%# Control Language="C#" ClassName="CartItemControl" AutoEventWireup="true"
CodeFile="CartItemControl.ascx.cs"
Inherits="UserControls_CartItemControl" %>
<fieldset id="FieldSet" runat="server">
<legend>
<asp:HyperLink ID="ItemLink" runat="server" />
</legend>
<asp:ImageButton ID="RemoveCartItem" AlternateText="Remove Item"
ImageUrl="~/img/buttons/remove_4c.gif" runat="server"
CommandName="Remove" OnCommand="RemoveCartItem_Command" />
<asp:Label ID="TotalItemCost" runat="server" Text="$0.00" />
<ol>
<li runat="server" id="ComponentsLI" visible="false">
<fieldset id="ComponentsFieldSet" runat="server">
<legend>Item Components</legend>
<asp:CheckBoxList ID="ItemComponentsCheckList"
runat="server" />
</fieldset>
</li>
</ol>
</fieldset>
Code behind for the UserControl CartItemControl.ascx.cs
public partial class UserControls_CartItemControl
: System.Web.UI.UserControl
{
public string ItemName { get; set; }
public int CartID { get; set; }
public string Cost { get; set; }
public IDictionary<int, SoftwareComponent> ItemComponents { get; set; }
protected void Page_PreRender(object sender, EventArgs e)
{
SetCartItemControlAttributes();
}
private void SetCartItemControlAttributes()
{
ItemLink.Text = ItemName;
TotalItemCost.Text = Cost;
RemoveCartItem.CommandArgument = CartID.ToString();
if (!ItemComponents.Count.Equals(0))
{
ComponentsLI.Visible = true;
foreach (KeyValuePair<int, ItemComponent> kvp in
ItemComponents)
{
ItemComponentsCheckList.Items.Add(
new ListItem(string.Format("{0} {1}",
kvp.Value.ComponentName,
kvp.Value.ComponentCost.ToString("c")),
kvp.Key.ToString()));
}
}
}
public event EventHandler CartItemRemoved;
protected void RemoveCartItem_Command(object sender, CommandEventArgs e)
{
int itemID;
if (int.TryParse(e.CommandArgument.ToString(), out itemID))
{
Profile.Cart.RemoveCartItem(itemID);
CartItemRemoved(sender, e);
Parent.Controls.Remove(this);
}
}
}
Just as you add CartItemControls to Content's Controls collection on init, you need to remove them on RemoveCartItem_Command. Do so by either exposing your own ItemRemoved event and handling it in the main page or by calling Parent.Controls.Remove(this) inside the RemoveCartItem_Command.
Or am I missing something?
Response.Redirect back to the page after you do the work to remove the cart item.
Try Server.Transfer to the same page.
That said, consider using Ruslan approach, by exposing an ItemRemoved event and handling it on the main page. You can do Content.Controls.Clear and call CreateCartItemControls again.
Just rebind the everything on the main page on every postback.

Categories