I have an ASP table, with a dropdownlist and text box in its 1st row. I have a button on click of which a similar row should be added. I have the following function in its button click event.
public void addRow()
{
int flag = Table1.Rows.Count;
DropDownList ddl = new DropDownList();
ddl.ID = "ddl" + (flag + 1).ToString();
TextBox tBox = new TextBox();
tBox.ID = "txt" + (flag + 1).ToString();
TableCell tCell1 = new TableCell();
TableCell tCell2 = new TableCell();
tCell1.Controls.Add(ddl);
tCell2.Controls.Add(tBox);
TableRow tRow = new TableRow();
tRow.Cells.Add(tCell1);
tRow.Cells.Add(tCell2);
Table1.Rows.Add(tRow);
}
This code works fine when adding 2nd row. When I click the button again, the value of flag which should have the value of number of rows of the table is not getting incremented. So a new row doesn't get created. Can you please tell me why the value of flag doesn't get incremented ? Or is their an easier way to do this ? Direct answer or link would be helpful.
Mark up code for Table:
<asp:Table ID="Table1" runat="server" Width="410px">
<asp:TableRow>
<asp:TableCell>
<asp:DropDownList runat="server" ID="ddl1"></asp:DropDownList>
</asp:TableCell>
<asp:TableCell>
<asp:TextBox runat="server" ID="txt1"></asp:TextBox>
</asp:TableCell>
</asp:TableRow>
</asp:Table>
Warning - Big ugly block of text
I'm going to try and explain why what you're trying to do is not as simple as it sounds (or as simple as I'd like it to be) and hopfully give you an alterantive approach, which while it
may not be straight forward, it should be "better".
It looks a high level overview of what you are trying to do is the following:
Give the user the oppotunity to add one or more records to a collection (database) before finalising the save.
It is possible to do this using postback to generate the additional rows/items beofore clicking the final save button. Hover this may not be the easiest, or best, way to do this.
From a technical viewpoint adding dynamic controls has to be handeld in a very specific manner at a specific point in the ASP.net page lifecylce. Dynamic controls also have to be recreated on every post back, so every time a user clicks the "add" button, you will have to re-reate the rows you already have in addition to the new row. This is why you only get the one row in this example. Thats just the adding of the dynaimc controls. Wait till you get to getting data from your dynamic text boxes and drop downs!
From an end users perspective, this approach is also not ideal as every time the user clicks the "add" button the page cycles through the request/response process, which at the minumun cuases a bit of an ugly flash.
Update
But how? I hear you ask. Well here it is (I've simplified it to one column for this example):
ASPX
<asp:Table ID="Table1" runat="server">
<asp:TableRow>
<asp:TableCell><asp:TextBox ID="txt1" runat="server"> </asp:TextBox></asp:TableCell>
</asp:TableRow>
</asp:Table>
<asp:Button ID="btnAdd" runat="server" Text="Add" onclick="Button1_Click" />
<asp:HiddenField ID="hdnRowCount" runat="server" />
<asp:Button ID="btnSave" runat="server" onclick="btnSave_Click" Text="Save" />
<div>
<asp:Label ID="lblResult" runat="server" Visible="false"></asp:Label>
</div>
C#
private int rowCount = 1;
private bool rowNeeded = false;
protected override void OnPreInit(EventArgs e)
{
//Pre-emptively create additional row on post back.
//We'll remove it if we don't need it later
//Controls Have to be added at pre-init to maintain their view state
//Adding a row in the button click event will result in any data
//entered in that row dissapearing from viewstate on next post back
if (IsPostBack)
{
//Get number of additional rows from hidden field
//We're using a hidden form field and Request.Form
//instead of ViewsState and hdnRowCount.value
//because ViewState has not been loaded at this stage
//of the page life cycle
if (!string.IsNullOrEmpty(Request.Form["hdnRowCount"]))
{
rowCount = int.Parse(Request.Form["hdnRowCount"]);
}
for (int i = 0; i < rowCount; i++)
{
TableRow tr = new TableRow();
TableCell tc = new TableCell();
TextBox tb = new TextBox();
// +2 as there are existing controls with "1", eg txt1
tb.ID = string.Format("txt{0}",i+2);
tc.Controls.Add(tb);
tr.Cells.Add(tc);
Table1.Rows.Add(tr);
}
}
base.OnPreInit(e);
}
//Add Row Click
protected void Button1_Click(object sender, EventArgs e)
{
//Hold number of additional rows in hidne field
hdnRowCount.Value = (++rowCount).ToString();
//Let the page know we need to keep the row
rowNeeded = true;
}
//An Exmple of how to get your data out
protected void btnSave_Click(object sender, EventArgs e)
{
string s = "";
//Iterate through the table rows finding the controls
//Using count -1 as we still have the pre-emptive row
for(int i = 0; i < Table1.Rows.Count-1; i++)
{
TextBox tb = (TextBox)Table1.Rows[i].FindControl(string.Format("txt{0}",i+1));
s += "," + tb.Text;
}
lblResult.Text = s;
lblResult.Visible = true;
}
protected override void OnLoadComplete(EventArgs e)
{
//Get rid of pre-emptive row if we don't need it
if (IsPostBack)
{
if (!rowNeeded)
{
Table1.Rows.RemoveAt(Table1.Rows.Count - 1);
}
}
base.OnLoadComplete(e);
}
End Update
So, how would I do it? Client side, is the answer to that question. I would use jQuery to create a clone of the first row. You then would need to adjust the id and name attributes of your form fields before appending your new row to the table. You would also need a hidden form field to keep track of the number of hidden rows. When your user finaly hits the "save" button, on the C# side, get the number of additional rows from you hidden field the go old school and use Request.Form[] to get the value of each of your added fields.
This, in my opionion, will be a smoother experience for the end user.
Another, somewhat hacky option,if you have a defined maximum number of additional rows. For this have the mamimum rows, with form controls in the table. You can then either use visible="false" in the aspx and use post back to change that to visible="true" when the user clicks "add". The other option there is to give the row a CSS class that hides the row and then use javascript/jquery to remove the class to make the row visible.
The issue here is that you are not understanding the ASP.Net Page Lifecycle and more importantly how it interacts with dynamic controls (your added controls.)
Unfortunately, we can not answer this question without understanding whether the values in these "dynamically" created controls are used server-side or client-side.
I can guess that:
1) If they are for client side consumption/usage, you are doing wrong. If you need more info ask another (more specific) question.
2) If they are for server side consumption/usage, you must only allow one new record per postback, and should include a "Save" button that will save the current "new" row and cause a page refresh that the user can add a new entry to.
3) See rule #0 (Every rule has exceptions. {This rule applies in so far that you understand the problem at hand.})
Monk
Related
I have two dropdownlist in my project and get the items from sql server. One of them show list of something (And I named it DropdownSoore) and another one show list of members of it (And I named it DropdownAye) that update when select an item from DropdownSoore. then when select an item from DropdownAye go to page of them by forwarding query string. I have no problem to updating DropdownAye but when I select item from it and redirect to page of it, the DropdownAye lose the selected item and show the first item (as default) and index to query string in url.
what should I do?
Excuse me about my grammer, I'm not English...
I set DropdownSoore:
<asp:DropDownList ID="DropDownListSoore" runat="server" class="nav-link btn btn-outline-secondary dropdown-toggle" aria-haspopup="true" aria-expanded="true" AutoPostBack="True"></asp:DropDownList>
for (int i = 0; i <= dt.Rows.Count - 1; i++)
{
string IdSoore = Convert.ToString(dt.Rows[i]["IdSoore"]);
string NameSoore = Convert.ToString(dt.Rows[i]["NameSoore"]);
DropDownListSoore.Items.Add(IdSoore + "." + NameSoore);
}
string forwardedIdSoore = Request.QueryString["IdSoore"];
if (forwardedIdSoore != null)
{
DropDownListSoore.Items.FindByValue(forwardedIdSoore);
}
And then set DropdownAye:
<asp:DropDownList ID="DropDownListAye" runat="server" class="nav-link btn btn-outline-secondary dropdown-toggle" aria-haspopup="true" aria-expanded="true" OnSelectedIndexChanged="DropDownListAye_SelectedIndexChanged" AutoPostBack="True"></asp:DropDownList>
DropDownListAye.Items.Clear();
for (int i = 0; i <= dt1.Rows.Count - 1; i++)
{
DropDownListAye.Items.Add(Convert.ToString(dt1.Rows[i]["NumberAye"]));
}
int iSelectedAye = Convert.ToInt32(DropDownListAye.SelectedIndex) + 1;
string SelectedAye = Convert.ToString(dt1.Rows[iSelectedAye]["IdAye"]);
Session["SSelectedAye"] = SelectedAye;
string forwardedIdAye = Request.QueryString["IdAye"];
if (forwardedIdSoore != null)
{
DropDownListSoore.Items.FindByValue(forwardedIdAye);
}
in page_Load. and dont have problem to update DopdownAye but when I used it to redirect always get IdAye=1 in url and show the first item of DropdownAye ...:
protected void DropDownListAye_SelectedIndexChanged(object sender, EventArgs e)
{
int SelectedAye = Convert.ToInt32(Session["SSelectedAye"]);
int SelectedSoore = Convert.ToInt32(DropDownListSoore.SelectedIndex) + 1;
string forward = "~/contentAye.aspx?IdSoore=" + SelectedSoore + "&IdAye=" + SelectedAye;
Response.Redirect(forward);
}
I try !IsPostBack but dont have utility too.
I do all thing I think and i dont know what shoild i do.
And where are you loading up these infomration?
Remember, page load ALWAYS fires and triggers for each post-back, for each button click, and any OTHER event on the page that calls code behind.
What does the above really mean?
Well, it means that you need to ONLY ONE TIME load up the drop downs, the gridview(s), or whatever else you have.
So, for the last 200+ web pages I have built, I ALWAYS have this code stub in the page load event:
so this markup:
<h3>Select Hotel</h3>
<asp:DropDownList ID="DropDownList1" runat="server"
DataValueField="ID"
DataTextField="HotelName" Width="356px">
</asp:DropDownList>
<br />
<br />
<asp:Button ID="Button1" runat="server"
Text="Show/get/display drop selection" OnClick="Button1_Click"
CssClass="btn" />
<br />
<h3>Selected result</h3>
<asp:Label ID="Label1" runat="server" Text=""></asp:Label>
and code behind:
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
// load up data, controls etc.
string strSQL =
"SELECT ID, HotelName FROM tblHotelsA ORDER BY HotelName";
DataTable dt = General.MyRst(strSQL);
DropDownList1.DataSource = dt;
DropDownList1.DataBind();
DropDownList1.Items.Insert(0, new ListItem("Please Select Hotel", ""));
}
}
protected void Button1_Click(object sender, EventArgs e)
{
string sResault =
$#"dropdown list value (pkid) = {DropDownList1.SelectedItem.Value}
<br/>
Dropdown list Text (Hotel) = {DropDownList1.SelectedItem.Text}";
Label1.Text = sResault;
}
and the result:
SUPER important:
If I leave out the !IsPostBack test in page load, then for EVERY button click, the dropdown list re-load code will trigger, and runs BEFORE your button stubb, or post back. As a result, I will see/find/get no valid value from the drop down list, since page load ALWAYS runs before the given code stub, and thus if page load "every time" re-loads the data, then it will blow out the choosen value in the drop down list.
So, loading up of grids, data, dropdowns etc. can ONLY occur one time in page load, since page load ALWAYS runs before each button or event code stub you have behind.
If you thus only load up the controls and dropdowns on the first REAL page load, then the values the user selects in those dropdowns should work, but only will work if you ALWAYS include that all important if (!IsPostBack) code stub in your page load event.
Now, of course in place of that button to loadup + display the dropdown list into that label?
Well, of course that button click may well (like often) jump or navigate to another page. So, in place of that code to fill out the selected value(s) into that lable, we could pass the selected value to the next page. And how to do that? Well, you can use session(), you can use parmaters in the URL, or you can even use a post-back URL, and then the WHOLE previous page becomes available (Page.Previous), which then in theory allows you to grab ANY and ALL values from the previous page.
but, say we choose session() to pass the value, then this:
Session["HotelPK"] = DropDownList1.SelectedItem.Value;
Response.Redirect("GridFun3.aspx");
I'm not quite sure how to solve this problem.
What I'm trying to do:
Go through a gridview with multiple rows, and force the user to enter in specific values if certain textboxes or drop down lists are left on the default value.
What I tried:
Tried client side requirements, by use of the focusout event in javascript. Focus goes onto these fields depending on the previous selections users make, that part works. My code:
var row = $(this).closest('tr');
var text = $(row).find("input[name*='txtCurriculum']").val('enter lesson/unit name here');
var ddl = $(row).find("select[name*='ddlCurrAdditions']");
$(ddl).focusout(function () {
if (ddl.val == "-select curriculum name-") {
$(ddl).focus();
alert("select a curriculum name before moving on");
}
});
$(text).focusout(function () {
if (text.val == 'enter lesson/unit name here') {
$(text).focus();
alert('enter in a lesson or unit name before moving on');
}
});
It never fired properly. the ddl focusout would fire, but wouldn't stop firing until I exited and went back to the page. the text focusout would never fire.
Tried server side validation, with the custom validation feature.
protected void cv1_ServerValidate(object source, ServerValidateEventArgs args)
{
CustomValidator cv = ((CustomValidator)(source));
GridViewRow gvr;
//if (FormCGrid.Rows)
foreach (GridViewRow row in FormCGrid.Rows)
{
//string ddl = ((DropDownList)row.FindControl("ddlCurrAdditions")).SelectedItem.Text;
string txtC = ((TextBox)row.FindControl("txtCurriculum")).Text;
bool ddl = ((DropDownList)row.FindControl("ddlCurrAdditions")).Visible;
if (txtC == "enter lesson/unit name here")
{
args.IsValid = false;
cv.ErrorMessage = string.Format("Please enter the Additional program name on row {0}", row.RowIndex);
}
else if (ddl == true)
{
cv.ErrorMessage = string.Format("Enter your lesson or unit for the curriculum selected on row {0}", row.RowIndex);
args.IsValid = false;
}
}
}
I tried to loop through the rows of the gridview here, checking the named dropdown for visibility (hidden until another dropdownlist reveals it, dependent on the value chosen there) and the textbox for the default value, yet this did not work either. It never fired at all. Any suggestions welcome.
I am generally ok with most programming/coding issues, but I find that working with the asp.net gridview, all of my techniques get thrown out the window.
Ok, there are two ways to do this, and you can use js quite easy here. However, lets go with a 100% server side code. I think sticking to server side code is easier.
Next up? I actually STONG suggest to use a listview in place of a gridview WHEN you plan to edit data. The reason is simple - the ListView does not require those "irratating" "item template" over and over for each asp.net control you want to drop/have in that grid display. Now to be fair, say 2-3 controls - then grid view is ok, but for more, then I really find that the listview starts to win here big time (no need for the item template).
The "trick" with listview is of course to setup a datasoruce on the web page, let it generate the markup, and then blow out (delete) all templates except for the item template. And then delete the datasourc1 item on the page, and remove the datasource1 setting from the list view.
However, lets go with the grid view - this will thus be some what "more messy"
So, we going to fill out the grid - and say we want a city combo box - MUST be selected. And lets toss in a basic check for say LastName also must be entered.
So, we have a city combo box (database driven). And say lets toss in a drop down for a hotel rating (1-5) - but that's only 5 choices - so we have that dropdown with the list in mark-up. (so two combo examples to show how this works).
So, say we have a grid like this, and we hit save, we get this result:
Now, of course we would fancy up that message box area. And in fact, we could VERY easy drop in jQuery.UI and make that cute div on the right appear in a dialog box (and we can trigger that box + jQuery.UI dialog display even from our server side code behind - but lets try and keep this short - but I would fancy up that error message area).
Ok, first the markup:
Just a simple grid, but "a bit messy" due to those "item Templates - (if you not figured this out, I really dislike those template things!!! - and with listview you don't need them!! - just drop in pure clean asp.net controls for the grid). Anyway:
<asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False" DataKeyNames="ID">
<Columns>
<asp:BoundField DataField="ID" HeaderText="ID" InsertVisible="False" ReadOnly="True" SortExpression="ID" />
<asp:TemplateField HeaderText="HotelName" >
<ItemTemplate><asp:TextBox id="HotelName" runat="server" Text='<%# Eval("HotelName") %>' /></ItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="FirstName" >
<ItemTemplate><asp:TextBox id="FirstName" runat="server" Text='<%# Eval("FirstName") %>' /></ItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="LastName" >
<ItemTemplate><asp:TextBox id="LastName" runat="server" Text='<%# Eval("LastName") %>' /></ItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="City">
<ItemTemplate>
<asp:DropDownList ID="cboCity" runat="server" DataTextField="City"
DataValueField = "City" Width="110px">
</asp:DropDownList>
</ItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="Active">
<ItemTemplate><asp:CheckBox id="Active" runat="server" Checked = '<%# Eval("Active") %>' /></ItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="Rating">
<ItemTemplate>
<asp:DropDownList ID="cboRating" runat="server" SelectedValue = '<%# Eval("Rating") %>' >
<asp:ListItem Value="1">Poor</asp:ListItem>
<asp:ListItem Value="2">Just Ok</asp:ListItem>
<asp:ListItem Value="3">Ok</asp:ListItem>
<asp:ListItem Value="4">Good</asp:ListItem>
<asp:ListItem Value="5">Excellent</asp:ListItem>
</asp:DropDownList>
</ItemTemplate>
</asp:TemplateField>
</Columns>
</asp:GridView>
</div>
<div id="ErrorDialog" style="display:normal;float:left;padding-left:30px;width:20%" runat="server">
<asp:TextBox ID="txtErrors" runat="server" TextMode="MultiLine" Style="width:100%" Rows="20" ></asp:TextBox>
</div>
<div style="clear:both;padding-top:20px">
<asp:Button ID="cmdSave" runat="server" Text="Save" />
<asp:Button ID="cmdUnDo" runat="server" Text="UnDo" Style="margin-left:20px"/>
</div>
Ok, now on SO? Gee, that is about the total max markup I would post in one shot - beyond above, we approach the "not very nice" post territory.
Ok, so lets look at the code to load up this grid. We have this code:
public class EditHotelsG : System.Web.UI.Page
{
private DataTable rstTable = new DataTable();
private DataTable rstCity = new DataTable();
protected void Page_Load(object sender, System.EventArgs e)
{
if (System.Web.UI.Page.IsPostBack == false)
{
ErrorDialog.Visible = false;
LoadGrid();
ViewState["rstTable"] = rstTable;
ViewState["rstCity"] = rstCity;
}
else
{
rstTable = ViewState["rstTable"];
rstCity = ViewState["rstCity"];
}
}
public void LoadGrid()
{
// load up our drop down list from database (city)
string strSQL;
strSQL = "SELECT City from tblCity Order by City";
using (SqlCommand cmdSQL = new SqlCommand(strSQL, new SqlConnection(My.Settings.TEST3)))
{
cmdSQL.Connection.Open();
rstCity.Load(cmdSQL.ExecuteReader);
// now load up main grid view
strSQL = "SELECT ID, FirstName, LastName, HotelName, City, Active, Rating from tblHotels ORDER BY HotelName";
cmdSQL.CommandText = strSQL;
rstTable.Load(cmdSQL.ExecuteReader);
GridView1.DataSource = rstTable;
GridView1.DataBind();
}
}
protected void GridView1_RowDataBound(object sender, GridViewRowEventArgs e)
{
// set combo box data source
if (e.Row.RowType == DataControlRowType.DataRow)
{
// setup city drop down list
DropDownList cboDrop = e.Row.FindControl("cboCity");
cboDrop.DataSource = rstCity;
cboDrop.DataBind();
cboDrop.Items.Insert(0, new ListItem(string.Empty)); // add blank row
cboDrop.Text = rstTable.Rows(e.Row.RowIndex).Item("City").ToString;
}
}
}
Ok, now that is not all that much code. And a good part was setting up that combo box. Since the cbo box (dropdown list) was database driven, then we have to bind + setup for each row. So, that cost us a good chunk of our coding time.
Note however how we persist the database soruce (table). We do that, since we want to with A LOT LESS code, update the whole database behind in ONE shot. By peristing that table, then we
Transfer data from Grid to table
Save the table to database in one shot
So, this reduces our coding efforts by a truckload.
The code to save our data, thus becomes this gem:
(and note how we ALSO put in our validation code).
protected void cmdSave_Click(object sender, EventArgs e)
{
// pull gride rows back to table.
bool bolDataOk = true;
txtErrors.Text = "";
foreach (GridViewRow rRow in GridView1.Rows)
{
int RecordPtr = rRow.RowIndex;
DataRow OneDataRow;
OneDataRow = rstTable.Rows(RecordPtr);
OneDataRow.Item("HotelName") = rRow.FindControl("HotelName") as TextBox.Text;
OneDataRow.Item("FirstName") = rRow.FindControl("FirstName") as TextBox.Text;
OneDataRow.Item("LastName") = rRow.FindControl("LastName") as TextBox.Text;
OneDataRow.Item("City") = rRow.FindControl("cboCity") as DropDownList.Text;
OneDataRow.Item("Active") = rRow.FindControl("Active") as CheckBox.Checked;
OneDataRow.Item("Rating") = rRow.FindControl("cboRating") as DropDownList.SelectedValue;
// check for missing City, or LastName
if (IsDBNull(OneDataRow("City")) | OneDataRow("City") == "")
{
bolDataOk = false;
if (txtErrors.Text != "")
txtErrors.Text += Constants.vbCrLf;
txtErrors.Text += "Missing City on row = " + (rRow.RowIndex + 1).ToString;
}
if (IsDBNull(OneDataRow("LastName")) | OneDataRow("LastName") == "")
{
bolDataOk = false;
if (txtErrors.Text != "")
txtErrors.Text += Constants.vbCrLf;
txtErrors.Text += "Missing lastName on row = " + (rRow.RowIndex + 1).ToString;
}
}
ErrorDialog.Visible = !(bolDataOk);
// now send table back to database with updates
if (bolDataOk)
{
string strSQL = "SELECT ID, FirstName, LastName, City, HotelName, Active, Rating from tblHotels WHERE ID = 0";
using (SqlCommand cmdSQL = new SqlCommand(strSQL, new SqlConnection(My.Settings.TEST3)))
{
cmdSQL.Connection.Open();
SqlDataAdapter daupdate = new SqlDataAdapter(cmdSQL);
SqlCommandBuilder cmdBuild = new SqlCommandBuilder(daupdate);
daupdate.Update(rstTable);
}
}
}
Now, once again, that above code snip is a wee bit long - and is aobut the max size of a code snip for SO. I would perhaps consider breaking out that above last code into 2 or even 3 routines.
but, all in all, the above shows quite a few tricks, and approaches here.
As noted, I was going to post how you can/could verify using client side code, ,but the OVER ALL idea of saving back the whole grid with one database update? Well, I would use that trick with a listview, gridview, and even if I was doing client side js verification? I will still use the bulk of the above design pattern.
And the un-do button? Well, I never finished that part - but it would just call the same code as the first page load (postback - false, and you would simply re-bind the grid from the database source for the un-do button.
Lets preface this with the fact that I am learning ASP.NET C# and this is my first "real" project so there is a good chance I am missing something obvious, I apologize in advance.
I am working on a web page that displays three columns. The first is "Categories", a user should be able to select a category then have a list of items to choose from appear in the second column "Items". When they click an item the third column should show details about said item. For the most part this is a classic Master/Detail scenario except we take it a step further and do Master/Detail/Detail.
To achieve this I am generating dynamic buttons on Page_Load() in the "Categories" column. In addition I have added a debug line when the page loads, this is important later.
protected void Page_Load(object sender, EventArgs e)
{
//DB query to get categories omitted
for (int i = 0; i < categories.Rows.Count; i++)
{
Button btn = new Button();
btn.Click += new System.EventHandler(CategorySelected_Click);
btn.Attributes["runat"] = "server";
btn.ID = "CatSelBtn" + i;
btn.Attributes["data-categoryid"] = qry.GetCategories().Rows[i]["id"].ToString();
//And some other non-relevant attributes
CategoriesPane.Controls.Add(btn);
}
System.Diagnostics.Debug.WriteLine("Page Loaded");
}
As you may have noticed these buttons have a Click event handler that calls the method CategorySelected_Click(). These buttons all generate successfully and clicking on them results in that method being successfully called. This method is set up in a similar fashion, it grabs a list of items then generates buttons for the items, of course this needs to be done asynchronously so it doesn't reset the user's category selection, so this time it is all contained with an update panel.
C#
protected void CategorySelected_Click(object sender, EventArgs e)
{
//DB query to get items omitted
Button btn = (sender as Button);
string categoryid = btn.Attributes["data-categoryid"].ToString();
for (int i = 0; i < items.Rows.Count; i++)
{
if (items.Rows[i]["Category"].ToString() == categoryid)
{
Button ibtn = new Button();
ibtn.Click += new System.EventHandler(this.ItemSelected_Click);
ibtn.Attributes["runat"] = "server";
ibtn.ID = "ItmSelBtn" + i;
ibtn.Attributes["data-itemid"] = qry.GetItems().Rows[i]["id"].ToString();
//And again some none relevant attributes here
ItemsParent.Controls.Add(ibtn);
}
}
ItemsPanel.Update();
}
ASP
<div class="col-md-2 items-pane">
<asp:UpdatePanel ID="ItemsPanel" runat="server" UpdateMode="Conditional" ChildrenAsTriggers="False">
<ContentTemplate>
<div id="ItemsParent" runat="server">
</div>
</ContentTemplate>
</asp:UpdatePanel>
</div>
<div class="col-md-8 view-pane">
<asp:UpdatePanel ID="ItemDetailsPanel" runat="server" UpdateMode="Conditional" ChildrenAsTriggers="False">
<ContentTemplate>
<div id="ItemDetailsParent" runat="server">
</div>
</ContentTemplate>
</asp:UpdatePanel>
</div>
Again this generates a list of buttons for each item matching the correct category. No issue there, but this time I need the clicked button to call the third and final method which will display the details for the item. This is where things stop working. I assumed that because I was able to generate buttons successfully on Page_Load() that it would work the same inside an update panel. Right now the third method just contains a debug line to check if its firing at all.
protected void ItemSelected_Click(object sender, EventArgs e)
{
System.Diagnostics.Debug.WriteLine("Item has been selected");
ItemDetailsPanel.Update();
}
In my output console in visual studio when I click on an Item Button control it writes Page Loaded indicating a successful postback but I am not seeing Item has been selected indicating that the third method is firing. I also inserted a breakpoint there but it is not being reached.
I initially thought I needed to add an asyncpostback trigger for each button generated to my update panel but that did not seem to resolve that issue, and because I can now see that Page_Load() is getting triggered I am pretty sure that isn't the issue. This leads me to believe that the click event is somehow not being registered. So my question to you is this: How do I make a dynamically generated button inside an update panel call a server side method? Any help is greatly appreciated.
You need to attach the event handlers on every postback.
It works for your categories-buttons, because the attaching is executed on every page load.
Do this for all the other items also, e.g. put in your Page_Load something like this:
foreach (var ctrl in ItemsParent.Controls)
{
Button ibtn = ctrl as Button;
if (ibtn != null)
{
ibtn.Click += new System.EventHandler(this.ItemSelected_Click);
}
}
I am dynamically added textboxes through code behind on button click and it is working fine. On another button click I want to get the values all the dynamically added textbox. But the value of the textboxes is empty. How to retain the value of the dynamically added textbox on postback?
Aspx code
<asp:Table runat="server" id="placeAddTds">
<asp:TableRow>
<asp:TableCell><asp:TextBox ID="txt_from" runat="server" class="sabsw" TabIndex="88"></asp:TextBox></asp:TableCell>
<asp:TableCell><asp:TextBox ID="txt_to" runat="server" class="sabsw" TabIndex="89"></asp:TextBox></asp:TableCell>
<asp:TableCell><asp:TextBox ID="txt_per" runat="server" class="sabsw" TabIndex="90"></asp:TextBox></asp:TableCell>
<asp:TableCell>
<asp:Button class="main-btn add-btn pointer" runat="server" ID="btn_add" Text="Add" OnClick="TdsAddClick" TabIndex="91"></asp:Button>
</asp:TableCell>
</asp:TableRow>
</asp:Table>
Code behind:
protected void TdsAddClick(object sender,EventArgs e)
{
try
{
TextBox[] l1 = new TextBox[txtcount];
TextBox[] l2 = new TextBox[txtcount];
TextBox[] l3 = new TextBox[txtcount];
for (int j = 1; j < txtcount; j++)
{
l1[j] = new TextBox();
l2[j] = new TextBox();
l3[j] = new TextBox();
l1[j].ID = "txt_from" + j;
l1[j].Attributes.Add("class","sabsw");
l2[j].ID = "txt_to" + j;
l2[j].Attributes.Add("class", "sabsw");
l3[j].ID = "txt_per" + j;
l3[j].Attributes.Add("class", "sabsw");
placeAddTds.Rows.Add(new TableRow());
placeAddTds.Rows[j].Cells.Add(new TableCell());
placeAddTds.Rows[j].Cells[0].Controls.Add(l1[j]);
placeAddTds.Rows[j].Cells.Add(new TableCell());
placeAddTds.Rows[j].Cells[1].Controls.Add(l2[j]);
placeAddTds.Rows[j].Cells.Add(new TableCell());
placeAddTds.Rows[j].Cells[2].Controls.Add(l3[j]);
}
txtcount++;
}
catch (Exception ex)
{
}
}
Thanks,
I hope this is his Requirement from your comment.
I have two buttons. On click of one button I want to add textboxes dynamically and on click of the second button I want to save the value of all the textboxes in the database
You have 2 buttons
One button you can create TextBox using Jquery. (Client Side script is very much faster while comparing with postbacks).
Here you can see how to add text box using Jquery
So you can add textbox dynamically without postback.
Another button you can serialize the form using Jquery and send it to server.
Pass Data to Server
It will create a JSON Object and pass it to server.
You can pass in __doPostBack(control,args). In args you can pass the JSON Value.
Click Here
Process the JSON Data
C# JSON custom serialization
This will show how to get data from JSON .
This is an approach which can be implemented with client side scripting and which may works much faster than the method you suggest.
Thanks
You can add a property with ViewState for this in your code-behind and store all values in a list:
private List<string> TextBoxValue
{
get
{
return (List<string>)ViewState["TextBoxValue"];
}
set
{
ViewState["TextBoxValue"] = value;
}
}
You can then store the values of textboxes in this property. example:
TextBoxValue.Add(textBox1.Text);
However, this will make page heavy.
There are lots of examples on this, and I'm pretty sound with the concept of using recursion to find the control. Once the control has been found on postback, it can be interacted with, etc.
I have an empty asp:table in my HTML markup:
<asp:Table ID="editDataTable" runat="server">
</asp:Table>
And on Page_Load the table is populated with many rows and many columns (I am quite proud that I figured that out). Inside some of the table cells there is a <asp:TextBox />.
You've guessed it, I need to get the value of these text boxes!
(I've got the code for recursion and checked it, and it seems good.)
My table has two columns. The left one contains titles like "Company Name, Telephone", etc. and the right column contains text boxes with its respective title's value. So a user can edit the text box (if the telephone number has change for example) and submit the changes.
Obviously the rows are dynamically added depending on the user.
The problem I'm having is: You need to ADD the control to the page when populating the table. Something along the lines of:
myTable.Control.Add(new TextBox());
In my case, my Table is called editDataTable. So in my code where I add the Rows, I've added the control too, as follows.
for (int i = 0; i < rowCount; i++)
{
editDataTable.Rows.Add(tblRow[j]); // This is where I add the ROW to my sexy table
editDataTable.Controls.Add(new TextBox()); // This is where I add the control
}
Those awake amongst you will know that you can't add a text box control to a table!
So finally, my questions are:
How do I add the control for The Text Boxes in my table?
Where do I add them?
Is there any extra advice to help me fulfill my quest of retrieving the text values of my dynamically added text boxes?
Here's my recursive code just in case you were curious:
private void getControls(Control parent)
{
foreach (Control c in parent.Controls)
{
if (c is TextBox && c.ID == null)
{
//Stuff
}
if (c.Controls.Count > 0)
{
getControls(c);
}
}
}
Here is an example of building a dynamic table in ASP.NET during PageLoad, and reading in the values through a postback. Since the table is dynamic, it will NOT be rebuilt when the page is postback to the server. If you want to rebuild the table, you'll need to render it again and use the values you pull in from Request.Form to re-populate it.
HTML Markup
<asp:Table ID="editDataTable" runat="server">
</asp:Table>
<asp:Button runat="server" Text="Submit" OnClick="Submit_Click" />
Code Markup
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
string[] dynamicTable = { "First Name", "Last Name", "Address", "Phone" };
foreach (string s in dynamicTable)
{
TableRow row = new TableRow();
// Add First Cell with Text to Row
row.Cells.Add(new TableCell() { Text = s });
// Create Second Cell
TableCell textBoxCell = new TableCell();
// Add Textbox Control to Second Cell
textBoxCell.Controls.Add(new TextBox() { ID = "Dynamic_" + s.Replace(" ","_") });
// Add Second Cell to Row
row.Cells.Add(textBoxCell);
// Add New Row to Table
editDataTable.Rows.Add(row);
}
}
}
protected void Submit_Click(object sender, EventArgs e)
{
for (int i = 0; i < Request.Form.Count; i++)
{
string key = Request.Form.GetKey(i);
if (key.Contains("Dynamic_"))
{
Response.Write("<p><strong>" + (key.Remove(0,8)).Replace("_"," ") + "</strong> :: " + Request.Form[i] + "</p>");
}
}
}
You need to add a TableCell to the TableRow.Cells collection, and add the TextBox to the TableCell.Controls collection:
TableCell cell = new TableCell();
cell.Controls = new TextBox();
tblRow[j].Cells.Add(cell);
editDataTable.Rows.Add(tblRow[j]);
The most direct way to access the textboxes is to keep them all in a list:
List<TextBox> textBoxes = new List<TextBox>();
and replace cell.Controls = new TextBox(); above with this:
TextBox tb = new TextBox();
textBoxes.Add(tb);
cell.Controls = tb;
And then you can iterate through the textBoxes afterward without having to search for them in a tree.