Home > Mobile >  Nested repeater source cannot be bound
Nested repeater source cannot be bound

Time:08-25

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:

enter image description here

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:

enter image description here

  • Related