views:

612

answers:

4

on the aspx:

<table>
<tr>
<asp:Repeater ID="rptHeader" runat="server">
    <ItemTemplate>
        <th><%#Eval("Category")%></th>
    </ItemTemplate>
</asp:Repeater>
</tr>
<tr>
<asp:Repeater ID="rptContents" runat="server">
    <ItemTemplate>
        <td valign="top">
            <%#Eval("Content")%>
        </td>
    </ItemTemplate>
</asp:Repeater>
</tr>
</table>

on the code-behind:

protected void Page_Load(object sender, EventArgs e) 
{
    rptHeader.DataSource = DataSource;
    rptHeader.DataBind();
    rptContentBlocks.DataSource = DataSource;
    rptContentBlocks.DataBind();
}

The problem here is that instead of using Two repeaters, can we use only one? We actually need the header to be separated from the contents using a different table row...

Edit: changed rptHeader's ItemTemplate's html element from <td> to <th> to be a little clearer. :-)

+1  A: 

An HTML table is always declared as cells nested within rows, i.e.

<table>
    <tr>
        <td>
        ...
        </td>
    </tr>
</table>

rather than

<table>
    <td>
        <tr>
        ...
        </tr>
    </td>
</table>

This means you won't be able to write a single Category followed by a single Content. You will have to repeate the Category values and then the Content values as you are already doing.

I see nothing wrong with the approach you are using. The use of 2 repeaters rather than one should be a negligible overhead.

The alternative would be to nest a table within each column. This would allow you to use just a single repeater but the resulting HTML would be rather contrived and bloated. I would not recommend this approach but somehow I can't stop myself from providing an example.

<table>
<tr>    
<asp:Repeater ID="rptHeader" runat="server">    
    <ItemTemplate>    
        <td>
            <table>
                <tr>
                    <td valign="top">    
                        <strong><%#Eval("Category")%></strong>    
                    </td>
                </tr>
                <tr>
                    <td valign="top">    
                        <%#Eval("Content")%>    
                    </td>
                </tr>
            </table>
        </td>
    </ItemTemplate>    
</asp:Repeater>    
</tr>    
</table>
AdamRalph
we just need to have a single table, not a nested one... I have tried that before using 2 repeaters actually. =)
Jronny
yes, definitely don't nest your tables, it's a bad idea. However, I don't see why you'd need 2 repeaters to do so
AdamRalph
Well, it's just that I have not found a way to use a single repeater for tables with multiple dynamic header data... Thanks anyways!
Jronny
sorry this is getting confusing. to clarify... single table with dynamic headers = 2 repeaters. nested tables = 1 repeater (although nested tables is a bad idea)
AdamRalph
A: 

Correct me if i misunderstood but it sounds to me like you could use only one repeater, integrating your first repeater into a HeaderTeplate tag and the second into the ItemTemplate.

GxG
you mean, the <%#Eval("Category")%> on the HeaderTemplate and <%#Eval("Content")%> on the ItemTemplate?
Jronny
yes... if that is what you need... if you need something like category1: value1category2: value2.................then this won't work
GxG
Unfortunately, HeaderTemplate does not actually loop through the values on the DataSource...
Jronny
then try something like AdamRalph said but instead of a table inside the repeater use <div></div>... it's not as elegant as a table but it might do the job...
GxG
This might just do the trick, but not as pretty as what we need.
Jronny
+1  A: 

If it's important to place the content in a table, then you could "rotate" your table into a different structure and bind on that structure. Or if it's not important that the data is in a table, you could place the items in divs and float them so they're side-by-side.

Edit:

Here's what I mean by rotating the data. If your data is currently in a structure like List<MyDataClass>, where MyDataClass is defined as something like:

class MyDataClass
{
    public string Category { get; set; }
    public string Content { get; set; }
    public int OtherField { get; set; }
}

...then the logical layout when iterating through the structure would look like this:

MyDataClass[0]: Category, Content, OtherField
MyDataClass[1]: Category, Content, OtherField
...

When rotating the data, you'd turn those rows into columns and columns into rows. For example, you could populate a List<List<string>> with code such as this:

var rotated = new List<List<string>> {
    new List<string>(), new List<string>(), new List<string>(),
};
for each (MyDataClass object in myCollection) 
{
    rotated[0].Add(object.Category);
    rotated[1].Add(object.Content);
    rotated[2].Add(object.OtherField.ToString("n0"));
}

Now your data structure looks like this:

rotated[0]: MyDataClass[0].Category, MyDataClass[1].Category, ...
rotated[1]: MyDataClass[0].Content, MyDataClass[1].Content, ...
rotated[2]: MyDataClass[0].OtherField, MyDataClass[1].OtherField, ...

Basically, you transform the data into a form that can be dropped right into your table.

Edit 2:

I actually have to do these sorts of rotations for reports often enough that I made an extension method for IEnumerable that will do the rotation in a somewhat more elegant manner. Here's the extension method:

public static class MyCollectionExtensionMethods
{
    public static IEnumerable<IEnumerable<TResult>> Rotate<TOrig, TResult>(
        this IEnumerable<TOrig> collection, 
        params Func<TOrig, TResult>[] valueSelectors)
    {
        return valueSelectors.Select(s => collection.Select(i => s(i)));
    }
}

And here's how I'd use it:

IEnumerable<IEnumerable<string>> rotated = data.Rotate(
    i => i.Category, i => i.Content, i => i.OtherField.ToString("n0"))
Jacob
we need tables, definitely... So if this is the case, I have no idea how to "rotate" the table's structure =)
Jronny
It's not pretty. I'll elaborate in my answer as to what I mean by rotation.
Jacob
uhm, it's not pretty all right... But I might need this "rotation" thingy someday. Thanks!
Jronny
+2  A: 

IMO it's impossible without repeater hacking. Anyway, there is a rather irregular approach that maybe works. Also you can use two foreach statements instead of repeaters.

Code

protected string[] headers = { "A", "B", "C", "D" };
protected string[] contents = { "Alpha", "Beta", "Counter", "Door" };

//string[] headers = { "A", "B", "C", "D" };
//string[] contents = { "Alpha", "Beta", "Counter", "Door" };

//DataList1.RepeatColumns = headers.Length;
//DataList1.DataSource = headers.Concat(contents);
//DataList1.DataBind();

Html markup

<table>
    <tr>
        <%foreach (string item in headers)
          { %>
          <th><%= item %></th>
        <% } %>
    </tr>
    <tr>
        <%foreach (string item in contents)
          { %>
          <td><%= item %></td>
        <% } %>
    </tr>
</table>
<!--
    <asp:DataList ID="DataList1" runat="server" RepeatDirection="Horizontal">
        <ItemTemplate>
            <%# Container.DataItem %>
        </ItemTemplate>
    </asp:DataList>
-->
Mehdi Golchin
+1 for foreach loops. I would reserve repeaters for only when you need to create controls. The data binding syntax is ugly in that it's not as type-safe as regular code.
Jacob
Nice trick! Now, the problem is the `<th>`. Thanks
Jronny
oh! If you are after generating headers from `<th>` elements, should use two seperated `foreach` statements as above.
Mehdi Golchin