views:

89

answers:

1

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);
});
A: 

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...?

David Hedlund
jQuery does no manipulation at all, see my edit.
ApoY2k
alright, i'm afraid i don't have a simple and awesome solution, but see my edit
David Hedlund
Okay, a workaround seemed to work; I set `EnableViewState="false"` and now it renders correctly every time... but deletes the selected boxes after reload :-/
ApoY2k
if it works when you disable viewstate, you have to re-bind it with data upon every postback. if you *are* re-binding it with data on every postback, you can, in that bind, check your post collection: `item.Checked = Request.Form[item.ClientID] == "on";`
David Hedlund
That is, no doubt, a very clever idea, however it doesn't work for me... If I try to check `Request.Form["Category"]` I get an exception that a reference is not set :-/
ApoY2k
well, yes, your ID, as you'll find it in the `Request.Form` collection is not "Category", it's "ctl00_Menu_Category_0", hence the `ClientID`-part. I don't know at what point this ClientID is assigned, though, (but definitely *after* adding it to `Category.Items`), so you might have to generate it manually - `'ctl00_Menu_Category'+someIndex` - which is hackish, but should work.
David Hedlund