Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Variable repeater columns

I have an objectdatasource which i want to bind to a repeater. the problem is, I cannot work out how to display a variable amount of columns with a variable amount of rows.

for example:

The dataset I have is structured like this. The objectdatasource is a List<item>.

item {
  string name;
  List<itemdata> data;
}

itemdata {
  DateTime year;
  double amount;
}

so basically i want to make a table

      |  year  |  year  |  year  |  year
 name | amount | amount | amount | amount
 name | amount | amount | amount | amount
 name | amount | amount | amount | amount
 name | amount | amount | amount | amount

The number of items are variable, as well as the number of itemdata that the item contains.

Hope someone can point me in the right direction.

Thanks

like image 738
Mike Avatar asked Jan 19 '10 01:01

Mike


1 Answers

The solution to your problem will require three different repeaters, one of which is nested inside another. Begin with the markup like this.

  <table>
       <tr class="headerRow">
          <td> &nbsp;</td>
          <asp:Repeater ID="rptYearHeader" runat="server" OnItemDataBound="rptYearHeader_ItemDataBound">
              <ItemTemplate>
                 <td class="header"><asp:Literal ID="litYear" runat="server"></asp:Literal></td>
              </ItemTemplate>
          </asp:Repeater>
       </tr>
       <asp:Repeater ID="rptName" runat="server" ItemDataBound="rptName_ItemDataBound">
          <ItemTemplate>
             <tr>
                <td><asp:Literal ID="litName" runat="server"></asp:Literal></td>
                <asp:Repeater ID="rptAmounts" runat="server" OnItemDataBound="rptAmounts_ItemDataBound">
              <ItemTemplate>
                 <td><asp:Literal ID="litAmount" runat="server"></asp:Literal></td>
              </ItemTemplate>
          </asp:Repeater>
             </tr>
          </ItemTemplate>
       </asp:Repeater>
    </table>

Binding to this can be a little tricky. The idea is, first we bind the header row - then we bind down the data rows and across the columns. You will want to handle the data binding through code behind using the OnItemDataBound event so that you can wire up the nested repeater with the necessary data.

First we bind the header row with Years. You need to isolate a collection of unique years present in your datasource and keep it in a private variable. You will need to access it during the data binding of the other repeaters later. This will serve as the data source for the header row, creating one cell/column for each year.

List<DateTime> _Years =  dataSource.SelectMany(x => x.data).GroupBy(y => y.Year);
rptYear.DataSource = _Years;
rptYear.DataBind();

Now, you need to bind the Name repeater with your original data source. Something like

rptName.DataSource = dataSource;
rptName.DataBind();

This will create one row for each item in your list.

During the OnItemDataBound event for this repeater, you will need to bind the nested repeater to a list of fiscal years - one per each fiscal year in our _Years variable - with any applicable data from the current row's data item. This gets a little tricky, but I'll try to explain:

protected void rptName_ItemDataBound(object sender, RepeaterItemEventArgs e)
{
// get the data item being bound
item currentItem = e.Item.DataItem as item;

// bind the item's name to the literal
//...
//

// get a list of amounts to bind to the nested repeater
// because we cant be sure that every item has amount for all years
// we create a list that we know has all years and plug in the items 
// data accordingly.

    List<double> amounts = new List<double>();
     for (int i = 0; i < _Years.Count; i++)
     {
        // check whether the current item has data for the year
        dataItem di = currentItem.data.Where(d => d.Year == _Years[i]).FirstOrDefault();

        if(di == null)
        {
             // the year did not exist, so we add an amount of 0 
             amounts.Add(0);
        }
        else
        {
           // the year did exist, so we add that year's amount
           amounts.Add(di.amount);
        }
     }

     // we now have a list of amounts for all possible years, with 0 filling in
     // where the item did not have a value for that year

     // bind this to the nested repeater
     rptAmounts.DataSource = amounts;
     rptAmounts.DataBind();

}

Good luck.

I have had to pull this off with multiple nested repeaters for sub-total and grand total rows before. I started seeing nested repeaters in my sleep.

like image 133
Ben Elder Avatar answered Nov 24 '22 10:11

Ben Elder