How can I stack an unknown number of ASP.NET Repeaters? - asp.net

I am building a pop-out menu, and the client wants it to be able to pop out continously based on a heirarchy.
For example, the first pane is a list of options. When they are hovered over, another pane should pop up next to it with the next level of options, and so on until the last level of options is reached.
I can handle all the javascript and stuff, but I can't think of a way to continously embed repeaters inside repeaters. I know I could do it once by putting a repeater inside another, but then I would only have two layers.
I need to be able to continously embed repeaters for each layer of options, or achieve this with a similar technique using a different control.
Any help is great, thanks!

You won't be able to build this in mark up. You'll have to add the controls dynamically in your code behind by building the Repeater for each level and adding it to the previous Repeater's template. It will require a full postback for each option selected because the nested Repeater could potentially be of different depth depending on which option is chosen.
You might be better off doing this all client-side, though, using AJAX and javascript. When an option is chosen, fire off an AJAX request to see if that option has sub-options. If it does (return them), then dynamically build the new options control using javascript and add it to the page. When a different option is chosen, you'll remove the elements from the DOM holding the previously chosen options sub-options.

If you can get your menu out in form of a list of MenuItem objects, each of which has a (sometimes empty) list of sub items (and I really mean a List<MenuItem> here... we're going to use this collection as a datasource for a sub-repeater, so it needs to implement IEnumerable<T>) as a property MenuItem.SubItems, you could probably make use of a UserControl that loops out one menu level, and calls upon itself for the next.
In the UserControl you'd have something like this:
<li><a href='<%= this.MenuItem.Url %>'><%= this.MenuItem.LinkText %></a></li>
<asp:Repeater ID="UCRepeater" runat="server">
<HeaderTemplate>
<ul>
<ItemTemplate>
<menu:MenuItem ID="MenuItemUC" runat="server" />
</ItemTemplate>
<FooterTemplate>
</ul>
</FooterTemplate>
</asp:Repeater>
The UserControl in the ItemTemplate is the same one, so for each item template the same thing will be rendered.
Below is the Code Behind for this user control, and this is where the magic happens:
public partial class MenuItemUserControl : UserControl
{
// A property we'll use as the data source
public MenuItem MenuItem { get; set; }
protected void Page_Load(object sender, EventArgs e)
{
// If the current menu item has sub items, we bind the repeater to them
// And by the way, there is no use doing this on every postback. First
// page load is good enough...
if(!Page.IsPostBack) {
{
if(MenuItem.SubItems.Count > 0)
{
UCRepeater.DataSource = MenuItem.SubItems;
UCRepeater.DataBind();
}
}
}
protected void UCRepeater_OnItemDataBound(object sender,
RepeaterDataBoundEventArgs e)
{
// Every time an Item is bound to the repeater, we take the current
// item which will be contained in e.DataItem, and set it as the
// MenuItem on the UserControl
// We only want to do this for the <ItemTemplate> and
// <AlternatingItemTemplate>
if(e.Item.ItemType == ListItemType.Item ||
e.Item.ItemType == ListItemType.AlternatingItem)
{
var uc = (MenuItemUserControl)e.Item.FindControl("MenuItemUC");
if(uc != null)
{
// This is the magic. Abrakadabra!
uc.MenuItem = (MenuItem)e.DataItem;
}
}
}
}
So in order to get this to work, the only thing missing is really a nice way of getting your data out as a hierarchical list of MenuItems. This I'll leave to your data access layer (and it would be cheap easy using LINQ to SQL or Entity Framework... ;) )
DISCLAIMER: This code is provided as is, and I wrote it off the top of my head. I have not tested it, but I think it will work - and if it doesn't, it could at least give you an idea of how to solve the problem. If you have problems, please post them in comments and I'll try to help out - but there are no promises of success here. Just a willingness to help out! =)

Related

Switcher<Placeholder, PlaceholderSwitcher>.GetStack returns null How to implement dynamic placeholders in sitecore

I'm trying to implement Nick Wesselman's Dynamic Place Holder technique in Sitecore. I'm using sitecore 6.5 and asp.net.
http://www.techphoria414.com/Blog/2011/August/Dynamic_Placeholder_Keys_Prototype
http://www.techphoria414.com/Blog/2012/May/Sitecore_Page_Editor_Unleashed
I used the sourcecode I Found in the Sitecore_Page_Editor_Unleashed BLOG
All the pipelines seem to be in place and working. But in the Dynamic Placeholder control the following bit of code returns 0 (zero) although there are five dynamic placeholders on the control
Stack<Placeholder> stack = Switcher<Placeholder, PlaceholderSwitcher>.GetStack(false);
To Isolate the problem I created a very simple sitecore instance. 1 layout and 1 sublayout.
In the codebehind sublayout I have the following code for demo purposes:
var list = new List<int>();
for (int i = 0; i < 5; i++)
{
list.Add(i);
}
Repeater.DataSource = list;
DataBind();
This is the source of the ascx/sublayout
<asp:Repeater runat="server" ID="Repeater">
<ItemTemplate>
<mi:DynamicKeyPlaceholder runat="server" ID="pl" Key="place"></mi:DynamicKeyPlaceholder>
</ItemTemplate>
</asp:Repeater>
Result is that all five placeholders still have the same key.
What to do?
I've never tested this solution with multiple dynamically created placeholder controls inside the same sublayout. It's designed to solve the problem of the same sublayout (with a placeholder) being placed on a page, multiple times. It works by grabbing the ID of the containing rendering and appending it to the placeholder key. Steps I would take:
Ensure that the sublayout containing your repeater is placed in the layout dynamically, through a placeholder. You should not be using <sc:Sublayout/> to put the sublayout on the page. This may explain why the stack is empty.
You will also need to databind the "Key" field on the DynamicKeyPlaceholder. Within the same sublayout, all the DynamicKeyPlaceholder controls need to have a unique Key.
Of course, the danger here is that if the data driving your repeater changes, the Key could change. You might consider re-evaluating your architecture and driving it based on placing the same sublayout multiple times (with datasources), instead of a repeater.
What I have done is similar what you are requesting...I created a reference of another sublayout (which implements dynamic placeholders) from the calling control (which want to have dynamic ph).
So suppose my calling control is ControlA and implementing control is ControlB.
This is the markup of ControlA:
<ControlB ID="SectionItems" runat="server" CurrentItem="<%# Container.DataItem %>" />
And this is the code behind for it:
private static void SetIndexForControlBItems(RepeaterItemEventArgs e)
{
var controlBItems = e.Item.FindControl("ControlB") as ControlB;
if (controlBItems != null)
controlBItems.PropertyName = e.Item.ItemIndex.ToString();
}
When you assign the property name (which is going to be present in ControlB) then you will get the value of which index you are assigning dynamic PH to. And in ControlB you can do something like this:
private void SetPlaceholderKeys(RepeaterItemEventArgs e)
{
Placeholder phPreColumnContentTopLeft = e.Item.FindControl("phColumnContentTopLeft") as Placeholder;
if (phPreColumnContentTopLeft != null)
phPreColumnContentTopLeft.Key = "topLeftColumnSectionItems" + e.Item.ItemIndex + PropertyName;
Hope this helps!

How to get access to DropDownList control if I add them from code

I am trying to access a DropDownList within a div after I add them from the code-behind.
If I add DropDownList to my .aspx page like this:
<div id="FiltersDiv" runat="server">
<asp:DropDownList ID="DropDownListMenu" runat="server"
DataTextField="name" DataValueField="id"
AutoPostBack="true"
onselectedindexchanged="DropDownListMenu_SelectedIndexChanged" >
</asp:DropDownList>
I can get access in any event
protected void AddButton_Click(object sender, EventArgs e)
{
var dl = addProductLocationFiltersDiv.Controls.OfType<DropDownList>();
}
but not if I add from code
var ddl = new DropDownList();
ddl.DataSource = flist;
ddl.DataTextField = "name";
ddl.DataValueField = "id";
ddl.DataBind();
FiltersDiv.Controls.Add(ddl);
I need to add several DropDownList’s and I do not now how many will be. So, I need to looping thru.
Did I miss some attribute or how I need to do it?
When you add a control via code, you access it through the variable name.
If you need access to several of these, consider putting then in a List<DropDownList> (or List<Control> if you have different types of controls).
If you need to share these across methods in your code behind, use a field instead of a local variable.
Note, that due to how the ASP.NET page lifecycle works, you will probably need to recreate these on every postback, otherwise you will not be able to access them. The best location to recreate such controls dynamically is in the OnInit handler.
foreach(DropDownList ddl in FiltersDiv.Controls.OfType<DropDownList>())
{
Trace.Write(ddl.SelectedValue);
}
This is off the top of my head, so you may need to cast the FiltersDiv to a drop down list - As said, you'll need to make sure the controls have been recreated as soon as possible on the page before the control data is written or read.
If you try this too soon, or havn't created the controls you'll get no results.

Populate gridview based on databound dropdown list selectedvalue change

I have an asp.net application for membership management. One page needs to have a gridview which is populated based on a dropdown list of statuses. I initially thought about hard-coding with a Select Case, but then remembered that the dropdown list is databound and needs to be dynamic (because the admin-level users have another page to change the statuses). I'm still new at this, and my searches are not turning up anything.
Any links or examples would be helpful. Thanks.
I would suggest to use OnSelectedIndexChanged event of dropdownlist for your purpose with AutoPostBack property set to true, something like this
<asp:DropDownList runat="server" ID="ddlStatus" OnSelectedIndexChanged="ddlStatus_SelectedIndexChanged" AutoPostBack="True"></asp:DropDownList>
And on your code behind page you can bind your grid differently for different selected values in your event handler, something like this
protected void ddlStatus_SelectedIndexChanged(object sender, EventArgs e)
{
if (ddlStatus.SelectedItem.Value == "RequiredValue")
{
// bind grid in some way
}
else
{
// bind grid in some other way
}
}
This will work irrespective of your binding the dropdownlist options dynamically or having them hard coded.

Displaying and hiding parts of the web form freely without limitations in order to avoid ViewState loading problems

The problem
I have a web form and I would like to hide and show some panels. When I say "Panel" I do not mean the Panel control but just a pane in the html (usually represented by a DIV).
Well I want to show and hide this panels as I wish... no limits means that not necessarly only one pane must by visible, also 2, 3 or 4 can be.
Usually I achieved this by:
<div id="mypane1" runat="server">
...
</div>
<div id="mypane2" runat="server">
...
</div>
<div id="mypane3" runat="server">
...
</div>
and this:
this.mypane1.Visible = true;
this.mypane2.Visible = false;
this.mypane3.Visible = true;
Unfortunately these panels contain controls and some of them are not shown from the beginning (first web form load, so when no PostBack happens). This leads to problems for ViewState loading.
Regarding ViewState loading
Of course many will obviously ask about these ViewState problems of mine. Well, briefly, the thing is this: a ViewState loading error is not easy to manage but in the end I could understand the following.
Let's say to have this:
<!-- In .aspx -->
<div id="mypane1" runat="server">
...
<asp:Literal ID="..." runat="server" ...></asp:Literal>
<asp:TextBox ID="..." runat="server" ...></asp:TextBox>
...
</div>
<div id="mypane2" runat="server">
...
<asp:LinkButton ID="lb1" OnClick="lb1_Click" runat="server" ...></asp:LinkButton>
<asp:LinkButton ID="lb2" OnClick="lb2_Click" runat="server" ...></asp:LinkButton>
<asp:LinkButton ID="lb3" OnClick="lb3_Click" runat="server" ...></asp:LinkButton>
...
</div>
<div id="mypane3" runat="server">
...
<asp:TextBox ID="..." runat="server" ...></asp:TextBox>
<asp:LinkButton ID="lb4" OnClick="lb4_Click" runat="server" ...></asp:LinkButton>
<asp:TextBox ID="..." runat="server" ...></asp:TextBox>
...
</div>
// In .aspx.cs
protected void Page_Load(...) {
if (!this.IsPostBack) {
this.mypane1.Visible = true;
this.mypane2.Visible = false;
this.mypane3.Visible = true;
}
}
/// ... In the page many happens and the panes are shown and hidden ... ///
// Event Handlers
protected void lb1_Click(object sender, EventArgs e) {
...
}
protected void lb2_Click(object sender, EventArgs e) {
...
}
protected void lb3_Click(object sender, EventArgs e) {
...
}
protected void lb4_Click(object sender, EventArgs e) {
...
}
Well the problem is the following.
At the beginning pane2 is not shown.
After some actions pane2 is shown and its controls are shown too.
User click lb1 --> ViewState error
Note that the same happens for every event associated to pane2's controls. Is lb4 is pressed, no problems.
The problem is that the beginning hierarchy is represented by controls in pane 1 and 3. When events are raised by controls not part if the beginning hierarchy, ViewState is found inconsistent (because I act on HtmlControls, usually ServerControls are more intelligent and can manage better ViewState when controls are added inside them).
Feasible solutoions
Note that I could simply do the following to hide and show panels:
this.mypane2.Attributes.Add("display", "none");
This is not something I would like to do for two reasons:
I want panes not to be rendered as Html.
I would like to use server controls.
Request
I need to use web controls to manage my panel management, I cannot use HtmlControls because Control Hierarchy is not correctly loaded.
I tried MultiView and View controls but they enable me to use only one active view. How can I do?
Note: Please do not focus too much on the ViewState problem, it is just a good desc to let you know what happens, my only interest is to find a way to hide and show panes freely using server controls.
Thankyou
I'm assuming that setting the visiblity to false for the panels causes the children not to be added to ViewState or interfers with it in some way. Is that correct?
Given that, if you want to have the controls there as far as the .Net rendering engine is concerned but hidden from the end user you could try to show and hide them using css i.e.
show
pnlDemo.Attributes.Add("style", "display:block");
hide
pnlDemo.Attributes.Add("style", "display:none");
I had a situation where I needed ASP.Net controls there for ViewState purpose but not shown. At the appropriate point I would show them using JQuery and this method work for that scenario so it may work for yours.
Have you tried using the ViewStateModeByIdAttribute?
The ViewStateModeByIdAttribute class is used to specify a control that requires view-state loading by ID. The default view-state loading behavior is for ASP.NET to load the view-state information for a control by its index in the control tree of the page. There is a performance cost for loading view-state information by ID because the page control tree must be searched for the control specifically before loading its view-state information.
Have you tried with page.RegisterStartupScript .
script will load at the end of page load so control will have their viewstate .
Write JavaScript code that get the id of panel to hide & change the CSS to visibility hidden not display none.
another asp button on which you want to display the panel.
you can write JavaScript code onClientClick event of button to change the CSS of panel to change its visibility.

ASP.Net - repeating input boxes on the client side using Repeater

I have the following requirement for creating a user profile in my application:
User should be able to enter multiple phone numbers/email addresses in his profile.
The screen looks somewhat like this:
- By default, on page load a single textbox for phone and email are shown.
- User can click a "+" button to add additional numbers/addresses.
- On clicking the "+" button we need to add another textbox just below the first one. User can add as many numbers/addresses as he wants. On submit, the server should collect all numbers/emails and save it in DB.
I tried using the Repeater control to do this. On page_load I bind the repeater to a "new arraylist" object of size 1. So, this renders fine - user sees a single textbox with no value in it.
When he clicks the "+" button, I ideally want to use javascript to create more textboxes with similar mark-up as the first.
My questions are these:
Can I render the new textboxes anyway using js? I notice that the HTML rendered by the repeater control is somewhat complex (names/ids) etc. and it might not be possible to correctly create those controls on client-side.
If there is a way to do #1, will the server understand that these additional inputs are items in the repeater control? Say, I want to get all the phone numbers that the user entered by iterating over Repeater.DataItems.
Conceptually, is my approach correct or is it wrong to use the Repeater for this? Would you suggest any other approach that might handle this requirement?
Coming from a Struts/JSP background, I am still struggling to get a grip on the .NET way of doing things - so any help would be appreciated.
The repeater control may be a bit of overkill for what you're trying to accomplish. It is mainly meant as a databound control for presenting rows of data.
What you can do is to dynamically create the boxes as part of the Page_Load event (C#):
TestInput.aspx :
<form id="form1" runat="server">
<asp:HiddenField ID="hdnAddInput" runat="server" />
<asp:Button ID="btnPlus" OnClientClick="setAdd()" Text="Plus" runat="server" />
<asp:PlaceHolder ID="phInputs" runat="server" />
</form>
<script type="text/javascript">
function setAdd() {
var add = document.getElementById('<%=hdnAddInput.ClientID%>');
add.value = '1';
return true;
}
</script>
TestInput.aspx.cs:
protected void Page_Load(object sender, EventArgs e)
{
if (ViewState["inputs"] == null)
ViewState["inputs"] = 1;
if (hdnAddInput.Value == "1")
{
ViewState["inputs"] = int.Parse(ViewState["inputs"].ToString()) + 1;
hdnAddInput.Value = "";
}
for (int loop = 0; loop < int.Parse(ViewState["inputs"].ToString()); loop++)
phInputs.Controls.Add(new TextBox() { ID = "phone" + loop });
}
I ended up using a PlaceHolder to dynamically add the text boxes and a HiddenField to flag when another TextBox needed to be added. Since the IDs were matching, it maintains the ViewState of the controls during each postback.
Welcome to the hairball that is dynamically-added controls in ASP.NET. It's not pretty but it can be done.
You cannot add new fields dynamically using javascript because the new field would have no representation in the server-side controls collection of the page.
Given that the requirements are that there is no limit to the number of addresses a user can add to the page, your only option is to do "traditional" dynamic ASP.NET controls. This means that you must handle the adding of the control server-side by new-ing a new object to represent the control:
private ArrayList _dynamicControls = new ArrayList();
public void Page_Init()
{
foreach (string c in _dynamicControls)
{
TextBox txtDynamicBox = new TextBox();
txtDynamicBox.ID = c;
Controls.Add(txtDynamicBox);
}
}
public void AddNewTextBox()
{
TextBox txtNewBox = new TextBox();
txtNewBox.ID = [uniqueID] // Give the textbox a unique name
Controls.Add(txtNewBox);
_dynamicControls.Add([uniqueID]);
}
You can see here that the object that backs each dynamically-added field has to be added back to the Controls collection of the Page on each postback. If you don't do this, data POSTed back from the field has nowhere to go.
If you want to user the repeater, I think the easiest way is to put the repeater in a ASP.Net AJAX update panel, add the extra textbox on the sever side.
There are definitely other way to implement this without using repeater, and it maybe much easier to add the textbox using js.
No, but you can create input elements similar to what TextBox controls would render.
No. ASP.NET protects itself from phony data posted to the server. You can't make the server code think that it created a TextBox earlier by just adding data that it would return.
The approach is wrong. You are trying to go a middle way that doesn't work. You have to go all the way in either direction. Either you make a postback and add the TextBox on the server side, or you do it completely on the client side and use the Request.Form collection to receive the data on the server side.

Resources