Reputation: 697
This issues is causing me no end of troubles! The requirements is for a web form (written in C#/ASP.Net Webforms) where the user can add and delete rows of controls for adding multiple instances of the same type pf data.
This page exists with in a larger site that has a hierarchy of MasterPages so I may be running into difficulty with MasterPages...
My issue is specifically with deleting a row from this "table". For good or for ill I built this as a Panel the holds multiple sub Panels. Each sub Panel represents one row of my "table".
My addRow buttons work perfectly. But the deleteRow buttons fail in different ways depending on how I try to handle them.
Here is my default.aspx (trimmed to the relevant parts):
<%@ Page Title="Alumni Outreach" Language="C#" EnableViewState="true" MasterPageFile="~/components/navigation/sidebar.master" AutoEventWireup="true" CodeBehind="default.aspx.cs" Inherits="www.law.unc.edu.alumni.outreach.outreach2" %>
<%@ MasterType VirtualPath="~/components/navigation/sidebar.master"%>
<asp:Content ID="Content1" ContentPlaceHolderID="cphContent1" runat="server">
<asp:Panel ID="pnlInformation" runat="server">
<h1>Community Outreach
</h1>
<p>
<span class="carolinaBlue">Purpose:</span> To quantify the UNC School of Law's <span class="carolinaBlue">Legacy of Leadership
</span>via Carolina Law alumni's stories of service, volunteerism, and leadership across the state and nation. Information gathered will be utilized in a variety of Carolina Law communnications/marketing pieces (for instance, an online presence with interactive maps) designed to illustrate Carolina Law alumni's leadership in the communities where they live and work.
</p>
</asp:Panel>
<asp:Panel ID="pnlForm" runat="server">
<h2>Volunteer Leadership Roles</h2>
<p>
Please share your commitment to community leadership by noting all organizations you have voluntarily served below.
</p>
<div style="display: inline-block;">
<asp:Button ID="btnAddLeadershipRow" OnClick="BtnAddLeadershipRow_Click" Text="+" runat="server" />
</div>
<div style="display: inline-block">
<div style="display: inline-block; width: 6.5em; font: 700 1em Verdana,Arial,Sans-Serif !important">Region</div>
<div style="display: inline-block; width: 7em; font: 700 1em Verdana,Arial,Sans-Serif !important">Profit?</div>
<div style="display: inline-block; width: 20.5em; font: 700 1em Verdana,Arial,Sans-Serif !important">Organization</div>
<div style="display: inline-block; width: 14em; font: 700 1em Verdana,Arial,Sans-Serif !important">Position</div>
</div>
<asp:Panel ID="pnlLeadershipFields" runat="server">
<!-- dynamically add controls here -->
</asp:Panel>
</asp:Content>
And here are the relevant parts of my default.aspx.cs file:
using System;
using System.Collections.Generic;
using System.Web.UI;
using System.Web.UI.WebControls;
using BusinessLogic.Alumni.Outreach;
using System.Text.RegularExpressions;
namespace www.law.unc.edu.alumni.outreach
{
[ViewStateModeById]
public partial class outreach2 : System.Web.UI.Page
{
private List<String> regions;
private List<String> profit;
private List<Button> _foundControls = new List<Button>();
public outreach2()
{
regions = new List<string> {
"Local",
"State",
"National"
};
profit = new List<string> {
"Non-Profit",
"For-Profit"
};
}
protected int LeadershipControlsMaxIndex
{
get {
if (ViewState["LeadershipControlsMaxIndex"] == null)
ViewState.Add("LeadershipControlsMaxIndex", 0);
return (int)ViewState["LeadershipControlsMaxIndex"];
}
set {
if (ViewState["LeadershipControlsMaxIndex"] == null)
ViewState.Add("LeadershipControlsMaxIndex", value);
else
ViewState["LeadershipControlsMaxIndex"] = value;
}
}
protected List<int> LeadershipControlsIndexes
{
get {
if (!(ViewState["LeadershipControlsIndexes"] is List<int>))
{
ViewState["LeadershipControlsIndexes"] = new List<int>();
}
return (List<int>)ViewState["LeadershipControlsIndexes"];
}
set { ViewState["LeadershipControlsIndexes"] = value; }
}
// If this is the first time the user is visiting the page
// create one set of controls
protected void Page_Load(object sender, EventArgs e)
{
if (!Page.IsPostBack)
{
CreateLeadershipControl();
}
else if (Page.IsPostBack)
{
// if I do:
// btnDelete.OnClientClick = "javascript:__doPostBack(\""+btnDelete.ID+"\",\"\")";
// when creating the delete button I enter the following if
// clause but cannot get an object (or Control or Button)
// reference to the btnDelete, whether I use this.Form or
// pnlLeadershipFields in the call to FindChildControlsRecursive
if (Request["__EVENTTARGET"].Contains("btnLeadershipRowDelete"))
{
FindChildControlsRecursive(this.Form);
object btnDelete = _foundControls.Find(x => x.ID == Request["__EVENTTARGET"]);
DeleteControls(btnDelete);
}
RecreateLeadershipControls();
}
}
public void FindChildControlsRecursive(Control control)
{
foreach (Control childControl in control.Controls)
{
if (childControl.GetType() == typeof(Button))
{
_foundControls.Add((Button)childControl);
}
else
{
FindChildControlsRecursive(childControl);
}
}
}
private void CreateLeadershipControl()
{
LeadershipControlsMaxIndex++;
LeadershipControlsIndexes.Add(LeadershipControlsMaxIndex - 1);
AddLeadershipControls(LeadershipControlsIndexes[LeadershipControlsIndexes.Count - 1]);
}
private void RecreateLeadershipControls()
{
List<int> indexes = LeadershipControlsIndexes;
for (int i = 0; i < indexes.Count; i++)
{
AddLeadershipControls(indexes[i]);
}
}
private void AddLeadershipControls(int index)
{
Panel pnlLeadershipControls = new Panel
{
ID = "LeadershipControls" + index.ToString(),
CssClass = "pnlControls"
};
Button btnDelete = new Button()
{
ID = "btnLeadershipRowDelete" + index.ToString(),
Text = "-"
};
//btnDelete.Click += new EventHandler(BtnDeleteRow_Click);
btnDelete.OnClientClick = "javascript:__doPostBack(\""+btnDelete.ID+"\",\"\")";
btnDelete.Attributes.Add("style", "display: inline-block; width: 1.4em;");
pnlLeadershipControls.Controls.Add(btnDelete);
DropDownList ddlRegion = new DropDownList
{
DataSource = regions,
ID = "LeadershipRegion" + index.ToString()
};
ddlRegion.DataBind();
ddlRegion.Attributes.Add("style", "display: inline-block; width: 7em;");
pnlLeadershipControls.Controls.Add(ddlRegion);
DropDownList ddlProfit = new DropDownList
{
DataSource = profit,
ID = "LeadershipProfit" + index.ToString(),
CssClass = "ddlProfit"
};
ddlProfit.DataBind();
ddlProfit.Attributes.Add("style", "display: inline-block; width: 7.5em;");
pnlLeadershipControls.Controls.Add(ddlProfit);
TextBox txtOrg = new TextBox
{
ID = "txtLeadershipOrganization" + index.ToString()
};
txtOrg.Attributes.Add("style", "display: inline-block; width: 20em;");
pnlLeadershipControls.Controls.Add(txtOrg);
TextBox txtPos = new TextBox
{
ID = "txtLeadershipPosition" + index.ToString()
};
txtPos.Attributes.Add("style", "display: inline-block; width: 20em;");
pnlLeadershipControls.Controls.Add(txtPos);
CheckBox cbCurrent = new CheckBox
{
ID = "cbLeadershipCurrent" + index.ToString(),
Text = "Current?",
};
cbCurrent.Attributes.Add("style", "display: inline-block; font: 700 1em Verdana,Arial,Sans-Serif !important");
pnlLeadershipControls.Controls.Add(cbCurrent);
pnlLeadershipFields.Controls.Add(pnlLeadershipControls);
}
private void DeleteControls(object sender)
{
Button button = (Button)sender;
string id = button.ID;
int index = Int32.Parse(Regex.Match(id, @"\d+$").Value);
Panel row = (Panel)button.Parent;
for (int i = row.Controls.Count - 1; i >= 0; i--)
{
if (row.Controls[i] is Button)
((Button)row.Controls[i]).Click -= new EventHandler(BtnDeleteRow_Click);
row.Controls[i].Dispose();
}
row.Parent.FindControl(row.ID).Dispose();
if (id.Contains("Leadership"))
{
LeadershipControlsIndexes.RemoveAt(LeadershipControlsIndexes.FindIndex(x => x == index));
}
}
protected void BtnAddLeadershipRow_Click(object sender, EventArgs e)
{
CreateLeadershipControl();
}
// If I do:
// btnDelete.Click += new EventHandler(BtnDeleteRow_Click);
// when creating the delete button, this method is called,
// but too late in the page life cycle
protected void BtnDeleteRow_Click(object sender, EventArgs e)
{
DeleteControls(sender);
}
}
}
I tried to add the rendered HTML, but went over the character limit for a new post!
In summary:
I understand why 'BtnDeleteRow_Click()' handler is not working the way I want it to, so I switched to using '__doPostBack' in the hopes that I could catch the deletion event earlier in the Page Life Cycle. I can catch it in Page_load, which I believe is the sweet spot (my ViewState has been loaded so I have access to my row indexes, but the page has not been rendered). But since I cannot get any sort of object reference to the actual button that was clicked, I cannot move forward. When I get to Page_load, I do have the correct ID in 'Request["__EVENTTARGET"]'.
Upvotes: -1
Views: 152
Reputation: 298
If I understand your goal, I would consider using a ListView wrapped in an UpdatePanel for this sort of functionality. The following example uses tables, but without them you can format the form anyway you would like:
https://www.codeproject.com/Articles/44070/Insert-a-new-record-using-ListView-with-a-GridView
Upvotes: 0