I need to display a nested repeater. Here is my class:
Public Class ItemData
Public Property ID() As Integer
...
Public Property KitItems() As List(Of ItemData)
End Class
And here is the markup:
<asp:Repeater ID="itemRepeater" Runat="server">
<HeaderTemplate>
<table id="wrapper" cellspacing="0" cellpadding="0">
<tr >
<th>Item No</th>
<th>Units/Pkg</th>
<th>Cart Qty</th>
<th>Total Units</th>
<th>Size</th>
<th>Color</th>
<th>Unit Price</th>
<th>Total</th>
</tr>
</HeaderTemplate>
<ItemTemplate>
<tr >
<td><%# DataBinder.Eval(Container.DataItem, "ItemNo") %></td>
<td><%# DataBinder.Eval(Container.DataItem, "Units") %></td>
<td><%# DataBinder.Eval(Container.DataItem, "Qty") %></td>
<td><%#DataBinder.Eval(Container.DataItem, "TotalUnits")%></td>
<td><%# DataBinder.Eval(Container.DataItem, "Size") %></td>
<td><%# DataBinder.Eval(Container.DataItem, "Color") %></td>
<td><asp:Literal ID="ltlCost" runat="server" /></td>
<td><asp:Literal ID="ltlTotalCost" runat="server" /></td>
</tr>
<asp:Repeater runat="server" DataSource='<%# Eval("KitItems") %>'>
<ItemTemplate><%# Eval("Name") %></ItemTemplate>
<SeparatorTemplate>,</SeparatorTemplate>
</asp:Repeater>
</ItemTemplate>
<FooterTemplate>
</FooterTemplate>
</asp:Repeater>
On this line
<asp:Repeater runat="server" DataSource='<%# Eval("KitItems") %>'>
I get an exception:
System.ArgumentException: 'An invalid data source is being used for . A valid data source must implement either IListSource or IEnumerable.'
I don't understand why List that implements IEnumerable is not a valid data source.
CodePudding user response:
ok,
So, this:
Public Class clsHotel
Public Property ID As Integer
Public Property FirstName As String
Public Property LastName As String
Public Property HotelName As String
End Class
But, the "list" you pass has to be based on ONLY exposed "simple" or base items. If you expose MORE, then that will not work.
ALSO DON'T call you class ItemData - that is a reserved word - it will cause you world poverty.
I don't see "much" logic in having a array (list) of items belonging to that class. (HOWEVER -- if you create or have a "add" method in the class, and you thus don't have to add an "instance" of the class, but ALWAYS use the "add" method of the class with parameters, then ok - that's fine.
In code, you can (would) declare an array or list of that class.
So, say I wanted to fill out a grid view with above.
So, my markup is this:
<asp:Button ID="Button1" runat="server" Text="Fill grid"
cssClass="btn"/>
<br />
<br />
<asp:GridView ID="GridView1" runat="server"
css Width="30%">
</asp:GridView>
And now the button code for above can then be this based on the class:
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
End Sub
Protected Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim MyHotels As New List(Of clsHotels)
For i = 1 To 10
Dim OneHotel As New clsHotels
OneHotel.ID = i
OneHotel.FirstName = "First " & i
OneHotel.LastName = "Last " & i
OneHotel.HotelName = "Hotel #" & i
' add to our "list" of hotels
MyHotels.Add(OneHotel)
Next
' now bind to grid or repeater
GridView1.DataSource = MyHotels
GridView1.DataBind()
End Sub
And our result is now this:
And for your example with a "list" exposed, so we don't have to create the array (or list) in code, then you can do this:
Our class becomes this:
Public Class clsHotel
Public m_Items = New List(Of clsHotel)
Public Property ID As Integer
Public Property FirstName As String
Public Property LastName As String
Public Property HotelName As String
Public Sub Add(iID As Integer,
sFirstName As String,
sLastName As String,
sHotelName As String)
Dim OneNew As New clsHotel
With OneNew
.ID = iID
.FirstName = sFirstName
.LastName = sLastName
.HotelName = sHotelName
End With
m_Items.Add(OneNew)
End Sub
End Class
So, we have a array exposed in above.
So, our code then becomes this:
(we do NOT create a instance to add each time - we let the class do that)
Protected Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim MyHotels As New clsHotel
For i = 1 To 10
MyHotels.Add(i, "First " & i, "Last " & i, "Hotel #" & i)
Next
GridView1.DataSource = MyHotels.m_Items
GridView1.DataBind()
End Sub
NOTE SUPER close in above!!! - we do NOT assign the WHOLE class to the Gridview (or repeater), but assign the "list" that is part of the class.
Hence,
GridView1.DataSource = MyHotels --- WRONG - does not work
GridView.DataSouruce = MyHotels.m_Items --- GOOD! - works!
so, the "thing" you assign to the repeater etc. must only expose base class items such as string or integer to the data type of control.
For the nested Rows? You have to in the top most repeater, then use EACH row bind, and feed to the nested repeater.
So, say we have a class with Hotel Name, City, and then inside booked people.
So, lets tweak our class. We will have the Hotel Name (one of them), and then many.
(going for coffee break - back in about 1 hours).
Edit - Part 2 - nesting
Ok, so now lets nest a 2nd "set" of data inside the repeater.
I going to use a gridview, but you COULD use a nested repeater.
The only big deal here?
The 2nd nested GridView (or repeater) is to be readered EACH time for the parent Repeater row. You create a WHOLE NEW instance of the child data object, and you can't referance both in one shot.
So, our nested repeater (and I nested a grid, since it easy)
So, our markup will look like this:
We have hotels, and then people booked in the hotels
<div style="width:40%">
<asp:Repeater ID="Repeater1" runat="server">
<ItemTemplate>
<h4>Hotel info for '<%# Eval("HotelName") %>' -- '<%# Eval("City") %></h4>
<div style="height:1px;background-color:darkgrey"></div>
<br />
<asp:GridView ID="GridView1" runat="server" CssClass="table">
</asp:GridView>
</ItemTemplate>
</asp:Repeater>
</div>
So, now we need a class - top part the hotel information, and then inside the people booked.
We have this now:
Public Class clsHotel
' top part Hotel Name, city, and Peoople Booked
Public Property HotelName As String
Public Property City As String
Public Property PeopleBooked As New List(Of People)
Public Class People ' one person to book in each hotel
Public Property FirstName As String
Public Property LastName As String
End Class
End Class
In this case, I assume "we" have to create the list of hotels (it does keep the example simple).
So, our button code becomes this:
Protected Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
' lets add 3 hotels, each with 2 people
Dim MyHotels As New List(Of clsHotel)
For iHotels = 1 To 3
Dim OneHotel = New clsHotel
OneHotel.HotelName = "Hotel #" & iHotels
OneHotel.City = "Hotel City #" & iHotels
' for each One hotel, add 2 people
For iPeople = 1 To 2
Dim OnePerson As New clsHotel.People
OnePerson.FirstName = "First #" & iPeople & " (" & OneHotel.HotelName & ")"
OnePerson.LastName = "Last #" & iPeople & " (" & OneHotel.HotelName & ")"
OneHotel.PeopleBooked.Add(OnePerson)
Next
MyHotels.Add(OneHotel)
Next
Repeater1.DataSource = MyHotels
Repeater1.DataBind()
End Sub
That takes care of the PARENT binding. But for EACH row of repeter, we have that child grid (or repeater). So, we have to fill out, code out that child information FOR EACH data binding.
So, we use the ItemDataBound event of the repeater.
That code looks like this:
Protected Sub Repeater1_ItemDataBound(sender As Object, e As RepeaterItemEventArgs) Handles Repeater1.ItemDataBound
If e.Item.ItemType = ListItemType.Item Or
e.Item.ItemType = ListItemType.AlternatingItem Then
' fill out child information
Dim GV As GridView = e.Item.FindControl("GridView1")
Dim OneHotel As clsHotel = e.Item.DataItem
GV.DataSource = OneHotel.PeopleBooked
GV.DataBind()
End If
End Sub
So we have to grab the ONE instance inside of the ONE repeater with find control (grid in my exmaple - repeater in your case).
Once we have the grid, then we need the ONE row information, and then we bind the GridView with the BookedPeople in that hotel.
The resulting output for above is thus this: