Selecting a dynamically created table row in ASP.NET with C# - c#

I am writing a web application in Visual Studio 2015 pro using C# and ASP.NET. Right now, I have it set up where the user will click a button and the C# code will go get a bunch of data then display it back to the user in tables. I have spent a day of work trying to figure out how to add some form of a clickable event to the table rows but have had no success. Ultimately, what I want to do is call a method in my C# code when the table row is clicked and send it the row index.
Here is the C# code I am using for generating the tables:
protected void searchButton_Click(object sender, EventArgs e)
{
try
{
// Remove the error display
resultListLabel.Style.Add("Display", "None");
// Get document groups
groups = TableCommands.getGroups(dbConn, "retriever", searchTextOne.Text, searchTextTwo.Text);
foreach (var dataPair in groups)
{
// Get data pair group names into list
List<string> name = dataPair.Key.Split('|').ToList();
// ====== Make table
Table resultTable = new Table();
resultTable.Attributes["class"] = "displayTable";
resultList.Controls.Add(resultTable);
// ====== Add table info row
TableRow groupInfo = new TableRow();
groupInfo.Attributes["class"] = "groupInfoLabel";
// add row to table
resultTable.Rows.Add(groupInfo);
// create cell with information
TableCell infoCell = new TableCell();
infoCell.Text = "MRN: "+name[0]+", Name: " + name[1];
infoCell.ColumnSpan = 3;
// add cell to row
groupInfo.Cells.Add(infoCell);
// ====== Make column label row
TableRow labelRow = new TableRow();
labelRow.Attributes["class"] = "columnLabel";
// add row to table
resultTable.Rows.Add(labelRow);
// make an array of column lables
string[] cellNames = new string[] { "Visit Date", "Document Type", "Doctor ID" };
// add column lables to row
foreach (string s in cellNames)
{
TableCell labelCell = new TableCell();
labelCell.Text = s;
labelRow.Cells.Add(labelCell);
}
// Add display names to table
foreach(var nameList in dataPair.Value)
{
TableRow nameRow = new TableRow();
nameRow.Attributes["class"] = "columnInfo";
for (int i = 0; i < 3; i++)
{
TableCell nameCell = new TableCell();
nameCell.Text = nameList[i];
nameRow.Cells.Add(nameCell);
}
resultTable.Rows.Add(nameRow);
}
}
}
catch(Exception ex)
{
// Display the error and write to log
resultListLabel.Style.Add("Display", "Inline-Block");
writeLog("Failed to generate tables", ex.ToString());
}
}

