Search code examples
arraysdynamichtml-tablerepeater

Generating Dynamic Table Content in C#


I'm having a bit of a challenge to create the following:

I need to generate a number of fixed sized tables of 2 columns and 4 rows in ASP.NET based on an xml content. So my html container is a multiple of table with a fixed sized 2 columns by 4 rows. In each table, each column will hold an [item] from the xml, where the top cell in the column is the [category] tag, and the 3 cells underneath holds the [make#] tags; so one for each [make#] for a total of 4 rows in the column. So a Table can hold 2 [item] ; one for each column.

Assuming my source is from an XML file:

<items>
<item>
<category>Cat 1</category>
<make1>101</make1>
<make2>102</make2>
<make3>103</make3>
</item>
<item>
<category>Cat 2</category>
<make1>201</make1>
<make2>202</make2>
<make3>203</make3>
</item>
<item>
<category>Cat 3</category>
<make1>301</make1>
<make2>302</make2>
<make3>303</make3>
</item>
<item>
<category>Cat 4</category>
<make1>401</make1>
<make2></make2>
<make3></make3>
</item>
<item>
<category>Cat 5</category>
<make1>501</make1>
<make2>502</make2>
<make3</make3>
</item>
</items>

and my desired output for the 5 categories where each category can have up to 3 items underneath in html is:

<table> 
<tr> <td>Cat 1</td> <td>Cat 2</td> </tr> 
<tr> <td>101</td> <td>201</td> </tr> 
<tr> <td>102</td> <td>202</td> </tr> 
<tr> <td>103</td> <td>203</td> </tr> 
</table>
<table> 
<tr> <td>Cat 3</td> <td>Cat 4</td> </tr> 
<tr> <td>301</td> <td>401</td> </tr> 
<tr> <td>302</td> <td>&nbsp;</td> </tr> 
<tr> <td>303</td> <td>&nbsp;</td> </tr> 
</table>
<table> 
<tr> <td>Cat 5</td> <td>&nbsp;</td> </tr> 
<tr> <td>501</td> <td>&nbsp;</td> </tr> 
<tr> <td>502</td> <td>&nbsp;</td> </tr> 
<tr> <td>&nbsp;</td> <td>&nbsp;</td> </tr> 
</table>

How would I achieve this?

Note I need tables because I'm using a carousel with a hard coded carousel with that structure...


Solution

  • After some discussion I think we came to the conclusion that the repeater method is not the best route to take. The basic problem here is how to input two entities side-by-side. There is an asp.net control built for this: the DataList.

    With the DataList, you can set the maximum number of items to repeat as well as the direction to repeat the items (Horizontal / Vertical).

    So, with that, you will want to add the following DataList code to your aspx page:

    <asp:DataList ID="DataList1" runat="server" DataSourceID="XmlDataSource1" RepeatColumns="2" RepeatDirection="Horizontal">
        <HeaderTemplate>Items</HeaderTemplate>
        <ItemTemplate>
            <table border=1>
                <tr>
                    <td>
                        <%# XPath("category")%>
                    </td>
                </tr>
                <tr>
                    <td>
                        <%# XPath("make1")%>
                    </td>
                </tr>
                <tr>
                    <td>
                        <%# XPath("make2")%>
                    </td>
                </tr>
                <tr>
                    <td>
                        <%# XPath("make3")%>
                    </td>
                </tr>
            </table>
    
        </ItemTemplate>
    </asp:DataList>
    

    The key here is that each item's data is bound in the <ItemTemplate>. Then the DataList itself takes care of arranging each item according to the flags RepeatColumns="2" and RepeatDirection="Horizontal".

    Some quick <asp:XmlDataSource> magic:

    <asp:XmlDataSource ID="XmlDataSource1" runat="server" 
        XPath="//items/item">
    </asp:XmlDataSource>
    

    Where XPath="//items/item" points the XmlDataSource to the location of each entity in the xml. This is bound to the DataList with the following tag on the DataList itself: DataSourceID="XmlDataSource1"

    On the code-behind file I cheated and just fed the XML as a string:

      this.XmlDataSource1.Data = @"<items>
                                      <item>
                                          <category>Cat 1</category>
                                          <make1>101</make1>
                                          <make2>102</make2>
                                          <make3>103</make3>
                                      </item>
                                      <item>
                                          <category>Cat 2</category>
                                          <make1>201</make1>
                                          <make2>202</make2>
                                          <make3>203</make3>
                                      </item>
                                      <item>
                                          <category>Cat 3</category>
                                          <make1>301</make1>
                                          <make2>302</make2>
                                          <make3>303</make3>
                                      </item>
                                      <item>
                                          <category>Cat 4</category>
                                          <make1>401</make1>
                                          <make2></make2>
                                          <make3></make3>
                                      </item>
                                      <item>
                                          <category>Cat 5</category>
                                          <make1>501</make1>
                                          <make2>502</make2>
                                          <make3></make3>
                                      </item>
                                      </items>";
    

    And she works!

    BTW, I'll never forgive you for the missing ">" in the XML of your original post. Drove me nuts finding it. :)

    Now, granted that ideally the best method to do all this is deserializing the xml into .NET classes and binding everything the proper way (ie: code-behind binding of data, etc), but the general idea is there.

    Regarding OP comment about nested table issue:

    The GridList has a property called RepeatLayout. Setting this property to RepeatLayout.Flow will use other elements rather than tables. From MSDN:

    Items are displayed without a table structure. Rendered markup consists of a span element and items are separated by br elements. Layouts for each enum here

    Also, this article discusses the differences between the DataGrid, DataList and Repeater MSDN

    OLD ANSWER:

    I'm thinking along the lines of a Usercontrol that accepts two item entities as parameters.

    So if you have a class called Item:

    public class Item
    {
        public string Category{get;set;}
        public List<string> Makes { get; set; }
    }
    

    And your UserControl looks like this: (ASPX)

    <table> 
    <tr>
        <td><asp:Label ID="lblCategory1" runat="server" /></td>
        <td><asp:Label ID="lblCategory2" runat="server" /></td>
    </tr> 
    <asp:Repeater ID="rptMakes" runat="server">
        <HeaderTemplate><tr></tr></HeaderTemplate>
        <ItemTemplate>
            <td>
            <asp:Label ID="lblMake1" runat="server" />
            </td>
            <td>
            <asp:Label ID="lblMake2" runat="server" />
            </td>
        </ItemTemplate>
        <SeparatorTemplate></tr><tr></SeparatorTemplate>
        <FooterTemplate>
            </tr>
        </FooterTemplate>
    </asp:Repeater>
    </table>
    

    and like this on the .cs page:

    private Item Item2;
    
    protected override void OnInit(EventArgs e)
    {
        this.rptMakes.ItemDataBound += new RepeaterItemEventHandler(rptMakes_ItemDataBound);
        base.OnInit(e);
    }
    
    void rptMakes_ItemDataBound(object sender, RepeaterItemEventArgs e)
    {
        if (e.Item.ItemType == ListItemType.Item || e.Item.ItemType == ListItemType.AlternatingItem)
        {
            ((Label)e.Item.FindControl("lblMake1")).Text = e.Item.DataItem.ToString();
            if (this.Item2 != null)
            {
                ((Label)e.Item.FindControl("lblMake2")).Text = this.Item2.Makes[e.Item.ItemIndex];
            }
            else
            {
                ((Label)e.Item.FindControl("lblMake2")).Text = "&nbsp;";
            }
        }
    }
    
    protected void Page_Load(object sender, EventArgs e)
    {
    
    }
    public void Populate(Item item1, Item item2)
    {
        this.lblCategory1.Text = item1.Category;
        if (item2 != null)
        {
            this.lblCategory2.Text = item2.Category;
        }
        else
        {
            this.lblCategory2.Text = "&nbsp;";
        }
        this.Item2 = item2;
        this.rptMakes.DataSource = item1.Makes;
        this.rptMakes.DataBind();
    }
    

    From there it is just a matter of iterating over the original list of Item entities on your aspx page and passing out every two entities to a UC.