Equivalent of FindControl() for use with <li> - c#

I have an <li> on my page. It has runat="server". I can now reference this li directly in code-behind. However, as I do with other controls on the page, I need to find it based on a string value that should match it's ID.
As part of a loop I have:
var li = Page.FindControl("li" + i);
Ultimately wanting to change the class of the li. But I don't seem to have access to any attributes just as I would if I had referenced the li directly. What have I done wrong here? Is there another method for amending an li's attributes from code-behind?

As you are using generic html elements on the server-side, you will get an instance of HtmlGenericControl when accessing the <li> on the server. The HtmlGenericControl class does not support the Class or CssClass properties as do most .NET web controls. Still you can change the class via the Attributes collection:
var li = (HtmlGenericControl) Page.FindControl("li" + i);
li.Attributes["class"] = "myCssClass";
The Page.FindControl("li" + i) method requires a valid control ID. Therefore, you need to also add an id for the <li> elements:
<li runat="server" id="li1">...</li>
If you are creating a dynamic list inside a repeater or list view control, however, this may not work for you, as the id's will get prefixed by the parent control. You should hook to the appropriate item created event for the repeater/list view control in order to programatically access the <li>.

Cast your control in HtmlGenericControl to add attributes
HtmlGenericControl li = (HtmlGenericControl)Page.FindControl("li" + i);
li.Attributes["onclick"] = "return null;";

Related

Disabling li elements asp.net

I programatically create li elements, however I need some of them disabled, so far I have:
HtmlGenericControl htmlLi = new HtmlGenericControl("li");
htmlLi.InnerText = row["name"].ToString();
tab_content.FindControl("tab_content_" + row["stars"].ToString()).Controls.Add(htmlLi);
Is it possible to disable them? If not, is their any alternatives?
An <li> element can't be disabled, as there's no input to prevent.
You can however hide the elements you don't want to be displayed.
You can either hide them from the code-behind:
htmlLi.Visible = false;
or you can add some styling to it so that it is put into the DOM, but made invisible by the CSS (so that you can make it visible again with javascript, should you want to):
htmlLi.Attributes.Add("style", "display: none");
or
htmlLi.Attributes.Add("class", "SomeInvisibleCssClass");

li with runat server

I have a problem with creating dynamic <li> elements from codebehind. I need to assign runat server to li, but I didnt find a way to assign runat server, so I can't find that li control when I need to change the attributes from code behind. Is there any answer to my problem? I am new to asp.net c#.
Here is my code:
<ul class="nav nav-tabs" runat="server" id="tabList">
//First i got ul control that i assign runat=server in aspx page
</ul>
//then i create li from code behind in Page_Init()
System.Web.UI.HtmlControls.HtmlGenericControl tab = new System.Web.UI.HtmlControls.HtmlGenericControl("li");
tab.ID = "tab" + (i + 1);
tab.Attributes.Add("runat", "server");//this is not working
tab.Controls.Add(new LiteralControl("Penumpang " + (i + 1) + ""));
//then i add the li to my ul controler called tablist
this.tabList.Controls.Add(tab);
My problem is, when the page loads, I can see the li on the page, but I cannot call li from the code behind when i need to do something with it. Is there any way to call the li in code behind? Or change the li attributes when it is assigned dynamically? Sorry for bad English.
Thanks in advance.
The reason why you cannot access the <li> in your code-behind is because it is dynamically generated.
Dynamically generated controls lose their state when they are rendered on the view and for you to access them again in your code-behind when a postback occurs, you need to recreate them before playing with them. So basically, you need to recreate them everytime on your postback to access their properties and values and to manipulate with them.
Also, runat='server' wont work from code-behind. Instead of using <li>, you could also try using some ASP.NET controls such as Listview or other data-binding controls.
Hope this helps.

User Controls required an ID in postback?

I add some div into a panel on server side, when the page is generated, and I add a ID for each one :
HtmlGenericControl divContainerInside = new HtmlGenericControl("div");
divContainerInside.ID = "inside_" + m_oIDCategoria + "_" + numero;
than, on postback (after re-creating them), I cycle them :
foreach (HtmlGenericControl divInside in myPanel.Controls.OfType<HtmlGenericControl>())
{
Response.Write(divInside.ID);
}
all is ok! But, if I remove that divContainerInside.ID when I generate it, I get a NullException cycling them. Why?
I guess you get NullException when you try to read the ID, which you haven't set.
If you change your code like this, you'll get the value:
foreach (HtmlGenericControl divInside in myPanel.Controls.OfType<HtmlGenericControl>())
{
Response.Write(divInside.ClientID);
}
PS: I don't know if you have got this line of code:
myPanel.Controls.Add(divContainerInside);
If you want to find out more about web controls you can read this article and this.
You cant add a control to the page using response.Write you need to add it to the Pages or another controls control collection like below:
Page.Controls.Add(divInside);

How to programmatically access a control on a aspx page from the cs file

