I have a repeater that creates n-number of panels. I am trying to dynamically add different controls to each of the panels. I may very well be going about this the wrong way.
My code is more or less:
.aspx:
<asp:Repeater ID="Repeater1" runat="server">
<ItemTemplate>
<% Response.Write("<asp:panel runat=\"server\" id=\"uxPanel_"); %>
<%# DataBinder.Eval(Container.DataItem, "TableId")%><% Response.Write("\"></asp:panel>"); %>
</ItemTemplate>
</asp:Repeater>
.cs:
public partial class class1: System.Web.UI.Page
{
DataSet ds= null;
protected void Page_Load(object sender, EventArgs e)
{
GetRecords(1,1);
}
protected void GetRecords()
{
ds= dal.LoadRecords();
this.Repeater1.DataSource = ds.Tables[0];
this.Repeater1.DataBind();
Literal lit = new Literal();
lit.Text = "Some text";
this.FindControl("uxPanel_1").Controls.Add(lit);
}
}
Just to be clear in this example "dal.LoadRecords" is simply call to a method that retrieves some records from a DB.
I think my problem is how I am adding my panels in the first place, but this seemed like an easy way to have them uniquely named.
Any Pointers?
Thanks
As the previous answerer has said, this is fraught with peril!
If you must: I should ditch the repeater approach altogether.
Create a container panel or placeholder and in the code behind dynamically add your panels to that using ContainerPanel.Controls.Add(newPanel);
Your child "panel" could be a UserControl if needs be too.
Be aware that you'll have to regenerate your dynamic controls on postback.
Pointers? Yes : Don't dynamically add controls!
Dynamically adding controls adds a lot of overhead and you run into issues with viewstates. Usually , it's not worth the time and headaches.
Panels, as well as Literals, PlaceHolders, and every other ASP.NET control have a Visible property that you can toggle on and off, like so:
myPanel.Visible=true;
myOtherPanel.Visible = false;
myTogglePanel.Visible = ! myTogglePanel.Visible; //toggle it's visibility
This approach is much, much easier.
Related
I am trying to create dynamic label's in my web form app when but seem to be getting this error
Multiple controls with the same ID 'Label1' were found. FindControl requires that controls have unique IDs.'
This is what I have:
<asp:UpdatePanel ID="Panel" runat="server" ChildrenAsTriggers="false" UpdateMode="Conditional">
<ContentTemplate>
<asp:Panel ID="OverViewUpdate" runat="server">
<asp:Label ID="Label" runat="server"></asp:Label>
</asp:Panel>
</ContentTemplate>
</asp:UpdatePanel>
And Code Behind:
for (int i = 1; i <= 20; i++)
{
Label label = new Label();
label.ID = "Label" + i.ToString();
label.Text = "Label" + i;
OverViewUpdate.Controls.Add(label);
}
((Label)OverViewUpdate.FindControl("Label")).Text = Convert.ToString("RoundTripTime: " + reply.RoundtripTime + "ms") + "<br/>";
I am not sure how to resolve this issue?
Thanks
Well, as per comments, it seems perhaps that the page already has a control with that ID. The next problem, if you have to be sure you don't run that code each time - in other words, only add the controls on first page load - not each additional button click and post back.
eg like this:
if Not IsPostBack then
' code here to add those controls
End if
Remember, on each button click or anything that post's back the page, then all your code runs again - including the page load event. So for a "really" first page load, we check IsPostBack as per above.
However, for the most part, when we need repeating data or information, there no need to inject or to try and add controls by code. You can try doing this, but in the VAST majority of cases, you can use one of the built control's that allow you to do this is great ease, and in most cases without looping code either.
So, for some controls - that you want to repeat? Then use what is called a repeater.
For table like data? Then use a gridview, or say listview. And even better is those controls are data bound - and will fill out for you automatic, and do so without again without looping code.
So, say we want some text boxes to show a list of hotels we have. and MORE often then not, such repeating data comes from a database, which is even better yet.
So, we don't even have to know how many labels or text boxes we need ahead of time.
So, now our markup can be this:
And now, our code can be this:
protected void Page_Load(object sender, EventArgs e)
{
if (IsPostBack == false)
{
LoadGrid();
}
}
public void LoadGrid()
{
using (SqlCommand cmdSQL = new SqlCommand("SELECT * from tblHotels ORDER BY HotelName",
new SqlConnection(Properties.Settings.Default.TEST3)))
{
cmdSQL.Connection.Open();
MyGrid.DataSource = cmdSQL.ExecuteReader();
MyGrid.DataBind();
}
}
And output:
so, you can see how we did not need to inject, or loop to inject or add controls to the web page, but used a repeating control.
But, in your case? I would make sure your code that adds the controls ONLY runs inside of the IsPostBack = false stub.
It seems perhaps that the page already has a control with Label1.
in that case you can use the following statement inside loop.
label.ID = "Label" + i+1.ToString();
I want to display a lot of data in a Table control, but put the code in a library method that returns a new Table object. When I assign that object directly to the control on the ASP page, the data does not show.
The library method looks like this:
public Table CreateTable(...)
{
Table tbl = new Table();
...
// adding lots of cells and setting lots of properties
...
return tbl;
}
The ASP page has a Table control:
<asp:Content ID= ... >
<asp:Table ID="Table_Data" runat="server">
</asp:Table>
</asp:Content>
In Code-Behind the Table control is assigned the new Table object:
protected void Button_Click(object sender, EventArgs e)
{
Table_Data = Lib.CreateTable(...);
}
but when tested the Table control shows empty.
This principle has worked by this:
Table NewTable = Lib.CreateTable(...);
PlaceHolder1.Controls.Add(NewTable);
but it seems that having a PlaceHolder in my asp page should not be needed.
Or is it?
Any help is appreciated!
Update:
The solution is, as the Accepted Answer, to keep the PlaceHolder, but not <asp:Content> but a new PlaceHolder control at the exact place where I want the table:
<asp:PlaceHolder ID="PlaceHolder_Table" runat="server">
</asp:PlaceHolder>
and in Code Behind:
PlaceHolder_Table.Controls.Add(Lib.CreateTable(...));
Simple, and works great.
<asp:Content ID="contId" >
<asp:Table ID="Table_Data" runat="server">
</asp:Table>
</asp:Content>
protected void Button_Click(object sender, EventArgs e)
{
contId.Controls.Clear();
contId.Controls.Add(Lib.CreateTable(...));
}
The asp.net table control isn't supposed to work like this. You're most likely looking for the GridView control, see here. This is used for getting data, binding it to the Gridview and displaying it
The asp.net table control info is here. It's more of a format thing rather than using for loads of data, that's why you can't databind to it.
Is it possible to add a div tag that can wrap a control from the behind? I want to send a list of controls to my function and have it dynamically add a div around each one, so that each control is within its own div.
Example:
html:
...
<p> some text</p>
Name: <asp:textbox id="txtName" runat="server" />
...
function should work to make the markup look like this-
...
<p> some text</p>
Name: <div id="divName" runat="server"><asp:textbox id="txtName" runat="server" /></div>
...
Yes, and there is more than one way to do this.
Recommended solution: create a custom control
You can easily create a custom control that extends System.Web.UI.WebControls.TextBox and overrides the Render method to write the markup for you. This gives you ultimate flexibility in how a control is rendered. This example uses base.Render() to render the TextBox itself, but wraps it in a DIV by writing directly to the HtmlTextWriter. The major benefits to this are that your custom control will be available in the code behind just like any other server control and that you can declare it in your .aspx/.ascx files.
To wit:
public class WrappedTextBox : System.Web.UI.WebControls.TextBox
{
protected override void Render(HtmlTextWriter writer)
{
writer.Write("<div id=\"div{0}\" runat=\"server\">", this.ID.Replace("txt", String.Empty));
base.Render(writer);
writer.WriteEndTag("div");
}
}
To use the control in an ASPX page, first Register the control's namespace:
<%# Register Assembly="WebApplication1" TagPrefix="cc" Namespace="WebApplication1" %>
Then add the control to your form like so:
<cc:WrappedTextBox ID="txtName" runat="server" Text="Wrapped!" />
cc is just an arbitrary prefix that I chose, but you can make up your own! (cc for "custom control")
Note that the runat=\"server\" is probably unnecessary in the div output (though you could be writing code that generates ASPX content, I suppose). The runat="server" just tells ASP.NET that you want it to be a server control, but in this case your WrappedTextBox is the server control. Also note that this isn't necessarily a best-practices example.. I wouldn't replace the txt normally, but rather would use a custom attribute for the div ID. (But this example will give the requested output.)
Alternate solution: Depth first search and modification of the Control Tree
protected void Page_Load(object sender, EventArgs e)
{
DeepControlSearch(Page);
}
private void DeepControlSearch(Control control)
{
if (control.HasControls())
{
for (int c = control.Controls.Count - 1; c > -1; c--)
{
DeepControlSearch(control.Controls[c]);
}
}
if (control is TextBox)
{
WrapTextBox((TextBox)control);
}
}
private void WrapTextBox(TextBox textBox)
{
System.Web.UI.HtmlControls.HtmlGenericControl div = new System.Web.UI.HtmlControls.HtmlGenericControl("div");
div.ID = String.Format("div{0}", textBox.ID.Replace("txt", String.Empty));
div.Attributes["runat"] = "server";
Control parent = textBox.Parent;
parent.Controls.Remove(textBox);
div.Controls.Add(textBox);
parent.Controls.Add(div);
}
The benefit to this approach is that you don't have to create custom controls (which requires a bit of up-front work, but usually is a worthwhile investment because you can reuse them in any ASP.NET project). The HtmlGenericControl can be manipulated like any other server control, but it is dynamic and so needs to be in ViewState if you want to access it on PostBack (unless you want to do it the oldskool way and loop through the posted Form elements to get values from dynamic controls).
Not sure I would recommend the second approach; this is really what custom controls are made for.
Alternate solution #2: emit JavaScript (jQuery makes it really easy) to wrap the controls
You could use ClientScript.RegisterStartupScript() to emit some JavaScript or jQuery to do this, for example:
ClientScript.RegisterStartupScript(
this.GetType(),
"wrap textboxes",
"$(document).ready(function(){$('input:text').each(function(){$(this).wrap('<div id=\"div' + this.id.replace('txt', '') + '\"></div>');});});",
true);
I have a repeater in one of the sublayouts in Sitecore 6.6. rev 120918. Inside this repeater, I have a placeholder. On ItemDataBound, I assign a key with a GUID to it, and using Nick Wesselman's GetAllowedRendering pipeline, I am able to render the insert options for this.
The repeater code is very simple:
<asp:Repeater runat="server" ID="rptrTimelineItemsEdit" OnItemDataBound="rptrTimelineItemsEdit_OnItemDataBound">
<ItemTemplate>
<sc:Placeholder ID="scTimelineItemPlaceholder" Key="timelineitemcontent" runat="server" />
</ItemTemplate>
</asp:Repeater>
The Item Data bound is also pretty simple:
protected void rptrTimelineItemsEdit_OnItemDataBound(object sender, RepeaterItemEventArgs e)
{
if (e.Item.ItemType == ListItemType.Item || e.Item.ItemType == ListItemType.AlternatingItem)
{
Item item = (Item)e.Item.DataItem;
Placeholder scTimelineItemPlaceholder = (Placeholder)e.Item.FindControl("scTimelineItemPlaceholder");
scTimelineItemPlaceholder.Key = "timelineitemcontent" + item.ID.ToString().ToLower();
}
}
Everything works up to this point. After I choose the sublayout I want to insert in the placeholder, it throws me an in the JS error popup. After inspecting in the chrome inspector, I see the error is:
Could not find the rendering in the HTML loaded from server PlaceholderChromeType.js:601
Sitecore.PageModes.ChromeTypes.Placeholder.Sitecore.PageModes.ChromeTypes.ChromeType.extend._frameLoaded
So - it would seem like that in the page reload, the sublayout renderings are happening before the repeater bindings, so it can't find the placeholder key.
I did some google, and found that there is a way to change the layout engine sequence using the layoutpageevent setting:
However, this didn't solve my problem. I'm out of ideas. Help!
Hi there Ive found a solution to this issue, it had me stumped as well.
It only occurs to placeholders that sit under a repeater.
Overwrite PlaceholderChromeType.js in Website\sitecore\shell\Applications\Page Modes\ChromeTypes\
With the one ive dropboxed (also available on the credit link below)
https://www.dropbox.com/s/7m99b8jgdz3cgl2/PlaceholderChromeType.
Your keys then have to have dynamic in them for it to work.
Example code:
protected void R1_ItemDataBound(Object Sender, RepeaterItemEventArgs e)
{
var p = e.Item.FindControl("andysplaceholder") as Placeholder;
p.ID = "Andy" + (e.Item.ItemIndex + 1);
p.Key = "dynamic" + p.ID;
}
<asp:repeater id="MyRepeater" runat="server" OnItemDataBound="R1_ItemDataBound">
<ItemTemplate>
<sc:placeholder runat="server" id="andysplaceholder" key="dynamicandysplaceholder" ></sc:placeholder>
</ItemTemplate>
</asp:repeater>
On top of this i do my repeater binding in the oninit method
protected override void OnInit(EventArgs e)
{
base.OnInit(e);
and i set the web.config setting from preInit to load
<setting name="LayoutPageEvent" value="load" />
Although that might be overkill, I will revert today to page_load and preinit today and see if it still works.
Credit to Sitecore support and Mickey Rahman for this http://mickeyrahman.wordpress.com/2014/05/05/an-ode-to-sitecore-support/#respond
This should also work with the dynamic key apprach from Nick, you'll just have to make sure the keys generated by his code have dynamic in them
You have not implemented it in the way it's explained by Nick.
What you need to do is create a WebControl class for your Dynamic Placeholder.
That will ensure that things are processed in the same order as it would have with a 'real' Placeholder.
So the first thing I would do is make sure you do it in the same way as it's been described by Nick.
Then verify that it works with just 1 dynamic placeholder on the page.
Secondly, you should take a look at this blog post: Dynamic Placeholders and IExpandable
It explains how to deal with adding multiple dynamic placeholders in repeaters (and other controls).
He also thought that it had something to do with the change in the LayoutPageEvent, but it did not turn out to be that.
EDIT:
I've reproduced the issue and I highly doubt that you're going to solve it with this approach.
The placeholders are not loaded yet when the Page Editor is requesting their renderings after trying to add controls to them.
Even when you create the Placeholders in the OnInit (which is the earliest), it doesn't work.
If I get more time I'll try to look into it further.
Ruud referenced my blog post Dynamic Placeholders and IExpandable in his answer, and I think that he's definitely on the right track. I know that you haven't used dynamic placeholders in the way that I describe, however you are still creating placeholders dynamically.
I personally have never had any success with the LayoutPageEvent; which is why I decided to post about the use of IExpandable as it isn't something that's been highlighted that much.
You ought to create your placeholders in the Expand method and also expand them after you add them to the page. I would personally advise against using a Repeater to do so - add them to the controls collection or maybe to a normal ASP.Net PlaceHolder control.
public class YourSublayout : UserControl, IExpandable
{
public void Expand()
{
var myListOfItems = GetItems()
foreach (var item in myListOfItems)
{
var placeholder = new Placeholder { Key = "key" + item.ID.ToString() };
container.Controls.Add(placeholder); // add to a control container
placeholder.Expand();
}
}
}
Hopefully that should get you a bit further (if you haven't solved it already). Just keep in mind the other things I mention on my post, namely that the Expand method can be called before the control gets added to the page, and so getting your DataSource may break if you rely on getting it from the parent control.
I have a master page and all of my pages are inheriting it.
For formatting, I thought to place the content that differs from one page to another in a ContentPlaceHolder.
Now, how can I insert everything into that? Since I am planning to populate the ContentPlaceHolder with stuff from a database I suppose I will have to do it programmatically.
How can I add controls to ContentPlace Holder?
I checked other answers, but I cannot access it by its ID.
Should I use multiple ContentPlaceHolders from the beginning? Let's say I want to put movies. Should there be only one with all the images and descriptions and ratings, ore one ContentPlaceHolder for each thing?
I am opened to other solutions, as I have no experience with ASP.
Old question... but I just ran into this issue and this was the #1 post that kept coming up on Google, so figure I'd add my answer since the others didn't work in my case.
Here is how I did it when a regular <asp:Content wouldn't work (though in normal use, the answer #JayC is how you do it):
MasterPage has this ContentPlaceHolder:
<asp:ContentPlaceHolder ID="ScriptsPlace" runat="server"></asp:ContentPlaceHolder>
Had to dynamically add some JavaScript from a User Control. Trying to use the ContentPlaceHolder directly gives this error:
Parser Error Message: Content controls have to be top-level controls
in a content page or a nested master page that references a master
page.
So I wanted to add the script from the code-behind. Here is the Page Load for the .ascx file:
protected void Page_Load(object sender, EventArgs e)
{
ContentPlaceHolder c = Page.Master.FindControl("ScriptsPlace") as ContentPlaceHolder;
if (c != null)
{
LiteralControl l = new LiteralControl();
l.Text="<script type=\"text/javascript\">$(document).ready(function () {js stuff;});</script>";
c.Controls.Add(l);
}
}
UPDATE: So it turns out I had to use this in more places than I expected, and ended up using a way that was much more flexible / readable. In the user control itself, I just wrapped the javascript and anything else that needed to be moved with a regular div.
<div id="_jsDiv" runat="server">
$(document).ready(function() {
//js stuff
});
Other server controls or HTML junk
</div>
And then the code behind will find that div, and then move it into the ContentPlaceHolder.
protected void Page_Load(object sender, EventArgs e)
{
ContentPlaceHolder c = Page.Master.FindControl("ScriptsPlace") as ContentPlaceHolder;
HtmlGenericCOntrol jsDiv = this.FindControl("_jsDiv") as HtmlGenericControl;
if (c != null && jsDiv != null)
{
c.Controls.Add(jsDiv);
}
}
I actually put this code in a custom user control, and I just have my regular user controls inherit from the custom user control, so once I wrap the javascript/etc with a <div id="_jsDiv" runat="server">, the custom user control takes care of the rest and I don't have to do anything in the code behind of the user control.
What normally happens is
you set up your master pages with the proper html and ContentPlaceHolders
you create pages based off that master page. If you use Visual Studio, and tell it to create a new page based upon a existing Master page, it will add the Content areas for you.
you add things to the Content areas in the newly created page.
If you want to dynamically add controls to the master (or any) page, you could add controls to any existing control. If it shouldn't be wrapped in any way, just add a Placeholder (it is an asp.net control).
I did like this
<asp:Content ID="HeaderContent" runat="server" ContentPlaceHolderID="HeadContent">
<asp:Literal ID="jsstuff" runat="server"></asp:Literal>
</asp:Content>
And this went into code behind:
string stuff = #"<script type=""text/javascript"">
var searchBox = 0;
var currentCountry = '';
</script>";
jsstuff.Text = stuff;
If the namespace for content Page and Master page is not same then the content page control not accessible in Codebehind in content page.
Also, check your designer files. if the control not listed in designer file then delete the file and recreate (project->convert to web application)