How do I hide empty nested repeaters?
In an asp.net page, I have nested repeaters to render up to four levels of my sitemap.
Question: How can I tell nested relays not to show a node if the node has no children? More specifically, when the nested relay has no children, it displays as a pair of tags <ul></ul>
what I need to hide from the displayed list.
What works: The HTML and script provided here correctly renders web.sitemap as a pure unordered list, only with empty UL tags where node has no children.
What I have tried (unconvincing):
- I looked into jQuery. While I found the correct jQuery command to remove empty pairs
<ul>
, the webresource is absolutely sure to overwrite jQuery commands. (Which also makes it impossible to use jQuery to remove the classes that put the rendered asp: menu). In other words, I have not yet found a way for jQuery to clean up something generated by the asp.net control. - I found forum posts, but they all seem to have repeated the same two-tier depth-repeater without removing the childless nodes.
- I double checked the web.sitemap file and made sure I didn't have anything in the source that would facilitate this. No problem with the source.
Looking around I found a very promising post here: Hide child and parent repeater when child repeater is empty . Adapting it to VB.NET and adding a null detection if-then statement, I have:
Protected Sub HideIfEmpty(sender As Object, e As RepeaterItemEventArgs) Handles Repeater0.ItemDataBound
If e.Item.ItemType = ListItemType.Item Then
If e.Item.FindControl("Repeater4") IsNot Nothing Then
If (DirectCast(e.Item.FindControl("Repeater3"), Repeater).Items.Count = 0) Then
e.Item.Visible = False
End If
End If
End If
End Sub
Based on the comment, I also tried Sub Preender. He submitted a list of cards with the same empty UL tags. Using breakpoints didn't determine why it didn't work as desired.
This is the aspx code:
<asp:SiteMapDataSource ID="siteMapDataSource1" runat="server" ShowStartingNode="false" />
<asp:Repeater runat="server" ID="Repeater0" DataSourceID="SiteMapDataSource1">
<HeaderTemplate>
<ul>
<li>
<asp:HyperLink ID="HyperLink4" runat="server" NavigateUrl="~/index.aspx">Homepage</asp:HyperLink>
</li>
</HeaderTemplate>
<ItemTemplate>
<li>
<asp:HyperLink ID="HyperLink1" runat="server" NavigateUrl='<%# Eval("Url") %>'><%# Eval("Title") %></asp:HyperLink>
<asp:Repeater ID="Repeater1" runat="server" DataSource='<%# DirectCast(Container.DataItem, SiteMapNode).ChildNodes%>'>
<HeaderTemplate>
<ul>
</HeaderTemplate>
<ItemTemplate>
<li>
<asp:HyperLink ID="HyperLink2" runat="server" NavigateUrl='<%# Eval("Url") %>'><%# Eval("Title") %></asp:HyperLink>
<asp:Repeater ID="Repeater2" runat="server" DataSource='<%# DirectCast(Container.DataItem, SiteMapNode).ChildNodes%>'>
<HeaderTemplate>
<ul>
</HeaderTemplate>
<ItemTemplate>
<li>
<asp:HyperLink ID="HyperLink3" runat="server" NavigateUrl='<%# Eval("Url") %>'><%# Eval("Title") %></asp:HyperLink>
<asp:Repeater ID="Repeater3" runat="server" DataSource='<%# DirectCast(Container.DataItem, SiteMapNode).ChildNodes%>' OnPreRender="HideIfEmpty">
<HeaderTemplate>
<ul>
</HeaderTemplate>
<ItemTemplate>
<li>
<asp:HyperLink ID="HyperLink3" runat="server" NavigateUrl='<%# Eval("Url") %>'><%# Eval("Title") %></asp:HyperLink>
</li>
</ItemTemplate>
<FooterTemplate>
</ul>
</FooterTemplate>
</asp:Repeater>
</li>
</ItemTemplate>
<FooterTemplate>
</ul>
</FooterTemplate>
</asp:Repeater>
</li>
</ItemTemplate>
<FooterTemplate>
</ul>
</FooterTemplate>
</asp:Repeater>
</li>
</ItemTemplate>
<FooterTemplate></ul></FooterTemplate>
</asp:Repeater>
This is the rendered HTML:
<ul id="menu_Main" class="sm sm-blue">
<li>
<a id="Repeater0_HyperLink4" href="index.aspx">Homepage</a>
</li>
<li>
<a id="Repeater0_HyperLink1_0">Data Manager Documents</a>
<ul>
<li>
<a id="Repeater0_Repeater1_0_HyperLink2_0" href="/Data_Manager/Skill_Journal.aspx">Skill Journal</a>
<ul>
</ul>
</li>
<li>
<a id="Repeater0_Repeater1_0_HyperLink2_1">Test Pages</a>
<ul>
<li>
<a id="Repeater0_Repeater1_0_Repeater2_1_HyperLink3_0" href="/Data_Manager/xslt_test.aspx">XSLT Test</a>
<ul>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>
<a id="Repeater0_HyperLink1_1">Incident Report</a>
<ul>
<li>
<a id="Repeater0_Repeater1_1_HyperLink2_0" href="http://wales:4885/IncidentReport/IncidentReport.aspx">Add/Edit/View</a>
<ul>
</ul>
</li>
<li>
<a id="Repeater0_Repeater1_1_HyperLink2_1" href="http://wales:4885/IncidentReport/V1_History.aspx">Archive</a>
<ul>
</ul>
</li>
</ul>
</li>
<li>
<a id="Repeater0_HyperLink1_2" href="/Ordering_Database/Ordering_Database.aspx">Ordering Database</a>
<ul>
<li>
<a id="Repeater0_Repeater1_2_HyperLink2_0" href="/Ordering_Database/Reports.aspx">Reports</a>
<ul>
</ul>
</li>
<li>
<a id="Repeater0_Repeater1_2_HyperLink2_1" href="/Ordering_Database/Stats.aspx">Stats</a>
<ul>
</ul>
</li>
<li>
<a id="Repeater0_Repeater1_2_HyperLink2_2" href="/Ordering_Database/View_Items.aspx">View Items</a>
<ul>
</ul>
</li>
<li>
<a id="Repeater0_Repeater1_2_HyperLink2_3" href="/Ordering_Database/Manage_Items.aspx">Manage Items</a>
<ul>
</ul>
</li>
<li>
<a id="Repeater0_Repeater1_2_HyperLink2_4">Utility</a>
<ul>
</ul>
</li>
</ul>
</li>
So the sitemap that appears in the nested unordered list is correct, but still creates empty UL tags that should be removed on the server side.
For this system, this is what worked for me as a true server side answer.
In the script:
<script>
Private Sub Hide_The_Orphans(ByVal sender As Object, ByVal e As RepeaterItemEventArgs)
Dim rpt As Repeater = CType(sender, Repeater)
If rpt IsNot Nothing Then
If rpt.Items.Count = 0 Then
rpt.Visible = False
Else
rpt.Visible = True
End If
End If
End Sub
</script>
In the body (I also changed the asp: hyperlink tags to tags <a>
to clear all those hyperlinks in the rendered HTML):
<nav>
<asp:SiteMapDataSource ID="siteMapDataSource1" runat="server" ShowStartingNode="false" />
<asp:Repeater runat="server" ID="Repeater0" DataSourceID="SiteMapDataSource1">
<HeaderTemplate>
<ul id="menu_Main" class="sm sm-blue">
<li>
<a href='<%# Page.ResolveClientUrl("~/index.aspx")%>'>Homepage</a>
</li>
</HeaderTemplate>
<ItemTemplate>
<li>
<a href='<%# Eval("Url") %>'><%# Eval("Title") %></a>
<asp:Repeater ID="Repeater1" runat="server" DataSource='<%# DirectCast(Container.DataItem, SiteMapNode).ChildNodes%>' OnItemDataBound="Hide_The_Orphans">
<HeaderTemplate>
<ul>
</HeaderTemplate>
<ItemTemplate>
<li>
<a href='<%# Eval("Url") %>'><%# Eval("Title") %></a>
<asp:Repeater ID="Repeater2" runat="server" DataSource='<%# DirectCast(Container.DataItem, SiteMapNode).ChildNodes%>' OnItemDataBound="Hide_The_Orphans">
<HeaderTemplate>
<ul>
</HeaderTemplate>
<ItemTemplate>
<li>
<a href='<%# Eval("Url") %>'><%# Eval("Title") %></a>
<asp:Repeater ID="Repeater3" runat="server" DataSource='<%# DirectCast(Container.DataItem, SiteMapNode).ChildNodes%>' OnItemDataBound="Hide_The_Orphans">
<HeaderTemplate>
<ul>
</HeaderTemplate>
<ItemTemplate>
<li>
<a href='<%# Eval("Url") %>'><%# Eval("Title") %></a>
</li>
</ItemTemplate>
<FooterTemplate>
</ul>
</FooterTemplate>
</asp:Repeater>
</li>
</ItemTemplate>
<FooterTemplate>
</ul>
</FooterTemplate>
</asp:Repeater>
</li>
</ItemTemplate>
<FooterTemplate>
</ul>
</FooterTemplate>
</asp:Repeater>
</li>
</ItemTemplate>
<FooterTemplate></ul></FooterTemplate>
</asp:Repeater>
</nav>
And to prove to yourself this is a true server side solution, it's from the "source code".
<ul id="menu_Main" class="sm sm-blue">
<li>
<a href='index.aspx'>Homepage</a>
</li>
<li>
<a href=''>Data Manager Documents</a>
<ul>
<li>
<a href='/Data_Manager/Skill_Journal.aspx'>Skill Journal</a>
</li>
<li>
<a href=''>Test Pages</a>
<ul>
<li>
<a href='/Data_Manager/.aspx'>Sitemap test</a>
</li>
<li>
<a href='/Data_Manager/xslt_test.aspx'>XSLT Test</a>
</li>
</ul>
</li>
</ul>
</li>
etc....
The places you saw are empty href tags because these specific nodes are more like folder containers, so the design is not a mistake.
Another plus for this approach is that there is only one subsystem that can be used by any of the repeaters, instead of using the chained approach as I previously tried.
I think you are pretty much there. One of your problems is when you are trying to find Repeater3 in Repeater0 when it is actually in Repeater2. You need to walk down the repeater line correctly by finding the correct child ID. Here's an example of using the event OnPreRender
, although you can still make it work for OnItemDataBound
using similar logic.
<asp:Repeater runat="server" ID="Repeater0" DataSourceID="SiteMapDataSource1" OnPreRender="Repeater0_PreRender">
In this case, every child repeater slips. If the child repeater has no items, hide the entire repeater. You can get an idea of ββthis code here. That said, you are probably better off recursively looping repeaters.
Protected Sub Repeater0_PreRender(ByVal sender As Object, ByVal e As EventArgs)
Dim repeater0 As Repeater = sender
If repeater0.Items.Count = 0 Then
repeater0.Visible = False
Else
For Each repeater0Item As RepeaterItem In repeater0.Items
If repeater0Item.ItemType = ListItemType.Item Then
Dim repeater1 As Repeater = repeater0Item.FindControl("Repeater1")
If repeater1.Items.Count = 0 Then
repeater1.Visible = False
Else
For Each repeater1Item As RepeaterItem In repeater1.Items
If repeater1Item.ItemType = ListItemType.Item Then
Dim repeater2 As Repeater = repeater1Item.FindControl("Repeater2")
If repeater2.Items.Count = 0 Then
repeater2.Visible = False
Else
// so on and so forth
End If
End If
Next
End If
End If
Next
End If
End Sub
source to share