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:
"switch" and "if" both give "invalid expression term" inside the <%# ... %> brackets.
"Container.DataItem" gives "the name "Container" does not exist in the current context" inside <% ... %> brackets.
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. :)
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
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);
}
...
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.
Related
<asp:Repeater ID="ReptServices" runat="server" OnItemDataBound="ReptServices_OnItemDataBound">
<b>
<td> <%# Eval("ApplicationSteps") %></td>
<td><%# Eval("Service.ServiceName") %></td>
<td>
<b><%# Eval("BeneficiaryUserUnitJob.User.NameAr") %> </b>
</td>
<td><%# Eval("DateCreated") %></td>
</b>
</asp:Repeater>
this repeater filled in c# as
ReptServices.DataSource = new ApplicationLogic(ApplicationType.Web).GetAllOutboxApplication(_currentUserUnitJob);
"ApplicationSteps"property contains many elements,which contains another property called submitactiontypeid as this image shows:
I want to get the value of submitactiontypeid which exist in the last elment of ApplicationSteps
I asked what I shall write in <%# Eval("ApplicationSteps") %>
I came with a different solution for you.
If you are not available to modify your code behind, still we don't need the repeater to solve.
<%
var repeaterDataSource = (dynamic)ReptServices.DataSource;
foreach (var item in repeaterDataSource)
{%>
<%=item.ApplicationSteps %>
<%}%>
This is your basic Repeater. Because of I do not know what your data type that you bind to the repater, I use dynamic type.
You can modify for your purpose.
<%=item.ApplicationSteps.Last().SubmitActionTypeId %>
First, debug without adding this line. If it works fine, add the code above and try again.
If you just need to render that value in the page then the repeater is useless. Try this code:
ASP
<asp:Label runat="server" ID="SubmitActionType"></asp:Label>
Then Server side (C#)
using System;
using System.Collections.Generic;
using System.Linq;
public partial class WebForm1 : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
var applicationSteps = GetApplicationSteps(); //Call the function that gives you the data you need
var lastElement = applicationSteps.Last(); //Get the last element
SubmitActionType.Text = lastElement.SubmitActionTypeId;
}
}
The repeater is useful when you need to render some html for each value inside an array of objects. In this case you are clearly not doing that.
Edit: Found the solution, and posted the answer below.
In my ASP.NET c# project, I have a ListView (ParentList) bound to a DataSource. Within the ParentList, inside the ItemTemplate, I have another Repeater (ChildList) bound to an attribute of each ListViewDataItem.
<asp:ListView ID="ParentList" runat="server" DataSourceID="objectDataSourceID" DataKeyNames="ID">
<ItemTemplate>
<tr>
<td>
<asp:Label ID="Label1" runat="server" Text='<%# Eval("Attribute1") %>' />
</td>
<td valign="top">
<asp:Repeater ID="ChildList" runat="server" DataSource='<%# Eval("Attribute2ReturnsAnotherList") %>'>
<HeaderTemplate>
<ul>
</HeaderTemplate>
<ItemTemplate>
<li>
<%# DataBinder.Eval(Container.DataItem, "childAttribute") %>
</li>
</ItemTemplate>
<FooterTemplate>
</ul>
</FooterTemplate>
</asp:Repeater>
</td>
</tr>
</ItemTemplate>
<LayoutTemplate>
...
</LayoutTemplate>
</asp:ListView>
The code above works just fine, everything renders great. Now I want to add a link that will show/hide the ChildList. Something like the below:
<td valign="top">
<a href="javascript:ToggleListVisibility()" >Show/Hide</a>
<asp:Repeater ID="ChildList" runat="server" DataSource='<%# Eval("Attribute2ReturnsAnotherList") %>'>
</asp:Repeater>
</td>
How can I achieve this? I can't just use getElementById as I normally would, as the ul lists are within a Repeater nested inside the ListView. I tried obtaining the parentNode, then accessing the children and toggling the visibility of the ul element within:
function ToggleListVisibility(source) {
var childrenlist = source.parentNode.children;
for (var i = 0; i < childrenlist.length; i++) {
if (childrenlist[i].tagName == 'ul') {
if (childrenlist.style.display == "none") {
childrenlist.style.display = "block";
} else {
childrenlist.style.display = "none";
}
}
}
}
<a href="javascript:ToggleListVisibility(this)" >Show/Hide</a>
but that didn't work. IE's 'error on page' gave me this error:
The parentNode is null or not an object.
I also tried setting the a runat="server" attribute to my ul element, then using <%# ulID.ClientID %> to pass the ul id to the Js function, but visual studio complained:
Server elements cannot span templates.
Finally, I tried just passing the ul object into the Js function, like this:
function ToggleListVisibility(src) {
if (src.style.display == "none") {
src.style.display = "block";
} else {
src.style.display = "none";
}
}
<a href="javascript:ToggleListVisibility(ulID)" >Show/Hide</a>
...
<ul id="ulID">
which works, but it toggles the visibility for the ChildList in all rows within my ParentList. I want it to only toggle the visibility for the ChildList in its own row.
I'm at a loss of what to do. JavaScript is not my forte, and I would appreciate if someone can provide some pointers. Thanks in advance.
Ok hopefully this will get you going - it worked for me in hiding a list. My source HTML looks like this:
<table>
<tbody>
...
<tr>
<td>
Hide
<ul>
<li>One</li>
<li>Two</li>
<li>Three</li>
</ul>
</td>
</tr>
...
</tbody>
</table>
<script type="text/javascript">
function toggleListVisibility(src) {
var childrenList = src.nextSibling.nextSibling;
childrenList.style.display = "none";
}
</script>
Note I had to use two "nextSibling"'s due to a text node that is created right after the "hide" anchor. Depending on how you structure your HTML, that bit will be different.
I found the painfully simple answer that makes me feel like a doofus. All I needed to do is wrap my Repeater in a <div> element, then show/hide the entire thing.
<a href="javascript:ToggleListVisibility('<%# Container.FindControl("divWrapper").ClientID %>')" >Show/Hide</a>
<div id="divWrapper" runat="server">
<asp:Repeater ID="ChildList" runat="server">
</asp:Repeater>
</div>
function ToggleListVisibility(id) {
var wrapper = document.getElementById(id);
if (wrapper.style.display == "none") {
wrapper.style.display = "block";
} else {
wrapper.style.display = "none";
}
}
Hooray for overthinking!
I want to know how to Add a UserControl Conditionally to a Repeater Control. I have tried to add it to the placeholder which is in Repeater Control but unable to load the usercontrol. This following code doesn't work.
<asp:Repeater ID="ResultsRepeater" runat="server">
<HeaderTemplate></HeaderTemplate>
<ItemTemplate>
<div>
<asp:PlaceHolder ID="PlaceHolder1" runat="server">
</asp:PlaceHolder>
</div>
</ItemTemplate>
</asp:Repeater>
public void GetStatus(int i)
{
UserControl uc = new UserControl();
if(i==1)
{
uc = LoadControl("DraftList.ascx") as UserControl;
}
else if(i==2)
{
uc = LoadControl("FinalList.ascx") as UserControl;
}
PlaceHolder p1 = (PlaceHolder)ResultsRepeater.Items[0].FindControl("PlaceHolder1");
p1.Controls.Add(uc);
}
Is there some reason that you don't want to just handle all of this in the aspx? That would be the simplest and cleanest option:
<asp:Repeater runat="server" ID="ResultsRepeater">
<ItemTemplate>
<uc1:DraftList ID="DraftList1" runat="server" Visible='<%# ((int)Eval("Status") == 1)%>' />
<uc2:FinalList ID="FinalList1" runat="server" Visible='<%# ((int)Eval("Status") == 2)%>' />
</ItemTemplate>
</asp:Repeater>
If a control is not visible, (i.e., Visible=false) then no markup is rendered, so coding in this fashion would not create any more work for the server or the client browser, while having the benefit of being much easier to read and providing user control properties at design-time.
You would just need to make sure to register your controls at the top of the page:
<%# Register src="DraftList.ascx" tagname="DraftList" tagprefix="uc1" %>
<%# Register src="FinalList.ascx" tagname="FinalList" tagprefix="uc2" %>
Why don't you try adding it within the repeaters ItemDataBound event? I.e.,
<asp:Repeater ID="ResultsRepeater" OnItemDataBound="ResultsRepeater_ItemDataBound" runat="server">
<HeaderTemplate></HeaderTemplate>
<ItemTemplate>
<div>
<asp:PlaceHolder ID="PlaceHolder1" runat="server">
</asp:PlaceHolder>
</div>
</ItemTemplate>
</asp:Repeater>
and in the code behind
protected void ResultsRepeater_ItemDataBound(object sender, RepeaterItemEventArgs e)
{
if (e.Item.ItemType == ListItemType.Item || e.Item.ItemType == ListItemType.AlternatingItem)
{
Panel PlaceHolder1 = (Panel)e.Item.FindControl("PlaceHolder1");
// declare/obtain the value of i given the DataItem
// e.g.,
int i = ((int)e.Item.DataItem); // or however you're getting i
if (i == 1)
{
var uc = LoadControl("~/DraftList.ascx");
PlaceHolder1.Controls.Add(uc);
}
else if (i == 2)
{
var uc = LoadControl("~/FinalList.ascx");
PlaceHolder1.Controls.Add(uc);
}
}
}
Judging by your last comment (to the question) you might need to also make sure you've attached and bound your datasource to the repeater as well. I.e.,
ResultsRepeater.DataSource = dataSource; //whatever your datasource is e.g., datatable, IEnumerable list etc
ResultsRepeater.DataBind();
For some reason, I cannot get text into any textbox or label!
I'm using Master pages and the code is going in the code behind view. I have created the textbox:
<asp:Textbox ID="whatever" runat="Server">
When I want to add some text I simply add the code in the code behind view like:
whatever.Text = "myText";
I get an error that says:
"System.NullReferenceException:Object reference not set to an instance of an object"
hightlighting this line in red: whatever.Text = "myText";
I guess its because it saying it not there but how can it let me reference the textbox?
Apologies if the answer is on the site, I have searched but found nothing. :)
This is my code in Basket.asp - I've changed the textbox to a label, it's called bskItems
<asp:Content ID="Content3" ContentPlaceHolderID="ContentPlaceHolder3" runat="server">
<asp:Label ID="bskItems" runat="server"></asp:Label>
<div id="cart">
<asp:Button ID="btnCheckout" CssClass="BasketBtnAdd" runat="server" CommandName="checkout" Text="Checkout" />
</div>
</asp:Content>
This is my masterpage, where I'm using a loginView. ContentPlaceHolder3 is where the textbox should be. I only want it to display a count of items.
<asp:LoginView ID="loginView" runat="server">
<LoggedInTemplate>
<asp:LoginName ID="loginName" runat="server" FormatString="Hi, {0}!"/>
(<asp:LoginStatus ID="loginStatus" runat="server" />)
<%
if (HttpContext.Current.User.IsInRole("Admin"))
{
%>
<asp:SiteMapDataSource ID="admin" SiteMapProvider="admin" runat="server" ShowStartingNode="false" />
<asp:Menu ID="Menu" runat="server" DataSourceID="admin">
<StaticItemTemplate>
<%# Eval("Text") %>
</StaticItemTemplate>
</asp:Menu>
<%
}
if (HttpContext.Current.User.IsInRole("Users"))
{
%>
<asp:SiteMapDataSource ID="user" runat="server" SiteMapProvider="user" ShowStartingNode="false" />
<asp:Menu ID="Menu1" runat="server" DataSourceID="user">
<StaticItemTemplate>
<%# Eval("Text") %>
</StaticItemTemplate>
</asp:Menu>
<%
}
%>
<asp:ContentPlaceHolder ID="ContentPlaceHolder2" runat="server"></asp:ContentPlaceHolder>
<asp:ContentPlaceHolder ID="ContentPlaceHolder3" runat="server"></asp:ContentPlaceHolder>
</LoggedInTemplate>
<AnonymousTemplate>
<asp:LoginStatus ID="loginStatus" runat="server" />
<asp:SiteMapDataSource ID="anon" runat="server" SiteMapProvider="anon" ShowStartingNode="false" />
<asp:Menu ID="Menu2" runat="server" DataSourceID="anon">
<StaticItemTemplate>
<%# Eval("Text") %>
</StaticItemTemplate>
</asp:Menu>
</AnonymousTemplate>
</asp:LoginView>
In addition to the other answers, if you're setting the value in Page.OnLoad, remember that the Master page controls haven't been created yet.
Here's a complete layout of the order in which things happen: Complete Lifecycle of an ASP Page
What I usualy do is to make the control visible as a property of my MasterPage.
On the master page (AMasterPage.master):
public TextBox MyTextBox { get { return this.theTextBoxControl; } }
So then, on a child using this masterPage (APage.aspx) :
((AMasterPage)this.Master).MyTextBox.Text = "myText";
When accessing Master Page members from Code-Behind in a Content Place Holder file, I believe you need to do:
this.Master.whatever.Text = "new Text";
Check this link on ASP.NET Master Pages, from MSDN.
You need to do get a reference to the textbox on the master page, then set the text
TextBox tb = Master.Page.FindControl("whatever") as TextBox;
if(tb != null)
{
tb.Text = "myText";
}
Set the ClientIDMode on the textbox to "Static". When the page is rendered it assigns the TextBox's ID to something random. By changing the ClientIDMode to "Static", you should be able to reference the ID because the ID will stay the same and not change.
Or try adding an OnDataBinding event handler and casting the "sender" as a (TextBox). For example:
protected void TextBox_OnDataBinding(object sender, EventArgs e)
{
var txt = (TextBox)sender;
txt.Text = "Something";
}
This should talk to the control directly.
i have in my aspx page
<%
for(int i=0;i<10;i++)
{
LinkButton1.CommandArgument=i.ToString();
%>
<asp:LinkButton ID="LinkButton1" runat="server" OnCommand="LinkButton1_Click"></asp:LinkButton>
<%
}
%>
how can i pass a value in my aspx.cs page like:
protected void LinkButtonStergeArticol_Click(object sender,CommandEventArgs e)
{
String id = e.CommandArgument.ToString();
}
?
i know it's not correct because i have more LinkButtons with the same ID, but is there any solution for my problem in this scenario? Or can you suggest me another approach?
i need to have a linkbutton for every item i have in an array. then i need to pass the item's index so that i can delete it in my aspx.cs page.
Can't you use a Repeater or ListView to generate that list of controls? Those are designed to generate a list of controls.
Have you tried something like following?
<%
for(int i=0;i<10;i++)
{
//LinkButton1.CommandArgument=i.ToString();
%>
<asp:LinkButton ID="LinkButton1"
runat="server"
OnCommand="LinkButton1_Click"
CommandName="SomeCommand"
CommandArgument="<%= i%>"></asp:LinkButton>
<%
}
%>