George
George

Reputation: 2210

How do I vary my ItemTemplate inside an asp:Repeater?

I have a user control which is used to display search results. The HTML for each result displayed will vary based on the type of result being displayed: "contacts" are displayed in one way, "news articles" are displayed in another, etc. There are around 10 different types of results that are all marked up differently when they get to HTML — so I need around 10 or so different templates for individual results that I can choose between based on the current item being displayed.

I'm using an asp:Repeater to display the results, but I don't know how to select the appropriate template within the asp:Repeater <ItemTemplate>. Ideally I'd like the ASP to select the appropriate template to use based upon the object type being passed in via the searchResultsRepeater.DataSource — but unfortunately I can't use switch on type (see this blog entry for C# switch on type). I can however just pass through an enum value for the type of result being displayed.

In the backend C# code I have an abstract inline SearchResult class, and children of that class like ContactSearchResult, NewsArticleSearchResult, etc. The searchResultsRepeater.DataSource would then be bound to a List<SearchResult>. Each SearchResult contains a ResultListingType type field which gives the type of the listing to be displayed.

Attempt 1: using control flow inside the ASP itself

My first attempt was something like this:

        <asp:Repeater ID="searchResultsRepeater" runat="server">
        <ItemTemplate>
        <div class="item">

        <% switch (DataBinder.Eval(Container.DataItem, "type")) { %>

        <% case ResultListingType.CONTACT: %>

            <p><%# DataBinder.Eval(Container.DataItem, "firstName") %></p>
            <p><%# DataBinder.Eval(Container.DataItem, "lastName") %></p>

            <% break; %>

        <% case ResultListingType.NEWS: %>

            <p><%# DataBinder.Eval(Container.DataItem, "newsHeadline") %></p>
            <p><%# DataBinder.Eval(Container.DataItem, "newsDate") %></p>

            <% break; %>

        <% Case AnotherTypeOfListing1: %>
        <% Case AnotherTypeOfListing2: %>
        <% Case AnotherTypeOfListing3: %>
        <% Case AnotherTypeOfListing4: %>
        <% Case AnotherTypeOfListing5: %>
        <% etc... %>

        <% } %>

        </div>

        </ItemTemplate>
        </asp:Repeater>

Unfortunately, this doesn't work:

Attempt 2: setting asp:PlaceHolder's to Visible = False

I found something that looked useful at how to change the ItemTemplate used in an asp:repeater?. I then tried something like:

        <asp:Repeater ID="searchResultsRepeater" runat="server">
        <ItemTemplate>
        <div class="item">

        <asp:PlaceHolder ID="newsResultListing" runat="server">
            <p><%# DataBinder.Eval(Container.DataItem, "newsHeadline") %></p>
            <p><%# DataBinder.Eval(Container.DataItem, "newsDate") %></p>
        </asp:PlaceHolder>

        <asp:PlaceHolder ID="contactResultListing" runat="server">
            <p><%# DataBinder.Eval(Container.DataItem, "firstName") %></p>
            <p><%# DataBinder.Eval(Container.DataItem, "lastName") %></p>
        </asp:PlaceHolder>

        </div>

        </ItemTemplate>
        </asp:Repeater>

In my ItemDataBound event I did:

        Control newsResultListing = e.Item.FindControl("newsResultListing");
        newsResultListing.Visible = false;
        Control contactResultListing = e.Item.FindControl("contactResultListing");
        contactResultListing.Visible = false;
        switch (item.type)
        {
            case ResultListingType.CONTACT:
                contactResultListing.Visible = true;
                break;
            case ResultListingType.NEWS:
                newsResultListing.Visible = true;
                break;
            default:
                throw new Exception("Unknown result listing type");
        }

Unfortunately this doesn't work because ASP seems to still be running the contents of the PlaceHolder even after I set Visible = false. I get the error "DataBinding: 'usercontrols_ResultsListing+ContactResultsListing' does not contain a property with the name 'newsHeadline'" — i.e. the newsResultListing PlaceHolder is still looking for the "newsHeadline" field, even though that field doesn't exist for the result listing type being displayed.

In fact I've tried a quick test throw new Exception("e"); in my ItemDataBound, and it looks like the "DataBinding" error is thrown even before control flow gets to the ItemDataBound method, so there's really nothing I can do in there to avoid this error.

I suppose I could add every single field to the parent class and leave most of them null in my children, but that seems really ugly.


Is there a way to make this work, or an easier way to vary my ItemTemplate based upon the type of Container.DataItem I'm currently iterating over? I'm very new to ASP so there's likely something simple that I've missed. :)