How can I access a control on an aspx page from the cs file in a programmatic way?
For instance, if I have a set of asp:Panel controls each with an ID named by a city (id="atlanta", id="chicago", id="pittsburgh", etc.) and then in the cs I grab a value from the database to match up to the control names what would I use?
I tried to use FindControl() as shown and it returns null.
aspx page:
<asp:Panel ID="atlanta" runat="server" Visible="false"></asp:Panel>
cs file:
controlName = storeLocation.City.ToLower();
Panel cityPanel = (Panel)FindControl(controlName);
cityPanel.Visible = true;
I suppose FindControl() is really for use in cases like Repeaters or Grids where you pass in the ItemTemaplate. In my case its just a simple content page with a content tag with a bunch of panels in it.
FindControl() isn't recursive, which may be your problem. However, there are many implementations of a recursive version, such as this one.
If you are creating the panels dynamically and you want access to them later in the page lifecycle, you can add them all to a Dictionary<string, Panel> where the ID is the key.
You can use this extension method to find controls recursively:
public static class ControlExtension
{
public static IEnumerable<Control> GetAllControls(this Control parent)
{
foreach (Control control in parent.Controls)
{
yield return control;
foreach (Control child in control.GetAllControls())
{
yield return child;
}
}
}
}
And then in your code behind page you could do something like this if you prefix your labels:
IEnumerable<Control> city_controls = this.GetAllControls().Where(x => x.Id.Contains("city_"))
or a single control:
var city = this.GetAllControls().Single(x => x.Id = "atlanta");
Where the this is your ASPX code behind page.
It also works on controls such as panels, so if you want to find all controls inside a panel etc.
If you know the containing control you may be able to use the FindControl() function of that control to find it. This method will be much faster and safer than using a recursive method.
Microsoft did not include a recursive FindControl() function for a reason, it will slow your page down over time as more controls get added to the page. If that doesn't work use a recursive function like other responses suggested.
This should work for you since the panels are nested in the asp:Content control.
Panel cityPanel =
(Panel)Master.FindControl("ContentPlaceHolderId").FindControl(controlName);
Where off course, ContentPlaceHolderId is the Id of the corresponding ContentPlaceHolder in the Master page you are implementing.

Prevent new render of CheckBoxList

I have an <asp:CheckBoxList> using RepeatLayout="Flow" in my page I dynamically assign values to. If the page is loaded the first time (!IsPostBack), the rendered version looks similar to this;
<span children="Cat">
<input id="ctl00_Menu_Category_0" type="checkbox" name="ctl00$Menu$Category$0"/>
<label for="ctl00_Menu_Category_0">Cat</label>
</span>
<br/>
<span class="Cat">
<input id="ctl00_Menu_Category_1" type="checkbox" name="ctl00$Menu$Category$1"/>
<label for="ctl00_Menu_Category_1"> - SubCat1</label>
</span>
children is an attribute I use for a jQuery-code, so when the user checks Cat, all SubCats are also checked.
The jQuery code searches for all <span>s that have the class equal to the children-attribute, so I need to maintain this structure that the jQuery works.
But, after I reload the page or follow a link, whatever, the list suddenly looks like this:
<input id="ctl00_Menu_Category_0" type="checkbox" name="ctl00$Menu$Category$0"/>
<label for="ctl00_Menu_Category_0">Cat</label>
<br/>
<input id="ctl00_Menu_Category_1" type="checkbox" name="ctl00$Menu$Category$1"/>
<label for="ctl00_Menu_Category_1"> - SubCat1</label>
How is that even possible? I assigned the values to the list only once, so why is it re-rendered after a PostBack and how can i prevent it from doing so?
Edit
Here is the code that creates the list;
// Get all available categories that are not a child of another category
DataTable categoryParents = functions.SelectSql(Resources.Data.GetCategoryParents);
// Get the child categories
foreach (DataRow parent in categoryParents.Rows)
{
// Add the category
ListItem parentItem = new ListItem(parent["Name"].ToString(), parent["Name"].ToString());
parentItem.Attributes.Add("children", parent["Name"].ToString().Replace(' ', '_'));
Category.Items.Add(parentItem);
// For every parent category, get all its child categories
DataTable categoryChildren = functions.SelectSql(Resources.Data.GetCategoryChildrenByParent.Replace("##PARENTID##", parent["ID"].ToString()));
// Add the child categories after their parents
foreach (DataRow child in categoryChildren.Rows)
{
ListItem item = new ListItem(" - " + child["Name"].ToString(), parent["Name"].ToString() + "\\" + child["Name"].ToString());
item.Attributes.Add("class", parent["Name"].ToString().Replace(' ', '_'));
Category.Items.Add(item);
}
}
Category.DataBind();
jQuery itself doesn't do anything with the HTML, it just holds the functionality for checking children categories when a parent is checked;
$("#Category :checkbox").click(function(){
var checked = $(this).attr("checked");
var children = $(this).parent("span").attr("children");
$("#Category ." + children + " :checkbox").attr("checked", checked);
});
The entire HTML code will always be rendered each time you reload the page. What you can prevent by checking IsPostBack is changing how the HTML is being rendered this time around. That means that when the page posts back, the server will not bind the checkbox list with new values, but will go directly to render them exactly the way it did last time, using the values that are stored in ViewState.
If your jQuery code alters the HTML after it is being rendered, the server will have no idea and there's really no feasible way of changing that. The interesting question here is: how are the wrapping spans and the children properties and all that code being applied in the first place?
Your options are to do one of the following:
Make an AJAX request rather than a full postback, changing only the part of the DOM you want to change
Re-apply the jQuery code that achieves this change, upon DOMReady after postback
EDIT
In response to the edits in the original post:
Is Category your CheckBoxList? If you've iteratively added all list items to it, why do you databind it over again, after that? I think the first for loop should make the databinding obsolete.
My best guess here is that when CheckBoxList is being serialized to ViewState, it stores the properties applied to it, along with a id/value dictionary for the listitems (and not the additional properties that you apply iteratively).
If it's not being saved in the ViewState - and it looks like it isn't - there are no really clean solutions to your problem. Workarounds would be to execute the code on every pageload, or inherit the CheckBoxList in a subclass, that overrides the Render method, to always produce the output you want. In fact that last option might be quite neat, if you're using this a lot throughout the site...?

Categories