Here's what I had to do:
Add a global boolean to called tableCall to the C# code
Move the code in searchButton_Click into an if(tableCall) statement in the Page_Load method.
protected void Page_Load(object sender ,EventArgs e){
...
if(tableCall){
//Do stuff from searchButton_Click
}
...
}
Add tableCall = true; and Page_Load(sender, e) to searchButton_Click
Modify the for loop to add buttons in the cell like so:
// Add display names to table
foreach (var nameList in dataPair.Value)
{
TableRow nameRow = new TableRow();
nameRow.Attributes["class"] = "columnInfo";
// Add display names to table
foreach (var nameList in dataPair.Value)
{
TableRow nameRow = new TableRow();
nameRow.Attributes["class"] = "columnInfo";
for (int i = 0; i < 3; i++)
{
TableCell nameCell = new TableCell();
nameRow.Cells.Add(nameCell);
Button b = new Button();
b.Attributes["class"] = "docButton";
b.Attributes.Add("DWdocid", nameList[3]);
b.Text = nameList[i];
b.Click += new EventHandler((s, ea) => test(s, ea, b.Attributes["DWdocid"]));
nameCell.Controls.Add(b);
}
resultTable.Rows.Add(nameRow);
}
This adds a button to each of the three cells in the row, but adds the same document id to each of the buttons in that row, so anywhere the user clicks on that row (except for the 1px borders) will call a method in the C# code while passing it the document ID. I'm sure that with better css skills somebody could make the button span all three cells.

Related

Asp.net: How to add event handler referencing dynamically created elements

I have an asp.net program that creates a simple survey form for users to answer.
Most of the questions use a dropdownlist with answer scores from 1-5 (bad-good) and I'm trying to add an event handler to the dropdownlist objects so that the comments box is only enabled if the user selects a score between 1 and 2.
However when I add the delegate lambda call for the event handler, instead of each dropdownlist affecting their own corresponding comment box, they all seem to point only at the last one added (and they work once, then no more and only the last ddl continues having the expected behaviour).
My code:
//Called from Page_Load
private void PopulateSurvey()
{
btnSubmit.Enabled = true;
List<Question> questions = (from p in context.Questions
join q in context.Survey_Questions on p.ID equals q.QuestionID
where q.SurveyID == surveyid
select p).ToList();
Table tbl = new Table();
tbl.Width = Unit.Percentage(100);
TableRow tr;
TableCell tc;
TableCell tc1;
TableCell tc2;
TextBox txt;
CheckBox cbk;
DropDownList ddl = new DropDownList();
foreach (Question q in questions)
{
if (q.Division.Equals("General") || q.Division.Equals(ddlDivisions.SelectedValue.ToString()))
{
tr = new TableRow();
tc = new TableCell();
tc.Width = Unit.Percentage(55);
tc.Text = q.Text;
tc.Attributes.Add("id", q.ID.ToString());
tr.Cells.Add(tc);
tc = new TableCell();
if (q.QuestionType.ToLower() == "singlelinetextbox")
{
txt = new TextBox();
txt.ID = "txt_" + q.ID;
//txt.Width = Unit.Percentage(40);
tc.Controls.Add(txt);
}
if (q.QuestionType.ToLower() == "multilinetextbox")
{
txt = new TextBox();
txt.ID = "txt_" + q.ID;
txt.TextMode = TextBoxMode.MultiLine;
//txt.Width = Unit.Percentage(40);
tc.Controls.Add(txt);
}
if (q.QuestionType.ToLower() == "singleselect")
{
ddl = new DropDownList();
ddl.ID = "ddl_" + q.ID;
//ddl.Width = Unit.Percentage(41);
if (!string.IsNullOrEmpty(q.Options))
{
string[] values = q.Options.Split(',');
foreach (string v in values)
ddl.Items.Add(v.Trim());
}
ddl.AutoPostBack = true;
tc.Controls.Add(ddl);
}
//tc.Width = Unit.Percentage(60);
tr.Cells.Add(tc);
//Add comment row
tc1 = new TableCell();
//tc.Width = Unit.Percentage(5);
tc1.Text = "Comentario: ";
tc1.Attributes.Add("id", q.ID.ToString());
//tc1.Visible = false;
tr.Cells.Add(tc1);
tc2 = new TableCell();
txt = new TextBox();
txt.ID = "txt_" + q.ID + "comment";
txt.TextMode = TextBoxMode.SingleLine;
//txt.Width = Unit.Percentage(25);
tc2.Controls.Add(txt);
tr.Cells.Add(tc2);
ddl.SelectedIndexChanged+= (sender, e) => ScoreChanged(sender, e, tc1,tc2, ddl.SelectedIndex);
tbl.Rows.Add(tr);
}
}
pnlSurvey.Controls.Add(tbl);
}
protected void ScoreChanged (object sender, EventArgs e, TableCell tc1, TableCell tc2, int score)
{
if( score <2)
{
tc1.Visible = false;
tc2.Visible = false;
}
else
{
tc1.Visible = true;
tc2.Visible = true;
}
}
First thing that comes to mind is maybe the event handler keeps the references themselves,m instead of the orphaned objects created with each question, so all event handlers end up with the same tc1 and tc2 reference and thus only the last object? Is that the case and if so how do I go around it?
Your problem is in this line:
ddl.SelectedIndexChanged +=
(sender, e) => ScoreChanged(sender, e, tc1,tc2, ddl.SelectedIndex);
Two things are important here:
You are using an anonymous method to invoke ScoreChanged.
You are passing tc1 and tc2 as an argument to the ScoreChanged method. You have defined those variables at the beginning of your code block, outside the loop.
The magic word in this context is closure. Since tc1 and tc2 are defined outside the scope of the anonymous method, they turn into closures. That means the will not have the value at the time you define the method, but at the time you invoke it. Since you are constantly overwriting the value of the variables in the foreach loop, at the time of the invokation those variables will have the value of the last iteration of the loop.
The solution is simple: Declare the variables inside the loop. This will create a new closure for each iteration of the foreach:
TableRow tr;
TableCell tc;
TextBox txt;
CheckBox cbk;
foreach (Question q in questions)
{
TableCell tc1;
TableCell tc2;
DropDownList ddl; //Don't forget to include ddl, since you are using its selected index
//...
On a more general note: Don't do "declaration" blocks like this at the beginning of a method in C#. Declare the variable the first time you use it (unless there is a good reason to do otherwise, for example you want it to be a part of a closure). There are many good reasons for that and you just have experienced one of them. Another one would be that when you turn a part of your code to a method with Visual Studio's refactoring feature, you will pass the predeclared variables as ref params. These are the most obvious reasons. There are more.

Use dynamic table to display list of string

I tried to display the list of time slots by using dynamic table in C#. However, it does not work properly. Here is my result:
And here is my code
List<AvailableTime> AllTimeSlots = RequestDirector.ListAllAvailableTimes(BookingDate);
foreach (AvailableTime resultTimeslot in AllTimeSlots)
{
TableRow TimeSLotRow = new TableRow();
TableCell TimeSlotCell = new TableCell();
TimeSlotCell.Text = Convert.ToString(resultTimeslot.TimeSlot);
TimeSLotRow.Cells.Add(TimeSlotCell);
Table1.Rows.Add(TimeSLotRow);
}
I want to display 5 time slots each row. Can anybody tell me how I can do that?
Probably something like this :
int i = 0;
TableRow TimeSLotRow = new TableRow();
foreach (AvailableTime resultTimeslot in AllTimeSlots)
{
TableCell TimeSlotCell = new TableCell();
TimeSlotCell.Text = Convert.ToString(resultTimeslot.TimeSlot);
TimeSLotRow.Cells.Add(TimeSlotCell);
i++;
if(i == 5)
{
Table1.Rows.Add(TimeSLotRow);
TimeSLotRow = new TableRow();
i = 0;
}
}

How do I make this table shorter?

I think I'm doing something terribly wrong here. I have a object inside import called oCultivationPlan. It contains data obviously. And I want to create a table which shows the data inside it. However I only want a selected few from that object and not all the data in it. Is there a way to make this shorter? I thought about using foreach or for, but that would loop through all the data inside the object :/ while I only want a selected few.
TableRow tblRow = new TableRow();
TableCell tblc = new TableCell();
tblc.Controls.Add(new LiteralControl("ID"));
TableCell tblc2 = new TableCell();
tblc2.Controls.Add(new LiteralControl(import.oCultivationPlan.iID.ToString()));
tblRow.Controls.Add(tblc);
tblRow.Controls.Add(tblc2);
tblImportPreview.Controls.Add(tblRow);
TableCell tblc3 = new TableCell();
TableCell tblc4 = new TableCell();
tblc3.Controls.Add(new LiteralControl("Description"));
tblc4.Controls.Add(new LiteralControl(import.oCultivationPlan.sDescription.ToString()));
TableRow tblRow2 = new TableRow();
tblRow2.Controls.Add(tblc3);
tblRow2.Controls.Add(tblc4);
tblImportPreview.Controls.Add(tblRow2);
TableCell tblc5 = new TableCell();
TableCell tblc6 = new TableCell();
tblc5.Controls.Add(new LiteralControl("DateCreated"));
tblc6.Controls.Add(new LiteralControl(import.oCultivationPlan.dDateCreated.ToString()));
TableRow tblRow3 = new TableRow();
tblRow3.Controls.Add(tblc5);
tblRow3.Controls.Add(tblc6);
tblImportPreview.Controls.Add(tblRow3);
not a foreach :) but you can use a for loop to get trough it. you should be able to use the code below as a solution for your question :)
its smaller cuz of the loop but it does the exact same thing as what you did. I use the string array to keep all the info you want to get inside the table so that it will be having something to go out after.
For each row you have you got 2 new cells in it and thats why we have the row*2 so the cells can get filled up :)
Hope it works for you and that you can use the solution :)
int _row = 1;
int _cell = 0;
string[] arr = new string[6] { "ID", import.oCultivationPlan.iID.ToString(), "Description", import.oCultivationPlan.sDescription.ToString(), "DateCreated", import.oCultivationPlan.dDateCreated.ToString() };
for (; _row <= 3; _row++)
{
TableRow tblRow = new TableRow();
for (; _cell < _row * 2; _cell++)
{
TableCell tblc = new TableCell();
tblc.Controls.Add(new LiteralControl(arr[_cell]));
tblRow.Controls.Add(tblc);
}
tblImportPreview.Controls.Add(tblRow);
}
I would create a strong typed Class
public Class ImportDto
{
public string RowIdentifier {get; set;}
public string RowValue {get; set;
}
Then as David said, write a filter function to filter data from Import class and map it to ImportValues
public List<ImportDto> FilterImportedData(Import import)
{
var importDto = new List<ImportDto>
{
new ImportDto { RowIdentifier ="ID", RowValue = import.oCultivationPlan.iID.ToString()},
new ImportDto { RowIdentifier ="Description", RowValue = import.oCultivationPlan.sDescription.ToString()}
};
}
Then in the aspx.cs class, just loop through List<ImportDto>and create LiteralControls
foreach(var dto in importDtos)
{
var row = new TableRow();
var identifierCell = new TableCell();
var valueCell = new TableCell();
identifierCell.Controls.Add(new LiteralControl(dto.RowIdentifier));
valueCell.Controls.Add(new LiteralControl(dto.RowValue ));
row.Add(identifierCell);
row.Add(valueCell);
tblImportPreview.Controls.Add(row);
}
That way all you need to do in future to add new filtered data, is to modify your mapping function and add a new ImportDto, and the it will be displayed in the frontend automatically.

Need to build table in 3 columns

Edit for clarity: I know the code has 2 columns, one for title and one for date, I am trying to change the code so it displays 3 events per row.
I am new to development and I am trying to take an ASP control used in a Sitefinity 6.1 website that currently takes data from our SQL database and outputs it into a single column and instead produce an output in 3 columns.
The .ascx looks like this
<div style="margin:10px">
<asp:MultiView ID="mvEvents" runat="server" ActiveViewIndex="0">
<asp:View ID="viewDefault" runat="server">
<asp:Table ID="tblEvents" runat="server" CellPadding="5">
</asp:Table>
</asp:View>
<asp:View ID="viewList" runat="server">
<asp:Table ID="tblEventsList" runat="server" CellPadding="5">
</asp:Table>
</asp:View>
</asp:MultiView>
</div>
The PageLoad and BuildEvents portions of the .ascx.cs code look like this
protected void Page_Load(object sender, EventArgs e)
{
try
{
CalendarDataContext db = new CalendarDataContext();
var evt = db.CalendarEvents(NumberToDisplay, Department);
foreach (CalendarEventsResult Evt in evt)
{
if (Department == "List")
{
BuildEventsList(Evt.event_name, Evt.event_start_date, Evt.event_idn, Evt.information_id);
}
else
{
BuildEvents(Evt.event_name, Evt.event_start_date, Evt.event_idn, Evt.information_id);
}
}
return;
}
catch(Exception ex)
{
}
}
protected void BuildEvents(string EvtTitle, DateTime EvtStart, int EvtIdn, int EvtInfoIdn)
{
//EvtInfoIdn shows Event Description without location. EvtInfoIdn - 1 shows location information. changing the href to eventInfoId
int EvtInfoId = EvtInfoIdn - 1;
TableRow tr = new TableRow();
tr.VerticalAlign = VerticalAlign.Top;
tr.HorizontalAlign = HorizontalAlign.Center;
TableCell tcTitle = new TableCell();
TableCell tcStart = new TableCell();
StringBuilder sb = new StringBuilder();
sb.AppendLine(ShortMonth(EvtStart.Month));
sb.AppendLine(EvtStart.Day.ToString());
tr.VerticalAlign = VerticalAlign.Bottom;
tr.Height = Unit.Pixel(80);
Literal litDate = new Literal();
litDate.Text = "<div class='EventDate'>" + sb.ToString() + "</div>";
tcStart.Controls.Add(litDate);
Literal litTitle = new Literal();
litTitle.Text = #"<div class='EventTitle'><a style='text-decoration:none;' href='http://events.website.edu/EventList.aspx?view=EventDetails&eventidn=" + EvtIdn.ToString().Trim() + "&information_id=" + EvtInfoId + "&type=&rss=rss'>";
litTitle.Text = litTitle.Text + EvtTitle + "</a></div>";
tcTitle.Controls.Add(litTitle);
tr.Cells.Add(tcStart);
tr.Cells.Add(tcTitle);
tblEvents.Rows.Add(tr);
}
I have tried adding a ColumnSpan = "3" attribute to the ascx file and also tried adding the ColumnSpan to tcStart and tcTitle in the cs file as well in addition to trying to redesign the ascx in design mode and nothing I have though of has worked. I realize this is likely an easy fix, but I am new to C# programming. I appreciate any help provided.
Try the following -- n.b. I haven't been able to test fully as I haven't mocked up your bespoke classes, e.g. CalendarDataContext.
The basic premise is that the CalandarEvents collection is processed in batches of 3 (or whatever number you choose) and that the BuildEvents method now is broken down into smaller parts, each with their own responsibility, which should help with maintenance going forward.
protected void Page_Load(object sender, EventArgs e)
{
try
{
CalendarDataContext db = new CalendarDataContext();
var evt = db.CalendarEvents(NumberToDisplay, Department);
if (Department == "List")
{
foreach (CalendarEventsResult cer in evt)
{
BuildEventsList(cer.event_name, cer.event_start_date, cer.event_idn, cer.information_id);
}
}
else
{
// Depending what type evt is may depend on how to convert to the List.
// This assumes that it supports IEnumerable (like DataTable)
List<CalendarEventsResult> events = evt.AsEnumerable().ToList();
// Rather than hard-coding, read this from config or database
// to allow easier changing to a different number of events per row?
int numberOfEventsPerRow = 3;
// loop increments in steps of 3 (numberOfEventsPerRow)
for (int rowNumber = 0; rowNumber < events.Count; rowNumber += numberOfEventsPerRow)
{
// use Linq to pick out the subset of Events to process
List<CalendarEventsResult> queryEvents = events
.Skip(numberOfEventsPerRow * rowNumber)
.Take(numberOfEventsPerRow).ToList<CalendarEventsResult>();
// Build a row using that subset
TableRow tr = buildRow(queryEvents, numberOfEventsPerRow);
// Add it to the table
tblEvents.Rows.Add(tr);
}
}
}
catch (Exception ex)
{
// this just suppresses any exceptions. Better to fix issues at source.
}
}
private TableRow buildRow(List<CalendarEventsResult> events, int eventsPerRow)
{
TableRow tr = new TableRow();
// Can these be added to CSS?
tr.HorizontalAlign = HorizontalAlign.Center;
tr.VerticalAlign = VerticalAlign.Bottom;
tr.Height = Unit.Pixel(80);
TableCell tc;
// for our event subset, build a pair of cells for each Event,
// adding them to a row
foreach (var evt in events)
{
tc = BuildEventDate(evt);
tr.Cells.Add(tc);
tc = BuildEventTitle(evt);
tr.Cells.Add(tc);
}
// If we're dealing with a partial row, i.e. only 1 or 2 Events,
// pad the row with empty cells.
if (events.Count < eventsPerRow)
{
tc = BuildEmptyCell(eventsPerRow - events.Count);
tr.Cells.Add(tc);
}
return tr;
}
private TableCell BuildEventDate(CalendarEventsResult evt)
{
TableCell tc = new TableCell();
Literal litDate = new Literal();
//String.Format should suffice c.f. StringBuilder
litDate.Text = String.Format("<div class='EventDate'>{0}{1}</div>", ShortMonth(evt.Month), evt.Day.ToString());
tc.Controls.Add(litDate);
return tc;
}
private TableCell BuildEventTitle(CalendarEventsResult evt)
{
TableCell tc = new TableCell();
int evtInfoId = evt.information_id - 1;
Literal litTitle = new Literal();
// n.b. hard-coded URL doesn't look good for ease of maintenance (move to config or CalendarEventsResult?)
litTitle.Text = String.Format(#"<div class='EventTitle'><a style='text-decoration:none;' href='http://events.website.edu/EventList.aspx?view=EventDetails&eventidn={0}&information_id={1}&type=&rss=rss'>{2}</a></div>",
evt.event_idn.ToString().Trim(), evtInfoId, evt.event_name);
tc.Controls.Add(litTitle);
return tc;
}
private TableCell BuildEmptyCell(int numberOfEvents)
{
// Define as const rather than having unexplained "2" hard-coded.
const int spanPerEvent = 2;
TableCell tc = new TableCell();
tc.ColumnSpan = spanPerEvent * numberOfEvents;
tc.Text = "";
return tc;
}
}

