I have an aspx file and its small part of the code is
<table class="table table-responsive-sm table-bordered table-hover" runat="server" style="align-content:center;">
<tr><th>Header1</th></tr>
<tr><td id="name1"></td></tr>
</table>
<table class="table table-responsive-sm table-bordered table-hover" runat="server" style="align-content:center;">
<tr><th>Header2</th></tr>
<tr><td id="name2"></td></tr>
</table>
<%-- Similary there are manly table lets say upto 20 --%>
<table class="table table-responsive-sm table-bordered table-hover" runat="server" style="align-content:center;">
<tr><th>Header20</th></tr>
<tr><td id="name20"></td></tr>
</table>
it's code behind is like to set the value is each td :
public void Page_Load()
{
name1.InnerText = "1"
name2.InnerTex = "2"
//similary all td is assigned like this
name20.InnerText = "20"
}
This works fine but in the future there may by many table let's say 100. then do I need to set all the value like the code above. Is there any other method?
I tried using following code:
for(int i=1; i<21; i++)
{
tableId = (HtmlTableCell)this.Page.Master.FindControl("name" + i.ToString());
tableId = (HtmlTableCell)this.Page.FindControl("name" + i.ToString());
tableId = (HtmlTableCell)this.FindControl("name" + i.ToString());
tableId.InnerText = i.toString();
}
but the value of tableId is always null.
How can I solve this?
Ok, this is one of those cases in which we want to consider a different approach.
Now, I don't think it makes sense to have say 10, or even 20 tables here.
Lets think of this question:
I need 20 integer variables.
Do, I do this:
int x1;
int x2;
int x3;
... and so on for 20 variables?
No, we don't do that. We would do this:
int[20] x;
Now, I can use a for loop, even a foreach or whatever.
The same issue applies to your probelm.
You have a group or thing that you need "many" of.
As others in the comments suggest, a repeater (or listview, datalist, gridview) are controls or a "system" that lets you with great ease "repeat" somthing.
So, I like that "array" might need 2 or 50 of these "things".
It makes no sense to try and code out a html writer, or some loop to create this repeating thing. We can do this in code.
So, lets use a repeater.
Say like this:
<asp:Repeater ID="Repeater1" runat="server"
OnItemDataBound="Repeater1_ItemDataBound">
<ItemTemplate>
<asp:Label id="MyHeader" runat="server"
Text='<%# Eval("TableGroup") %>'
Font-Size="X-Large" >
</asp:Label>
</ItemTemplate>
</asp:Repeater>
Very simple. Now, do we want 2 table groups, or 60 of them?
Our code to load up the above can look like this:
We will simple create a list of "what" we want to repeat.
So, lets create something with table like structure. In fact, lets create a .net table!!!
Say we drop a button on the above, and then this code:
protected void Button1_Click(object sender, EventArgs e)
{
DataTable TblList = new DataTable();
TblList.Columns.Add("TableGroup");
for (int i = 1;i<=3;i++)
{
DataRow OneRow = TblList.NewRow();
OneRow["TableGroup"] = "Table " + i;
TblList.Rows.Add(OneRow);
}
Repeater1.DataSource = TblList;
Repeater1.DataBind();
}
Note how clean, how simple, and HOW we did not have to "try" and write code to spit out markup. And while that for/loop is 3, we could make it 30.
Now our output is this:
So, note how what we "made" repeats 3 times. But it could just as well repeat 20, or 100 times.
Now, inside each repeater, we want a table. Once again, we "could" use a table. but if you think about this, a table needs a header, then rows and for each row we would have to define a column.
Once again, that's quite a bit of work. So, lets use a control that can display + render a WHOLE table in one shot!
I suggest a gridview. So, now in the above repeater, lets drop in that grid view.
And we even use your bootstrap class(s) you specified - (since they look VERY nice - well done on your part!!!!).
Ok, so now our repeater becomes this:
<asp:Repeater ID="Repeater1" runat="server" OnItemDataBound="Repeater1_ItemDataBound">
<ItemTemplate>
<asp:Label id="MyHeader" runat="server"
Text='<%# Eval("TableGroup") %>'
Font-Size="X-Large" >
</asp:Label>
<asp:GridView ID="GTable" runat="server"
class="table table-responsive-sm table-bordered table-hover" Width="20%">
</asp:GridView>
</ItemTemplate>
</asp:Repeater>
<br />
Note HOW little markup we have so far. And Gridview is really just a nice table - but one that's less work and hassle.
So, now we need to add code to fill up the table for each group.
Turns out, there is HANDY event that fires for each group creation for us!!!!
So, lets just cook up a table - say a user name, and age.
So, we use the ItemDataBound event. This event fires for each group!!!
So, 3 groups, or 60 - it will not matter.
So, we can code out a table, then then STUFF it right into our gridview.
this code:
protected void Repeater1_ItemDataBound(object sender, RepeaterItemEventArgs e)
{
// each new group/thing, lets fill the grid/table
GridView gV = (GridView)e.Item.FindControl("GTable");
DataTable TestTable = new DataTable();
TestTable.Columns.Add("Name");
TestTable.Columns.Add("Age");
int TableGroup = e.Item.ItemIndex + 1;
// add 3 rows
for (int i = 1; i <= 3; i++)
{
DataRow OneRow = TestTable.NewRow();
OneRow["Name"] = "Group " + TableGroup + " Name " + i;
OneRow["Age"] = i * 15;
TestTable.Rows.Add(OneRow);
}
gV.DataSource = TestTable;
gV.DataBind();
}
Note again:
We are ONLY writing code for ONE thing. Might be repeated over and over, but we don't care. Note also how "easy" it is to think this way.
Now, I did include the repeater "index" to at least add somewhat different rows for each table.
Note again how NICE it is to create that table. It gives us headings, rows, columns
all in a nice simple structure.
And our output is now this:
Is that not cool or what?
So, now with the above automatic rendered.
What do you want to do? Grab a particular repeater item? Maybe add a button to each grid row? We can do that, do that one time, and it will repeat for us.
but, you can get say the 2nd row this way:
protected void Button2_Click(object sender, EventArgs e)
{
// get 2nd repeater (0 based, so get #1)
int RepeatNum = 1;
RepeaterItem MyRepItem = Repeater1.Items[RepeatNum];
// ok, got the item, get <h2> header item
//
Label MyHead = (Label)MyRepItem.FindControl("MyHeader");
Debug.Print(MyHead.Text);
// now get our grid
GridView GV = (GridView)MyRepItem.FindControl("GTable");
foreach (GridViewRow gRow in GV.Rows)
{
Debug.Print("Name = " + gRow.Cells[0].Text);
Debug.Print("Age = " + gRow.Cells[1].Text);
}
output window:
Table 2
Name = Group 2 Name 1
Age = 15
Name = Group 2 Name 2
Age = 30
Name = Group 2 Name 3
Age = 45
So think in groups. I have VERY little markup. And BETTER is how the code behind does not try and spit out a ZILLION "tr" "th" "table" etc. I don't write all that HTML - but leverage the built in tools.
What is nice, is I can now change my mind, and say render this as say "two things" going accross the page - kind of like a "card view".
So, we wrote ONE thing for the group.
So we wrote ONE thing for the table.
We then repeated both over and over.
Related
So as my question states I want to use a for loop on my frontend and use an if statement of Eval() inside the for loop but I can't seem to get it to work. My ratings eval will return a n integer number between 1 and 5. I'm getting the error "Databinding methods such as Eval(), XPath(), and Bind() can only be used in the context of a databound control.'"
<% for (int i = 1; i <= 5; i++)
{
if (i == (int)Eval("ratings"))
{
Response.Write("<input type='radio' checked='true' disabled='true' name='rating-star' class='rating__control screen-reader'>");
}
else
{
Response.Write("<input type='radio' disabled='true' name='rating-star' class='rating__control screen-reader' '>");
}
}; %>
Ok, this is one of those architecture issues that may not be all that clear to you.
the "instant" you put in the % and start using server side "directives"?
That code does not run in the front end "just" because you placed it in the markup. That code STILL runs 100% on the server side. And MORE important, while the server is rendering the page, then that code runs. And THEN THE WHOLE page is sent down to the browser side.
As a result?
You actually better off in 99% of cases to write such code in the code-behind. It actually in most cases becomes more diffiuct and more confusing to try and mix in that server side code inside of your markup.
So, now that we cleared up the above?
The first issue is you want a choice 1-5. but, if you chose the 3rd one, then the current 5th or 1st one has to be un-checked, right?
As a result of this common problem, then better off to use a RadioButton list, or a check box list.
Drop in a Say a RadioButton list like this:
<asp:RadioButtonList ID="RadioButtonList1" runat="server">
</asp:RadioButtonList>
Now, do we want 5 choices, then use this editor:
(you can hand edit the choices in the markup when you become more comfortable).
So we can type in say 0 - 5 choices
Like this:
Note how I am typing in 5 and "Five" for display. Also, use the property sheet - set the cell spacing (try 10), and set Repeat direction = Horizontal.
You see this markup:
<asp:RadioButtonList ID="RadioButtonList1" runat="server" CellSpacing="10"
RepeatDirection="Horizontal">
<asp:ListItem Value="0">Zero</asp:ListItem>
<asp:ListItem Value="1">One</asp:ListItem>
<asp:ListItem Value="2">Two</asp:ListItem>
<asp:ListItem Value="3">Three</asp:ListItem>
<asp:ListItem Value="4">Four</asp:ListItem>
<asp:ListItem Value="5">Five</asp:ListItem>
</asp:RadioButtonList>
and we run, and see this:
Ok, what about using code to fill out the choices.
Ok, lets try another RadioButtion list like this:
<asp:RadioButtonList ID="RadioButtonList2" runat="server"
RepeatDirection="Horizontal">
</asp:RadioButtonList>
Now what about code? Say I want 1 to 10?
Ok, then we have this code:
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
for (int i = 1;i<=10;i++)
{
RadioButtonList2.Items.Add(new ListItem(i.ToString(), i.ToString()));
}
}
}
And now we run, and we see this:
And again, REALLY nice since ONLY ONE choice can be picked - we don't have to write any code.
So, it not clear if you want one Radio button list, and 10 choices.
Or, maybe we want to display 10 hotels, and each has a 1-5 rating to display?
Again, we would use say a GridView (for the hotel list), and drop in a RadioButton list as ONE of the columns for the Grid.
And last but not least?
Maybe we want to drive the choices from a data base? Say, a list of Cities.
So, "please pick your favorite city?
Lets drop in a 3rd RB list, and we have this:
<asp:RadioButtonList ID="RadioButtonList3" runat="server"
DataTextField="City"
DataValueField="ID" >
</asp:RadioButtonList>
Note how we added to more settings DataValue Field, and Data text field. So, I want to display the City list, but we using a database, and I might want to DISPLAY the text values, but when I pull the choice, I want the database PK row.
So, now my code behind is this:
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
for (int i = 1;i<=10;i++)
{
RadioButtonList2.Items.Add(new ListItem(i.ToString(), i.ToString()));
}
// now load up the city picking Radio button list
using (SqlConnection conn = new SqlConnection(Properties.Settings.Default.TEST4))
{
string strSQL = "SELECT ID, City from City ORDER BY City";
using (SqlCommand cmdSQL = new SqlCommand(strSQL,conn))
{
conn.Open();
RadioButtonList3.DataSource = cmdSQL.ExecuteReader();
RadioButtonList3.DataBind();
}
}
}
}
And now our page looks like this:
So, I hope the above gives you some ideas as to the "general" approach, and the options you have if you want to edit + enter the values, or use some code to fill out the codes, or even use code + database to load up the RadioButton choices.
I have a drop down list box like following. Under some condition, in the databound event I want to remove the items whose bitActive is set to 0 (inactive). I did not put a WHERE bitAcive!=0 in the selectCommand, because I only want to remove them under some conditions. Is there any way I could iterate the items and check the value of bitActive?
<tr>
<td width="30%" align="right">Location<span class="littlefont">*</span></td>
<td width="70%" align="left">
<asp:DropDownList ID="ddlLocation" runat="server"
DataSourceID="SqlDSLocation" DataTextField="txtRefLocation_Name"
DataValueField="intRefLocation_ID" ondatabound="ddlLocation_DataBound">
</asp:DropDownList>
<asp:SqlDataSource ID="SqlDSLocation" runat="server"
ConnectionString="<%$ ConnectionStrings:SPRConnectionString %>"
SelectCommand="SELECT DISTINCT [intRefLocation_ID], [txtRefLocation_Name], [location], [bitActive] FROM [tblRefLocation] ORDER BY [intRefLocation_ID]">
</asp:SqlDataSource>
</td>
</tr>
In codebehind you can call the SQLDataSource.Select() method:
System.Data.DataView dv = (System.Data.DataView)SqlDSLocation.Select(DataSourceSelectArguments.Empty);
And then iterate through the rows returned, finding the "bitActive" rows who are set to zero and removing them from your DropDownList (code sort of hacked from the example linked above):
foreach(System.Data.DataRow row in dv.Table.Rows)
{
// This is approximate logic, tailor this to fit your actual data
if (row["bitActive"].ToString() == "False")
{
ddlLocation.Items.Remove(row["intRefLocation_ID"].ToString());
}
}
Note that this is not removing these rows from your SQL table. Make sure you don't databind your DropDownList again after this - otherwise all the stuff you just removed will return.
EDIT: For a more efficient and elegant solution, see James Johnson's answer.
Instead of removing items in the ItemDataBound event, why not just filter the datasource before binding it?:
var table = new DataTable("MyTable"); //assume it's populated
if (table.Rows.Count > 0)
{
var results = table.AsEnumerable().Where(r => r.bitActive).AsDataView().ToTable();
if (!results.HasErrors)
{
DropDownList1.DataSource = results;
DropDownList1.DataBind();
}
}
Is it possible to grab the cell data from Repeater items property such as
Dim test As String = myRepeater.Items(index).DataItem(2)
where myRepeater has x rows and 10 columns and was bound from the DataTable
Seems like it should be trivial, but looks like individual cells CAN NOT be accessed. Please shed some light on this.
Not directly -- remember, repeaters are very, very simple webcontrols and the repeater itself really has a limited idea of what is underneath it.
But you can easily get at items within. The best method is to use a control within the item to help you find stuff. EG, given this repeater "row":
<ItemTemplate>
<asp:Button runat="server" ID="theButton" text="Click Me!" OnClick="doIt" /> <asp:TextBox id="theTextBox" value="Whateeavs" />
</ItemTemplate>
You can use the button's click handler to find other items in the row:
protected void doIt(object s, EventArgs e) {
var c = (Control)s;
var tbRef = c.NamingContainer.FindControl("theTextBox");
var tb = (ITextControl)tbRef;
doSomethingWith(tb.Text);
}
Typically much cleaner than finding things in rows using absolute positioning -- you don't have to worry about indexes and such.
PS: it has been a long time since I laid down significant web forms code, no garuntees I got the class names, etc, exactly right.
is there any way to bring up a subheader row at changes in a field in an databound asp:repeater control e.g.
Instead of
country | colour | number
uk | red | 3
uk | green | 3
france | red 3
Do this:
==UK==
colour | number
red | 3
green 3
==FRANCE==
colour | number
red | 3
Many thanks for any help.
There's no built-in support, but that doesn't mean it's impossible.
You'll need to over-ride the OnItemDataBound event, and have something like this in markup:
<asp:Repeater OnItemDataBound="NextItem" ... >
<ItemTemplate><asp:Literal Id="Header" Visible="False" Text="{0}<strong>{1}</strong><br/><table>" ... />
<tr>
<td><asp:Label id="Color" Text="<%# Eval("Color")" ... /></td>
<td><asp:Label id="Number" Text="<%# Eval("Number")" ... /></td>
</tr>
</ItemTemplate>
</asp:Repeater></table>
Then in the code-behind:
private string CurCountry = string.Empty;
private void NextItem(object sender, RepeaterItemEventARgs e)
{
if ( e.Item.ItemType != ListItemType.Item
&& e.Item.ItemType != ListItemType.AlternatingItem) return;
DbDataRecord row = (DbDataRecord)e.Item.DataItem;
if (CurCountry != row["country"].ToString() )
{
string prev = (CurCounter == string.Empty)?"":"</table>";
CurCountry = row["country"].ToString();
Literal header = (Literal)e.Item.FindControl("Header");
Literal footer = (Literal)e.Item.FindControl("Footer");
header.Text = string.Format(header.Text, prev, CurCountry);
header.Visible = true;
}
}
Another solution is to simply use two repeaters, one nested within the other. You can pass your groups with the child records to the first repeater, and on the ItemDataBound of the groups repeater, pass the child records to the child repeater and call DataBind() there.
This is more code but does actually give you more control over the layout without having HTML creation code in your code-behind.
As you can see here, we have a parent repeater and in the item template we can customise each group as we see fit. In the ChildRepeater, we have our item template in which we can customise each item inside the grouping. Very clean and all with declarative UI.
<asp:Repeater runat="server" id="GroupRepeater">
<ItemTemplate>
<asp:Literal runat="server" id="HeaderText" />
<asp:Repeater runat="server id="ChildRepeater">
<ItemTemplate>
<asp:Literal runat="server" id="InfoGoesHere" />
</ItemTemplate>
</asp:Repeater>
</ItemTemplate>
</asp:Repeater>
In the code behind we can have something like this:
private void GroupRepeater_ItemDataBound(object sender, RepeaterItemEventArgs e)
{
//Get the child records, this can be any data structure you want
SomeChildCollection children = ((SomeGroupCollection)e.Item.DataItem).Children;
//Find the child repeater
Repeater childRepeater = e.Item.FindControl("ChildRepeater") as Repeater;
childRepeater.ItemDataBound += SomeMethod;
childRepeater.DataSource = children;
childRepeater.DataBind();
}
After binding each child you can subscribe to the ItemDataBound event and do the child binding to controls as you see fit.
To joel Solution, I may advice following - change this line:
header.Text = string.Format("<strong> {0} </strong><br/><table>", CurCountry);
to
header.Text = string.Format(header.Text, CurCountry);
And than you can customize look & feel from .as?x file.
Ah - you're after a grouping repeater. The solution that I've adapted in the past seems to have disappeared. Maybe you can find it. But anyway, a search for grouping repeaters should help.
There seem to be a few bugs in the dotnetjunkies code but I've found a much simpler solution here http://www.west-wind.com/weblog/posts/140753.aspx
Now repeaters don't support grouping, so you have to do this on your own, but it's pretty easy to do. In the repeater above the following data expression is responsible for the grouping:
<%# this.RenderGroup(Eval("Group") as
string) %>
The expression calls a method on the form to render an extra div tag. The code for this simple method looks like this:
string LastGroup = "###~";
protected string RenderGroup(string Group) {
if (Group == this.LastGroup)
return ""; // *** Group has changed
this.LastGroup = Group;
return "<div class='groupheader'>" +Group + "</div>";
}
This code gets called for every item and it simply checks to see if the group has changed. If it hasn't nothing is returned otherwise a simple div tag is returned as a string to embed into the markup. I chose to send back the HTML but I suppose you could also return true or false and conditionally render a control in the Repeater which would make the CSS Nazis happier .
I have a listview, where I set the datasource in the code-behind - this works fine.
When I add another listview (or databound control) to the listview's itemtemplate, and set the datasource for that control in the codebehind, the fields returned by the query seem to be unavailable to the nested listview; ASP.NET throws the following error: DataBinding: 'System.String' does not contain a property with the name 'j_Name'.
In the example below, d_Description works fine, whereas j_Role throws the error indicated above. I can see the data being returned by the query, and I know the column names match, so what's causing the error (and how do I solve it)?
ASPX page
<asp:ListView ID="LV1" runat="server">
<LayoutTemplate>
<table runat="server" id="tblSummary">
<tr runat="server" id="itemPlaceholder" />
</table>
</LayoutTemplate>
<ItemTemplate>
<tr>
<td>
<%#Eval("d_Description")%>
</td>
</tr>
<tr>
<td>
<asp:ListView ID="LV2" runat="server">
<ItemTemplate>
<%#Eval("j_Role")%>
</ItemTemplate>
<LayoutTemplate>
<asp:placeholder id="itemPlaceholder" runat="server" />
</LayoutTemplate>
</asp:ListView>
</td>
</tr>
</ItemTemplate>
</asp:ListView>
CODE BEHIND
var qry1 = from q in context.Descriptions select q.d_Description;
LV1.DataSource = qualificationQry;
LV1.DataBind();
var qry2 = from q in context.Roles select q.j_Role;
LV2.DataSource = qualificationQry;
LV2.DataBind();
EDIT:
I've added code similar to that below to the ItemDataBound event of the outer listview, and I am still encountering the same error. Presumably I am misunderstanding the instructon?
protected void LV_ItemDataBound(object sender, ListViewItemEventArgs e)
{
using (dbDataContext context = new dbDataContext()
{
var qry2 = from q in context.Roles select q.j_Role;
ListView tempLV = (ListView)e.Item.FindControl("LV2");
tempLV.DataSource = qry2;
tempLV.DataBind();
}
}
EDIT: 2
After reading around the web some more (now that I have an idea of what to search for) the proposed answer seems to be correct - however, it's not working - can anyone suggest why?
EDIT: 3
If I ditch the outputting of j_Name, and just have a hardcoded string, there is no error, and the hard-coded string ouputs the expected number of times. This would indicate that it's just the column name (j_Name) that is wrong - even though I can see the dataset coming back from the query with that exact column name.
EDIT: 4
Fixed it.
This is wrong
var qry2 = from q in context.Roles select q.j_Role;
This is correct
var qry2 = from q in context.Roles select q;
You need to bind the inner list on every row of the outer list in the ItemDataBoundEvent for the outer list.
So this code:
var qry2 = from q in context.Roles select q.j_Role;
LV2.DataSource = qualificationQry;
LV2.DataBind();
will need to go in an even handler for the outer lists ItemDataBound event:
protected void ContactsListView_ItemDataBound(object sender, ListViewItemEventArgs e)
{
if (e.Item.ItemType == ListViewItemType.DataItem)
{
// Bind the inner list on every repeat of the outer list
var qry2 = from q in context.Roles select q.j_Role;
LV2.DataSource = qualificationQry;
LV2.DataBind();
}
}
And presumably you'll want to filter the values for the inner list based on a value from the current item of the outer list