Upvotes: 13

Views: 20605

Answers (3)

Doug Domeny
Doug Domeny

Reputation: 4470

To add to George's solution, the <ItemTemplate> can be a mix of markup and dynamic controls. The following example renders a table of name/value pairs.

<table cellspacing="0" cellpadding="5" border="0" width="100%">
    <tbody>

        <asp:Repeater ID="TheRepeater" OnItemDataBound="TheRepeater_ItemDataBound" runat="server">
            <ItemTemplate>

                <tr>
                    <td class="LabelText"><%# ((NameValuePair)Container.DataItem).Name%>:</td>
                    <td class="ValueText">
                        <asp:PlaceHolder ID="ValuePlaceHolder" runat="server" />
                    </td>
                </tr>

            </ItemTemplate>
        </asp:Repeater>

    </tbody>
</table>

Code-behind

    protected void TheRepeater_ItemDataBound(object sender, RepeaterItemEventArgs e)
    {
        if (ListItemType.Item == e.Item.ItemType || ListItemType.AlternatingItem == e.Item.ItemType)
        {
            NameValuePair nvp = (NameValuePair)e.Item.DataItem;
            PlaceHolder container = (PlaceHolder)e.Item.FindControl("ValuePlaceHolder");

            if (typeof(nvp.Value) is String)
            {
                Literal textControl = new Literal() { Mode = LiteralMode.Encode, Text = (string)nvp.Value, EnableViewState = false };
                container.Controls.Add(textControl);
            }
            ...

Upvotes: 3

George
George

Reputation: 2210

OK, I think I've found a solution — I've created multiple additional user controls, one for each type of search result. Each one of these controls defines the HTML template for its corresponding type of search result.

My asp:Repeater contains absolutely nothing inside its ItemTemplate:

    <asp:Repeater ID="searchResultsRepeater" runat="server">
        <ItemTemplate>
            <%-- empty template: we insert usercontrols in the ItemDataBound --%>
        </ItemTemplate>
    </asp:Repeater>

I bind a List<SearchResultData> to my asp:Repeater as before, and as before this list contains more specific subtypes of SearchResultData based on the type of result to be shown.

In my ItemDataBound handler I instantiate one of those user controls based on the type of data in e.Item.DataItem and then insert that user control into the repeater:

        var aspxItem = e.Item;
        var dataItem = (SearchResultData) e.Item.DataItem;

        if (dataItem is ContactSearchResult.ContactSearchResultData)
        {
            var contactSearchResultUC = LoadControl("~/UserControls/ResultsListingSearchResult/ContactSearchResult.ascx") as ASP.ContactSearchResult;
            contactSearchResultUC.data = (ContactSearchResult.ContactSearchResultData)dataItem;
            aspxItem.Controls.Add(contactSearchResultUC);
        }
        else if (dataItem is NewsArticleSearchResult.NewsArticleSearchResultData)
        {
            var newsArticleSearchResultUC = LoadControl("~/UserControls/ResultsListingSearchResult/NewsArticleSearchResult.ascx") as ASP.NewsArticleSearchResult;
            newsArticleSearchResultUC.data = (NewsArticleSearchResult.NewsArticleSearchResultData)dataItem;
            aspxItem.Controls.Add(newsArticleSearchResultUC);
        }

        ...etc

Upvotes: 14

Roger Willcocks
Roger Willcocks

Reputation: 1667

You will need to override the ItemDataBound event handler and control it there.

Alternatively, put the logic into a user control and put the user control into the template instead.

The user control will still need to implement the appropraite switching, but makes it easier to reuse the logic.

Upvotes: 1

Related Questions