I need to bind a repeater with hierarchical data as follows:
Category1
- Item1
- Item2
Category2
- Item3
- Item4
I currently have a single dataset with the items as well as the category that each item belongs to.
I'm trying to learn Linq and was wondering if there was a way I can do the same using Linq?
Below is what I tried:
var groupbyfilter = from dr in dtListing.AsEnumerable()
group dr by dr["Category"];
DataTable dtFinal = dtListing.Clone();
foreach (var x in groupbyfilter)
x.CopyToDataTable(dtFinal, LoadOption.OverwriteChanges);
rptList.DataSource = dtFinal;
rptList.DataBind();
But trouble with it is it repeats category for each item.
You'll need a repeater nested inside another.
Do a distinct on the dtlisting selecting only the category field. Bind this to the outer repeater.
In the 2nd repeater, select data whose where condition has category field which equals to the value that is being databound to the repeater item. You'd have to handle this in the repeater's onitem_databound event.
Here is an example.
<%# Import Namespace="System.Data" %>
<asp:Repeater ID="Repeater1" runat="server" OnItemDataBound="Repeater1_ItemDataBound">
<ItemTemplate>
<div>
Category: <b><%# Container.DataItem%></b>
<asp:Repeater ID="Repeater2" runat="server">
<FooterTemplate>
<%="</ul>" %>
</FooterTemplate>
<HeaderTemplate>
<%= "<ul>"%>
</HeaderTemplate>
<ItemTemplate>
<li>
<%# ((Data.DataRow)Container.DataItem)[1] %>, <%# ((Data.DataRow)Container.DataItem)[0] %>
</li>
</ItemTemplate>
</asp:Repeater>
</div>
</ItemTemplate>
</asp:Repeater>
For this sample I used a csv as my datasource, and created a datatable using it. So my codebehind looks like:
using Microsoft.VisualBasic;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics;
public class _Default : System.Web.UI.Page
{
DataTable csvData;
protected void Page_Load(object sender, System.EventArgs e)
{
csvData = Utils.csvToDataTable("data.csv", true);
GridView1.DataSource = csvData;
GridView1.DataBind();
Repeater1.DataSource =
(from x in csvData.AsEnumerable() select x["category"]).Distinct();
Repeater1.DataBind();
}
protected void Repeater1_ItemDataBound(object sender, System.Web.UI.WebControls.RepeaterItemEventArgs e)
{
if (e.Item.ItemType == ListItemType.Item |
e.Item.ItemType == ListItemType.AlternatingItem) {
Repeater rptr = (Repeater)e.Item.FindControl("Repeater2");
rptr.DataSource =
csvData.AsEnumerable().Where(x => x["category"].Equals(e.Item.DataItem));
rptr.DataBind();
}
}
}
You can either use nested repeaters like deostroll says, or specify a grouping field and have a group header in your ItemTemplate that's dynamically hidden (or rendered) every time your grouping field changes.
More detail in fernan's answer at http://forums.asp.net/t/1252235.aspx
Related
I am trying to get a page to display information in a row layout using repeaters. I have one working that allows me to dynamically create hyperlinks, however i cant get my nested repeater to work to display the date the file was created. Is it possible to use repeaters to dynamically display multiple variables from lists as i'm trying to do below?
.aspx
<asp:Repeater id="repLinks" runat="server">
<ItemTemplate>
<tr><td>
<asp:HyperLink runat="server" NavigateUrl='<%# Container.DataItem.ToString() %>' Text="<%# Container.DataItem.ToString().Split('\\').Last() %>" />
<td>
<asp:Repeater ID="Repeater2" runat="server" OnItemDataBound="Repeater2_ItemDataBound" >
<ItemTemplate>
<%# Container.DataItem.ToString()%>
</ItemTemplate>
</asp:Repeater>
</td>
<td>
Submitted By <!--add repeater-->
</td>
<td>
Mark as Billed <!--add repeater-->
</td>
</td></tr>
</ItemTemplate>
</asp:Repeater>
.aspx.cs
public List<string> CD = new List<string>();
protected void Page_Load(object sender, EventArgs e)
{
//Welcomes User
string Uname = Environment.UserName;
UserName.Font.Size = 17;
UserName.Text = "Welcome: " + Uname;
//gives path and constructs lists for directory paths and file links
string root = "C:\\Users\\James\\Documents\\Visual Studio 2015\\WebSites";
List<string> lLinks = new List<string>();
//adds files to list
foreach (var path in Directory.GetDirectories(#root))
{
foreach (var path2 in Directory.GetFiles(path))
{
lLinks.Add(path2);
CD.Add(File.GetCreationTime(path2).Date.ToString("yyyy-mm-dd"));
}
}
//Define your list contents here
repLinks.DataSource = lLinks;
repLinks.DataBind();
}
protected void Repeater2_ItemDataBound(object sender, RepeaterItemEventArgs e)
{
if (e.Item.ItemType == ListItemType.Item || e.Item.ItemType == ListItemType.AlternatingItem)
{
Repeater Repeater2 = (Repeater)(e.Item.FindControl("Repeater2"));
Repeater2.DataSource = CD;
Repeater2.DataBind();
}
}
The issue with your code is that you are binding the nested repeater control (Repeater2) in the ItemDataBound event of Repeater2 repeater itself which will never get fired because ItemDataBound event is fired for each item in collection when it is bounded to the repeater control.
You should write the logic in ItemDataBound event of your parent repeater like this:-
<asp:Repeater id="repLinks" runat="server" OnItemDataBound="Repeater1_ItemDataBound">
Then, write the logic in this event handler:-
protected void Repeater1_ItemDataBound(object sender, RepeaterItemEventArgs e)
{
if (e.Item.ItemType == ListItemType.Item ||
e.Item.ItemType == ListItemType.AlternatingItem)
{
Repeater Repeater2 = (Repeater)(e.Item.FindControl("Repeater2"));
Repeater2.DataSource = CD;
Repeater2.DataBind();
}
}
Also, In the Page_Load event you should bind the Parent Repeater Repeater1 on just the initial page load so wrap it inside !IsPostBack and populate your datasource lLinks & CD from a separate method intead of doing it in Page_Load event.
I'm using a Repeater to show some data coming from a web service.
My Repeater structure is:
<asp:Repeater ID="rptgrp" runat="server">
<ItemTemplate>
<asp:CheckBoxList ID="chkBoxListGoup" runat="server"
DataSource='<%# DataBinder.Eval(Container.DataItem, "Titles")%>'
DataTextField="Title"
DataValueField="IDTitle">
</asp:CheckBoxList>
</ItemTemplate>
</asp:Repeater>
Now, my web service returns these fields in "Titles":
1) Title
2) IDTitle
3) isUserTitle
Now, I would like to set checked a checkbox when isUserTitle is = 1.
How can I do that?
You can find checkboxlist as follows
Find checkboxlist in itemdatabound,
check item text of every checkboxlist using loop,
select the item whose text is 1
Protected void Repeater_ItemDataBound(Object Sender, RepeaterItemEventArgs e) {
if (e.Item.ItemType == ListItemType.Item || e.Item.ItemType == ListItemType.AlternatingItem)
{
CheckBoxList chklst = (CheckBoxList)e.Item.FindControl("chkBoxListGoup");
for (int i = 0; i < chk.Items.Count; i++)
{
if (chk.Items[i].Text == "1")
{
chk.Items[i].Selected = true;
}
}
}
}
Try changing <asp:CheckBoxList ID="chkBoxListGoup" runat="server"
to
<asp:CheckBoxList ID="chkBoxListGoup" Checked='<%#Eval("Flag")%>' runat="server"
Flag being your Column..
Then in your method or event handler you want to run some code to say if this value = 1 checked, elseif value = 0 unchecked...
Here is sample code that demonstrates the idea:
Code-behind:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web.UI.WebControls;
using WebApp.RepeaterCheckboxList.TODODatasetTableAdapters;
namespace WebApp.RepeaterCheckboxList
{
public partial class WebForm1 : System.Web.UI.Page
{
IEnumerable<TODODataset.TasksViewRow> view;
IEnumerable<TODODataset.TasksViewRow> subview;
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
TasksViewTableAdapter adp = new TasksViewTableAdapter();
var dt = adp.GetData();
view = dt.AsEnumerable();
var names = (from x in view
select new
{
Person = x.Name,
ID = x.PersonID
}).Distinct();
DataList1.DataSource = names;
DataList1.DataBind();
}
}
protected void CheckBoxList1_DataBound(object sender, EventArgs e)
{
CheckBoxList theList = (CheckBoxList)sender;
var person = ((DataListItem)theList.Parent).DataItem as dynamic;
var name = person.Person;
var id = person.ID;
var vw = subview;
for (int i = 0, j = vw.Count(); i < j; i++)
{
var task = vw.ElementAt(i);
theList.Items[i].Selected = task.Completed;
}
}
protected IEnumerable<TODODataset.TasksViewRow> GetTasks(object data)
{
var vw = data as dynamic;
return subview = (from x in view
where x.PersonID == vw.ID
select x);
}
}
}
Aspx:
<%# Page Language="C#" AutoEventWireup="true" CodeBehind="WebForm1.aspx.cs" Inherits="WebApp.RepeaterCheckboxList.WebForm1" %>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title></title>
</head>
<body>
<form id="form1" runat="server">
<div>
<asp:DataList ID="DataList1" runat="server">
<ItemTemplate>
<div style="padding:5px">
<h3><%# Eval("Person") %></h3>
<div>
<asp:CheckBoxList OnDataBound="CheckBoxList1_DataBound" ID="CheckBoxList1"
runat="server"
DataTextField="TaskDesc" DataValueField="TaskID"
DataSource="<%# GetTasks(Container.DataItem) %>"></asp:CheckBoxList>
</div>
</div>
</ItemTemplate>
</asp:DataList>
</div>
</form>
</body>
</html>
If you are interested in the data, click here
Try just setting the Checked value to the object being Evaled.
<asp:Repeater ID="rptgrp" runat="server">
<ItemTemplate>
<asp:CheckBoxList ID="chkBoxListGoup" runat="server"
Checked=<%# Eval("isUserTitle") %>>
</asp:CheckBoxList>
</ItemTemplate>
</asp:Repeater>
What i'm trying to do is this
<asp:Repeater ID="ParentRepeater" runat="server" OnItemDataBound="ItemBound">
<ItemTemplate>
<asp:Repeater ID="Repeater_SideMenu_Guides_Medlem" runat="server">
<ItemTemplate>
</ItemTemplate>
</asp:Repeater>
</ItemTemplate>
</asp:Repeater>
Codebehind
ParentRepeater.DataSource = CraftGuides.GetAllGroups();
ParentRepeater.DataBind();
protected void ItemBound(object sender, RepeaterItemEventArgs args)
{
if (args.Item.ItemType == ListItemType.Item)
{
Repeater childRepeater = (Repeater)args.Item.FindControl("ChildRepeater");
childRepeater.DataSource = CraftGuides.GetGuidesByGroupID( Insert ID from Parent Here );
childRepeater.DataBind();
}
}
Now, the thing is I don't know to get the ID from the parent inside the child to collect the data from the database
Providing that you have a Group object, you can use the following:
var item = args.Item;
var dataItem = item.DataItem as Group;
Then you easily grab the id of the group object and pass it into your GetGuidsByGroupID().
I like to use the as keyword since it will return null if the cast fails. Using (Group)item.DataItem would throw an exception if it failed.
I need to know how to nest repeaters within a user control. The html side of things is fine, it's the binding and code behind I need help with. I've only been able to find examples using an sql data source, which doesn't really help.
My repeaters look like this:
<asp:Panel ID="pnlDiscipline" runat="server" CssClass="">
<asp:Repeater ID="rptDiscipline" runat="server">
<ItemTemplate>
<h4><%#Eval("Discipline")%></h4>
<ul>
<asp:Repeater ID="rptPrograms" runat="server">
<ItemTemplate>
<li><asp:HyperLink runat="server" Text='<%#Eval("Name") %>' NavigateUrl='<%#Eval("Link") %>'></asp:HyperLink> <%#Eval ("Notation") %></li>
</ItemTemplate>
</asp:Repeater>
</ul>
</ItemTemplate>
</asp:Repeater>
What I need to do is hopefully reasonably clear - the h4 discipline should appear once, all the entries that belong to the discipline are listed below, then the next h4, then the appropriate list, the next h4 and so on.
The datasource is a dataview created in the codebehind where each row has 'Name', "Link', 'NOtation' and 'Discipline'. I've bound the dataview to the outermost repeater, and it behaves as expected - lists the discipline name for each entry, but shows no data in the inner repeater.
How do I go about making this work?
EDIT: Just to clarify, I have one datatable in the codebehind. Each row in that table is an item, each item belongs to a discipline. I want to use the outer repeater to list the disciplines, the inner to list the items grouped under each discipline. Like so:
<h4>DISCIPLINE 1</h4>
<ul>
<li>Item</li>
<li>Item</li>
<li>Item</li>
</ul>
<h4>DISCIPLINE 2</h4>
<ul>
<li>Item</li>
<li>Item</li>
</ul>
<h4>DISCIPLINE 3</h4>
<ul>
<li>Item</li>
<li>Item</li>
</ul>
At present, binding the datatable to the outer repeater gives this (example uses the data above):
<h4>DISCIPLINE 1</h4>
<h4>DISCIPLINE 1</h4>
<h4>DISCIPLINE 1</h4>
<h4>DISCIPLINE 2</h4>
<h4>DISCIPLINE 2</h4>
<h4>DISCIPLINE 3</h4>
<h4>DISCIPLINE 3</h4>
I've used OnItemDataBound on the outer repeater as suggested, and as a test case am able to access the data:
protected void rptDiscipline_ItemDataBound(Object Sender, RepeaterItemEventArgs e)
{
DataRowView drView = (DataRowView) e.Item.DataItem;
string name = drView["Name"] as string;
string link = drView["Link"] as string;
string notation = drView["Notation"] as string;
Response.Write(name + link + notation + "<br />")
}
So the data is there, it is exactly what I would expect to see, I just can't get it bound to the inner repeater. If there is a more performant way to achieve the same, I'm happy to rework my solution.
On the outer control, use the ItemDataBound event, like this:
<asp:Repeater ID="rptDiscipline" runat="server"
OnItemDataBound="rptDiscipline_ItemDataBound">
...
Then, in the code-behind, handle the rptDiscipline_ItemDataBound event and manually bind the inner repeater. The repeater's ItemDataBound event fires once for each item that is repeated. So you'll do something like this:
protected void rptDiscipline_ItemDataBound(Object Sender, RepeaterItemEventArgs e)
{
// To get your data item, cast e.Item.DataItem to
// whatever you're using for the data object; for example a DataRow.
// Get the inner repeater:
Repeater rptPrograms = (Repeater) e.Item.FindControl("rptPrograms");
// Set the inner repeater's datasource to whatever it needs to be.
rptPrograms.DataSource = ...
rptPrograms.DataMember = ...
rptPrograms.DataBind();
}
EDIT: Updated to match your question's update.
You need to bind the outer repeater to a data source that has only one record per item you want the repeater to render. That means the data source needs to be a collection/list/datatable/etc that has only the disciplines in it. In your case, I would recommend getting a List<string> of disciplines from the DataTable for the inner collection, and bind the outer repeater to that. Then, the inner repeater binds to a subset of the data in the DataTable, using the ItemDataBound event. To get the subset, filter the DataTable through a DataView.
Here's code:
protected void Page_Load(object sender, EventItems e)
{
// get your data table
DataTable table = ...
if ( !IsPostBack )
{
// get a distinct list of disciplines
List<string> disciplines = new List<string>();
foreach ( DataRow row in table )
{
string discipline = (string) row["Discipline"];
if ( !disciplines.Contains( discipline ) )
disciplines.Add( discipline );
}
disciplines.Sort();
// Bind the outer repeater
rptDiscipline.DataSource = disciplines;
rptDiscipline.DataBind();
}
}
protected void rptDiscipline_ItemDataBound(Object Sender, RepeaterItemEventArgs e)
{
// To get your data item, cast e.Item.DataItem to
// whatever you're using for the data object
string discipline = (string) e.Item.DataItem;
// Get the inner repeater:
Repeater rptPrograms = (Repeater) e.Item.FindControl("rptPrograms");
// Create a filtered view of the data that shows only
// the disciplines needed for this row
// table is the datatable that was originally bound to the outer repeater
DataView dv = new DataView( table );
dv.RowFilter = String.Format("Discipline = '{0}'", discipline);
// Set the inner repeater's datasource to whatever it needs to be.
rptPrograms.DataSource = dv;
rptPrograms.DataBind();
}
If you don't want to do it on the ItemDataBound event, you can also do it inline in your page by binding to a child property of the parent item if the child property is a collection like so:
<asp:Repeater runat="server" ID="OuterRepeater" >
<ItemTemplate>
Outer Content: <%# DataBinder.Eval(Container.DataItem, "ParentProperty")%>
<asp:Repeater runat="server" ID="InnerRepeater" DataSource='<%# DataBinder.Eval(Container.DataItem, "ChildCollection")%>' >
<ItemTemplate>
<%# DataBinder.Eval(Container.DataItem, "ChildProperty")%>
</ItemTemplate>
</asp:Repeater>
</ItemTemplate>
</asp:Repeater>
First you need two lists, the list of disciplines, and then the list of all your data.
Data bind the list of disciplines to the outer repeater. If there are 6 disciplines, the repeater should repeat 6 times.
<asp:Repeater ID="rptDiscipline" runat="server" OnItemDataBound="rptDiscipline_ItemDataBound">
<ItemTemplate>
<h4><%# Eval("Discipline")%></h4>
<ul>
<asp:Repeater runat="server" ID="InnerRepeater" >
<ItemTemplate>
<li>
<asp:Label runat="server" ID="lbl" />
</li>
</ItemTemplate>
</asp:Repeater>
</ul>
</ItemTemplate>
</asp:Repeater>
protected void rptDiscipline_ItemDataBound(Object Sender, RepeaterItemEventArgs e)
{
Repeater inner= (Repeater) e.Item.FindControl("InnerRepeater");
//You want to bind all the data related to this discipline
DataRowView drView = (DataRowView) e.Item.DataItem;
string discipline= drView["Discipline"] as string;
//filter your total data with ones that match the discipline
inner.DataSource = //data bind the filtered list here
inner.DataBind();
}
I am reading XML using xmlreader, and then binding the xml to a Repeater
Here's the code behind:
XmlReaderSettings settings = new XmlReaderSettings();
settings.ProhibitDtd = false;
XmlReader xmlData = XmlReader.Create(webClient.OpenRead(requestUrl), settings);
try
{
xmlData.ReadToFollowing("SearchResults");
Label1.Text = xmlData.GetAttribute("TotalCount");
int numberResults = Convert.ToInt32(xmlData.GetAttribute("TotalCount"));
if (numberResults > 0)
{
DataSet ds = new DataSet();
ds.ReadXml(xmlData);
Repeater1.DataSource = ds.Tables[1];
Repeater1.DataBind();
}
else
{
Repeater1.DataSource = null;
Repeater1.DataBind();
}
}
Here's the repeater
<asp:Repeater ID="Repeater1" runat="server">
<HeaderTemplate><b>Results</b><br /><br /></HeaderTemplate>
<ItemTemplate>
<a href="<%#DataBinder.Eval(Container.DataItem, "Url")%>">
<%#DataBinder.Eval(Container.DataItem, "Title")%></a><br />
</ItemTemplate>
</asp:Repeater>
And the xml looks like:
<SearchResults PageSize="1" PageIndex="0" TotalCount="155">
<SearchResult>
<ContentId>2458</ContentId>
<Title>Component description</Title>
<Url>http://whatever/19/p/1537/2458.aspx</Url>
<Date>2009-06-10T09:34:00+01:00</Date>
<ContentType>forum</ContentType>
<Tags>
<Tag>Component</Tag>
</Tags>
<Users>
<User>
<Id>2533</Id>
<DisplayName>Haubent</DisplayName>
<Username>Haubent</Username>
</User>
</Users>
<IndexedAt>2010-07-29T15:40:52.414+01:00</IndexedAt>
</SearchResult>
So far so good.
But - I want to be able to show the contents of the DisplayName node in my repeater item template.
I tried
<%#DataBinder.Eval(Container.DataItem, "DisplayName")%>
and
<%#DataBinder.Eval(Container.DataItem, "Users.User.DisplayName")%>
but I get an error:
System.Web.HttpException: DataBinding: 'System.Data.DataRowView' does not contain a property with the name 'Users'.
How to get at the DisplayName?
I think the best (and most natural) way to do this is by using Linq To XML.
You can change your try block in this way:
try
{
xmlData.ReadToFollowing("SearchResults");
Label1.Text = xmlData.GetAttribute("TotalCount");
int numberResults = Convert.ToInt32(xmlData.GetAttribute("TotalCount"));
if (numberResults > 0)
{
XDocument xml = XDocument.Parse(xmlData.ReadOuterXml());
Repeater1.DataSource = xml.Element("SearchResults").Elements("SearchResult");
Repeater1.DataBind();
}
else
{
Repeater1.DataSource = null;
Repeater1.DataBind();
}
}
catch
{
// do some catch
}
passing an XElement collection to de repeater. Then you can declare an OnRepeaterDataBound method similar to this:
protected void Repeater1_OnItemDataBound(object sender, RepeaterItemEventArgs e)
{
if ((e.Item.ItemType == ListItemType.Item) || ((e.Item.ItemType == ListItemType.AlternatingItem)))
{
XElement User = ((XElement)e.Item.DataItem).Element("Users").Element("User");
HyperLink hlUrl = ((HyperLink)e.Item.FindControl("hlUrl"));
hlUrl.NavigateUrl = ((XElement)e.Item.DataItem).Element("Url").Value;
hlUrl.Text = User.Element("DisplayName").Value;
}
}
Having the repeater as:
<asp:Repeater ID="Repeater1" runat="server" OnItemDataBound="Repeater1_OnItemDataBound">
<HeaderTemplate>
<b>Results</b><br />
<br />
</HeaderTemplate>
<ItemTemplate>
<asp:HyperLink ID="hlUrl" runat="server" />
</ItemTemplate>
</asp:Repeater>
On the Repeater1_OnItemDataBound you will be able to parse your xml in the way you want.
If you inspect (by setting a breakpoint at ds.ReadXml(xmlData);) the dataset you'll see that the Users and User elements actually ends up as records in their own tables. The records have id columns that enables you to relate the rows back to the containing SearchResult row.
In your sample your are binding Tables[1], which is the SearchResult table, to the repeater. You should rather make a selection into the dataset joining the three tables together. LINQ should prove useful here.
You can use xml deserialization to helper class, that copies the structure of xml nodes and inside the repeater create another repeater for Users collection like this:
<asp:Repeater runat="server" DataSource='<%# Eval("Users") %>' >...