Creating table header using C#

I am trying to create table like spreadsheet using asp.net C# table to use it as timesheet
right now I can create table numberOFDayInMonth X 4 cols. I need some help in how to add header name and text box control to table dynamically?? could someone help me ??
my C# code
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Globalization;
namespace Compudata_ProjectManager
{
public partial class testPage : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
DateTimeFormatInfo info = DateTimeFormatInfo.GetInstance(null);
for (int i = 1; i < 13; i++)
{
//Response.Write(info.GetAbbreviatedMonthName(i) + "<br />");
ddl_months.Items.Add(new ListItem(info.GetMonthName(i), i.ToString()));
}
}
}
protected void ddl_months_SelectedIndexChanged(object sender, EventArgs e)
{
// Remove all the current rows and cells.
// This is not necessary if EnableViewState is set to false.
tbl_timesheet.Controls.Clear();
//declare datetime dt
DateTime dt = new DateTime();
int orderOfMonth = Convert.ToInt32(ddl_months.SelectedValue.ToString());
//get number of date in X month
int noOfDays = DateTime.DaysInMonth(dt.Year, orderOfMonth);
int numOfCols = 4;
for (int row = 0; row < noOfDays; row++)
{
// Create a new TableRow object.
TableRow rowNew = new TableRow();
// Put the TableRow in the Table.
tbl_timesheet.Controls.Add(rowNew);
for (int col = 0; col < numOfCols; col++)
{
// Create a new TableCell object.
TableCell cellNew = new TableCell();
cellNew.Text = "Example Cell (" + row.ToString() + ",";
cellNew.Text += col.ToString() + ")";
// Put the TableCell in the TableRow.
rowNew.Controls.Add(cellNew);
}
}
}
}
}
for headerrow and Dynamic TextBox control
Table tbl = new Table(); // Creating a new table
TableHeaderRow header = new TableHeaderRow(); // Creating a header row
tbl.Rows.Add(header); // Add the header row to table tbl
// Creating Dynamic TextBox Control
TextBox t = new TextBox();
t.ID = "myTextBox"; // assing an ID
// Now add this in a table row
TableRow rowNew = new TableRow(); // Creating a new table row
rowNew.Cells[0].Controls.Add(t); // add the textbox control at cell zero
// or you can add it as
rowNew.Controls.Add(t);

Categories

Resources