Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to nest repeaters in asp.net

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.

like image 620
Nathan Avatar asked May 30 '11 02:05

Nathan


3 Answers

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();
}   
like image 87
Katie Kilian Avatar answered Sep 22 '22 15:09

Katie Kilian


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>
like image 28
lomaxx Avatar answered Sep 23 '22 15:09

lomaxx


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();
}
like image 43
EdmundYeung99 Avatar answered Sep 20 '22 15:09

EdmundYeung99