2/17/2008 3:06:09 AM
I was working on a personal project of mine tonight that has been in the works for a couple of months now. One piece of functionality required having nested Repeaters. I had my outer repeater, a nested repeater, and then a repeater inside of that repeater for a total of three. At first that sounds like a lot of data, which it is. The idea is that I would only display the top-level data (outer repeater) and have a link for "Expand" to view the nested repeater and within this repeater expand even more. You get the idea. Think of an outer repeater displaying department stores, the nested repeater would have departments in that department store and the nested repeater for the departments in the store might have sales data for a given time period. FWIW, I'm using JavaScript calendar pop-ups to select date ranges as a possible way to filter the data. For this example I will simply show how to display one nested repeater with simply a "view" link.
In the past I've accomplished this functionality with a bunch of postbacks. Lately I've been on this kick where, if at all possible; I will try to not have any unnecessary postbacks. Since my JavaScript game is impeccable (not really), I decided to tackle this. Again, coupled with Control.ClientID I figured this would be a breeze. It was.
<
asp:Repeater ID="repeater" OnItemDataBound="ItemDataBound" runat="server">
<ItemTemplate>
<span><%# Eval("Foo") %></span>
<span><%# Eval("Bar") %></span>
<span><asp:Label ID="view" Text="View" runat="server" /></span>
<div id="details" style="display: none;" runat="server">
<asp:Repeater ID="nested" runat="server">
<ItemTemplate>
<div style="text-align: left;">
<span><%# Eval("Hai") %></span>
<span><%# Eval("LOL") %></span>
<span><%# Eval("ROFL") %></span>
</div>
</ItemTemplate>
</asp:Repeater>
</div>
</ItemTemplate>
</asp:Repeater>
Simple asp:Repeater web control. The ItemDataBound event is where we are going to add pertinent code. I'll get to that in a minute. Notice how I have a div wrapping my nested repeater? This is important to grasp. In my post the other day I mentioned how display: none is better than visibility: hidden when it comes to styling elements of your page. With display: none the elements are ignored when the page is created (i.e. as if they are not there). With visibility: hidden the elements are added to the flow of the page even though you can't visibly see them. With a large set of data (or small) your real estate will diminish rather quickly. In the case above, when the nested repeater is bound it won't be calculated into the flow of the page.
protected
void ItemDataBound(object sender, RepeaterItemEventArgs e)
{
if (e.Item.ItemType == ListItemType.Item ||
e.Item.ItemType == ListItemType.AlternatingItem)
{
Foo foo = e.Item.DataItem as Foo;
Label view = e.Item.FindControl("view") as Label;
HtmlGenericControl div = e.Item.FindControl("details") as HtmlGenericControl;
Repeater repeater = e.Item.FindControl("nested") as Repeater;
if (foo != null &&
repeater != null &&
view != null &&
div != null)
{
view.Attributes.Add("onclick", string.Format("javascript:(display('{0}')", div.ClientID));
repeater.DataSource = _cachedListOfT.FindAll(
delegate(FooDetails match)
{
return match.FooId.Equals(foo.FooId);
});
repeater.DataBind();
}
}
}
Here we are casting the current data Item to type Foo and searching for our controls. It is always wise to use the as operator instead of a direct cast. You can never guarantee those controls will be there. A designer could go in and try to be a hero one day and the next thing you know your application just shit itself. The JavaScript function is added to the onclick event of our asp:Label. I am then setting the DataSource of the nested Repeater to a List<T> of a cached collection of objects and using a predicate by way of a delegate to filter out pertinent data. There could probably be a better way to do this but I'm lazy and this is only a proof-of-concept.
function
display(repeaterId)
{
document.getElementById(repeaterId).style.display = "";
}
I'm not even going to explain that JavaScript. Actually, yes. The intuitive reader might be wondering why I have an empty string for the display. It should display as "inherit" or "block" but for some reason no one in the browser development world can agree on anything so if you set it to nothing the browser will display it by default because in trying to determine what an empty string means the browser comes to a point where it simpy says, "F**k it." At least that's how I read why it works this way.
In my real-life version of this I determine the css display style with JavaScript and change the text to "Hide" and clicking on the link again will hide the div. I didn't want to confuse people so I'll just keep it simple.
Have fun with this (or not). Also, this can be extended to display the nested data asynchronously. The onclick event of the "view" link could make an asynchronous request via XmlHttpRequest or ClientCallback and return a string of HTML with your data and then set the innerHTML of the details div to this value. Easy.

C#,
JavaScript

Comments

I like the nested repeaters, they come in handy for obvious reasons. I`m not a fan of using server events to intercept rendering to add client side functionality. I used to be but I`ve found better(IMO) ways. Tacking on client side functionality while one the client side. Using something like this: http://panteravb.com/play/toggle.html
prototype, jquery, and lots of other libraries make this a breeze.
Posted by: Chris Carter | 2/17/2008 9:36:29 PM

That is pretty cool Chris. I need to explore some of those JavaScript libraries when I get some time. The reason I have done things this way is because of time constraints. Pretty much it`s use AJAX .NET or roll my own.
I`d use AJAX .NET but I stop reading a tutorial and close my browser when I read, "to make this asynchronous all you need to do is wrap your gridview in an update panel..."
Posted by: Will Asrari | 2/17/2008 11:10:21 PM