Dynamically growing text fields - c#

What's the best way to approach the following situation in asp.net mvc2 (c#):
I have a form where a user can add more fields ... for example I ask user the details of personal computing devices that they have. For simplicity sake let's say I ask for the brand and type (dropdown list of Desktop/Laptop/Tablet/Other) and they can provide unlimited devices. I will start with a pair of text boxes:
<label>Type</label>
<select><option>Desktop</option><option>Laptop</option><option>Tablet</option><option>Other</option></select>
<label>Brand</label>
<input type="text" />
<input type="button" value="Add more device" />
And then as user click the "Add more device" button I will display another pair of Brand and Type text boxes ... and so on.
My question is how should I use HTML helper for this, how I construct the viewModel, etc.
I am from PHP background and I used to use the following in PHP:
<input type="text" name="brand[]" />
which on the back end will give me an array of brands ... not sure if that's doable in asp.net environment. I would appreciate any input. Thank you.
[I added the following lines to make my question clearer]
I think I have not been very clear with my question.
Suppose I have the following viewmodel:
public class UserRegisterViewModel
{
public string DeviceBrand { get; set; }
public string DeviceType { get; set; }
}
That works well when I have two text boxes:
<%: Html.TextBoxFor(m => m.DeviceBrand) %>
<%: Html.TextBoxFor(m => m.DeviceType) %>
but the situation that I am facing is I need to allow user to add more pair of device brand and type text boxes ... user should be able to add as many as he needs to.
How should I write my viewmodel and view?
I hope this makes my question a bit clearer. I don't have problem in hiding and showing the text boxes (and yes I use JQuery for that).
Thank you.

In C#, like C++ arrays are fixed sizes. You can however use a List<> to do something similar. It allows you to dynamically add data with an add function. Like so:
List<object> name = new List<object>();
name.Add(an_object);
So say a list of strings:
List<string> myStrings == new List<string>();
myStrings.Add("Blah");
They're accessed just like arrays - Console.WriteLine(myStrings[0]) outputs Blah

Depending on scenarion there are following solutions:
If you already know which Textboxes will be shown add them in your page and set their visiblity to false (like in CSS "display:none") And show them later.
If you dont know in advance which textboxes will be shown you can still append them in HTML DOm (Add it to your page's controls collection)
To show them there are two apporaches:
In first case Either use jQuery or Javascript to show them back.
In first case Use can also do it from server side that will result in whole page post back.
In second Apporach Also you can use jQuery or Javascript to add new textboxes in the form
In second case you can add new boxes from your C# code on server but again it will result in whole page post back.
Use any approach that best suits you

What you can do is having UserRegister as Model and have UserRegisterViewModel
whith List property
Then in your view you can have loop to render your viewmodel list property
in a foreach for example in C#

Lets say you have your ViewModel defined as:
public class BrandListViewModel
{
public List<UserRegisterViewModel> Brands {get;set;}
}
Your View would need to output HTML as:
<select name="Brands[0].DeviceType">...</select>
<input type="text" name="Brands[0].DeviceBrand"/>
and
<select name="Brands[1].DeviceType">...</select>
<input type="text" name="Brands[1].DeviceBrand"/>
You need to keep the index going so that the model binder can construct your list in the correct order. Now, you could use jQuery to do this and as you append items to the page just get a count and increment by one. If you want to remove a single one, you need to rename all of the fields.
You could also use a pure jQuery method, parse the elements, push items to an array and send the array to the server.

Related

Load data initially when Blazor component is initialized; don't fire that query again when page is updated

I have a Blazor component that displays data from SQL in an unordered list.
The LoadData method is called by the OnInitialized() event and populates a List<T> (in this case, a list of ProjectModels). The data volume is large, therefore I only fetch the top 100 rows from SQL (meant as a preview).
I iterate over that list with a foreach and create a <li> for each element.
This works fine.
However, the page also contains a search box. Once the user enters a search string and clicks the search button, a different method is called that gets data from SQL again, this time filtering by the user-provided search term. The filtering is done in the SQL query. The query result is again used to populate the list of ProjectModels (it overwrites the first instance of the list).
I can see the new, filtered list for a millisecond, then the initial data pops back up. I guess this is because OnInitialized is triggered again, which contains the query that populates the list with the initial data.
This is what my component looks like:
#page "/projects"
<form>
<input type="search" placeholder="enter search term..." #bind-value="searchTerm">
<button type="submit" #onclick="GetSearchResults">Search</button>
</form>
<ul>
#foreach (var project in projects)
{
<li>#project.ProjectId</li>
<li>#project.ProjectTitle</li>
}
</ul>
#code {
private string searchTerm;
private List<ProjectModel> projects = new List<ProjectModel>();
protected override void OnInitialized()
{
projects = GetProjects(); //the method that loads the inital data
}
private void GetSearchResults()
{
projects = GetProjectsBySearchTerm(searchTerm);
}
}
How can I do this correctly? i.e.
Get the initial preview data on page load and put it in a <ul>
replace the data in the <ul> with the data that was queried based on the provided search term while avoiding that the initial load is triggered once the data changes.
C# and Blazor noob here, so please go easy and let me know if I need to provide more details.
thanks to all of you.
This is happening because of the form - your button is submitting the form, which causes the browser to reload the page.
Easiest fix - don't use a form on a SPA app, unless you really want to reload the page.
Both and Blazor and normal HTML. A submit button reloads the page. When the page is reloaded it launches the OnInitialize function. In blazor is better to use a normal button for it. In your code simply remove the type="submit" from your button. That will make it work. Additionally, you don't need to put it into a form since you won't be sending the form as a whole to the backend.
Change your code to this:
<div>
<input type="search" placeholder="enter search term..." #bind-value="searchTerm">
<button #onclick="GetSearchResults">Search</button>
</div>
I'm pretty sure you can just #bind instead of #bind-value. However, the problem is having form/submit. There's no reason to submit anything, as you're not sending data out of this page. Also, OnInitialized fires 2x if PreRender is on (which it is by default). If that really bothers you, then load your data in OnAfterRender and check for firstRender.

Add item into List from View Loaded Dynamically and pass it to Controller in asp.net core

I am working on Mobile Store Management System's Order page. I want to allow users to select a company through a select list, and then select multiple models of that company from another select list which is loaded dynamically through AJAX.
The code for the cascading models is working, but I am unable to send the selected models to the server because it is adding them in the DOM through JavaScript.
The following is the code for the cascading selection:
<div class="form-group row">
<label class="control-label col-6">Company Name</label>
<div class="col-12">
<select id="CompanyId" class="custom-select mr-sm-2"
asp-items="#(new SelectList(
#ViewBag.Companies,"Phoneid","Com_name"))">
<option value="">Please Select</option>
</select>
</div>
<span class="text-danger"></span>
</div>
<div class="form-group row">
<label class="control-label col-6"></label>
<div class="col-12">
<select id="modelId" multiple class="custom-select mr-sm-2"
asp-items="#(new SelectList(string.Empty,"modelId","model_name","--Select--"))">
<option value="">Please Select</option>
</select>
</div>
<span class="text-danger"></span>
</div>
<div>
<input type="button" id="saveBtn" value="Save" />
</div>
Cascading Code:
$("#CompanyId").change(async function()
{
await $.getJSON("/Order/GetAllModels",{ PhoneId: $("#CompanyId").val()},
function(data)
{
$("#modelId").empty();
$.each(data, function (index, row) {
$("#modelId").append("<option value='" + row.modelId + "'>" +
row.model_name + '</option>')
});
});
}
Once the Save button is clicked, I am displaying the product for the currently selected models using a partial view:
$('#saveBtn').click(function () {
$.ajax({
url: '/Order/GetProduct?Phoneid=' + $("#CompanyId").val() + "&modelId=" + $('#modelId').val(),
type: 'Post',
success: function (data) {
$('#products').append(data);
},
})
})
Problem 1
When the user selects the first company and their two models, and then clicks the Save button, the partial view loads with indexes i=0,i=1. Then, the user selects another company and selects their models. Again, the partial view renders with same indexes. How can I make the indexes unique? This partial view is rendered when the user clicks the Save button, which renders only the current company's selected models.
#model List<Mobile_Store_MS.ViewModel.Orders.Products>
<table class="table">
<tbody>
#for (int i = 0; i < Model.Count; i++)
{
<tr class="card d-flex">
<td>
<input asp-for="#Model[i].isSelected" />
</td>
<td>
<input hidden asp-for="#Model[i].Phoneid" /> <input hidden asp-for="#Model[i].modelId" />
#Html.DisplayFor(modelItem => Model[i].com_Name) #Html.DisplayFor(modelItem => Model[i].model_name)
</td>
<td>
<input asp-for="#Model[i].Quantity" />
</td>
<td>
<input class="disabled" readonly asp-for="#Model[i].price" />
</td>
</tr>
}
</tbody>
</table>
Problem 2
How can I send all of the items rendered through the partial view to the server? I just want to send these selected products along with the quantity and price for each model to the server. This means binding these items in the product list of the OrderViewModel.
You can find my OrderViewModel and Products model in the following diagram:
Can you tell me how to bind Razor items into a list to post to the controller? I would be very grateful if you give me some suggestions.
Related
Link of my Previous Question
Sample of my order page
TL;DR: Instead of relying on the asp-for tag helper, you can set your own name attribute. This gives you the flexibility to start the index at whatever number you want. Ideally, you will pass the number of existing products to GetProduct() and start indexing off of that. In addition, you also need to prefix your name attribute with Products, thus ensuring those form elements are properly bound to your OrderViewModel.Products collection on post back.
<input name="Products[#(startIndex+i)].Quantity" value="#Model[i].Quantity" />
You can then filter the OrderViewModel.Products collection on the server-side using LINQ to limit the results to selected products:
var selectedProducts = c.Products.Where(p => p.IsSelected);
For a more in-depth explanation of how this approach works, as well as some of the variations in the implementation, read my full answer below.
Detailed Answer
There's a lot going on here, so this is going to be a lengthy answer. I'm going to start by providing some critical background on how ASP.NET Core MVC connects the dots between your view model, your view, and your binding model, as that will better understand how to adapt this behavior to your needs. Then I'm going to provide a strategy for solving each of your problems.
Note: I'm not going to write all of the code, as that would result in me reinventing a lot of code you've already written—and would make for an even longer answer. Instead, I'm going to provide a checklist of steps needed to apply the solution to your existing code.
Background
It's important to note that while ASP.NET Core MVC attempts to standardize and simplify the workflow from view model to view to binding model through conventions (such as the asp-for tag helper) these are each independent of one another.
So when you call asp-for on a collection using e.g.,
<input asp-for="#Model[i].Quantity" />
It then outputs the something like the following HTML:
<input id="0__Quantity" name="[0].Quantity" value="1" />
And then, when you submit that, the server looks at your form data, and uses a set of conventions to map that data back to your binding model. So this might map to:
public async Task<IActionResult> ProductsAsync(List<Product> products) { … }
When you call asp-for on a collection, it will always start the index at 0. And when it bind the form data to a binding model, it will always start at [0] and count up.
But there's no reason you need to use asp-for if you need to change this behavior. You can instead set the id and/or name attributes yourself if you need flexibility over how they are generated.
Note: When you do this, you'll want to make sure you're still sticking to one of the conventions that ASP.NET Core MVC is already familiar with to ensure data binding occurs. Though, if you really want to, you can instead create your own binding conventions.
Problem 1: Setting the index
Given the above background, if you want to customize the starting index returned from your call to GetProducts() for your second model, you‘ll want to do something like the following:
Before calling GetProduct(), determine how many products you already have by e.g. counting the number of elements with the card class assigned (i.e., $(".card").length).
Note: If the card class is not exclusively used for products, you can instead assign a unique class like product to each tr element in your _DisplayOrder view and count that.
Include this count in your call to GetProduct() as e.g., a &startingIndex= parameter:
$('#saveBtn').click(function () {
$.ajax({
url: '/Order/GetProduct?Phoneid=' + $("#CompanyId").val() + "&modelId=" + $('#modelId').val() + "&startingIndex=" + $("#card").length,
type: 'Post',
success: function (data) {
$('#products').append(data);
},
})
})
[HttpPost]
public IActionResult GetProduct(int Phoneid, string[] modelId, int startingIndex = 0) { … }
Relay this startingIndex to your "partial" view via a view model; e.g.,
public class ProductListViewModel {
public int StartingIndex { get; set; }
public List<Product> Products { get; set; }
}
Use that value to offset the index written to the output:
<input id="#(Model.StartingIndex+i)__Quantity" name="[#(Model.StartingIndex+i)].Quantity" value="#Model.Products[i].Quantity" />
That's not as tidy as asp-for since you're needing to wire up a lot of similar information, but it offers you the flexibility to ensure that your name values are unique on the page, no matter how many times you call GetProduct().
Notes
If you don't want to create a new view model, you could relay the startingIndex via your ViewData dictionary instead. I prefer having a view model that includes all of the data I need, though.
When using the asp-for tag helper, it automatically generates the id attribute, but if you're not ever referencing it via e.g. JavaScript you can omit it.
Browsers will only submit values to the server for form elements that have a name attribute. So if you have input elements that are needed on the client-side but aren't needed in the binding model, for some reason, you can omit the name attribute.
There are other conventions besides {Index}__{Property} that you can follow. But unless you really want to get into the weeds of model binding, you're best off sticking to one of the existing collection conventions.
Be careful of your indexing!
In the Model Binding conventions for collections, you'll notice a warning:
Data formats that use subscript numbers (... [0] ... [1] ...) must ensure that they are numbered sequentially starting at zero. If there are any gaps in subscript numbering, all items after the gap are ignored. For example, if the subscripts are 0 and 2 instead of 0 and 1, the second item is ignored.
As such, when assigning these, you need to make sure that they're sequential without any gaps. If you're using the count (.length) of existing e.g. $(".card") or $(".product") elements on your page to seed the startingIndex value, however, then that shouldn't be a problem.
Problem 2: Sending these values to the server
As mentioned above, any form element with a name attribute will have its data submitted to the server. So it doesn't really matter if you're using asp-for, writing out your form manually using HTML, or constructing it dynamically using JavaScript. If there's a form element with a name attribute, and it's within the form being submitted, it will get included in the payload.
Debugging your form data
You're likely already familiar with this, but if not: If you use your browser's developer console, you'll be able to see this information as part of the page metadata when you submit your form. For instance, in Google Chrome:
Go to Developer Tools (Ctrl+Shift+I)
Go to the Network tab
Submit your form
Click on the name of your page (normally the first entry)
Go to the Headers tab
Scroll down to the Form Data section (or Query String Parameters for a GET request)
You should see something like:
[0].isSelected true
[0].Phoneid 4
[0].modelId 10
[0].Quantity 5
[0].price 10.50
[1].isSelected true
[…]…
If you're seeing these in Chrome, but not seeing these data reflected in your ASP.NET Core MVC controller action, then there's a disconnect between the naming conventions of these fields and your binding model—and, thus, ASP.NET Core MVC doesn't know how to map the two.
Binding Problems
There are two likely issues here, both of which might be interfering with your data binding.
Duplicate Indexes
Since you are currently submitting duplicate indexes, that could be causing collisions with the data. E.g., if there are two values for [0].Quantity, then it will retrieve those as an array—and may fail to bind either value to e.g. the int Quantity property on your Products binding model. I haven't tested this, though, so I'm not entirely sure how ASP.NET Core MVC deals with this situation.
Collection Name
When you bind to a List<Products> with the asp-for tag helper, I believe it will use the [i].Property naming convention. That's a problem because your OrderViewModel is not a collection. Instead, these needs to be associated with the Products property on your binding model. That can be done by prefixing the name with Products. This will be done automatically if you use asp-for to bind to a Products property on your view model—as proposed on the ProductListViewModel above. But since you need to dynamically generate the name's based on the IndexOffset anyway, you can just hard-code these as part of your template:
<input id="Products_#(Model.StartingIndex+i)__Quantity" name="Products[#(Model.StartingIndex+i)].Quantity" value="#Model.Products[i].Quantity" />
Selected Products
There's still a problem, though! This is going to include all of your products—even if they weren't otherwise selected by the user. There are a number of strategies for dealing with this, from dynamically filtering them on the client, to creating a custom model binder that first validates the Products_[i]__isSelected attribute. The easiest, though, is to simply allow all of them to be bound to your binding model, and then filter them prior to any processing using e.g. LINQ:
var selectedProducts = c.Products.Where(p => p.IsSelected).ToList();
…
repo.SetProducts(selectedProducts);
For the 1st question, you can try different things. When you do the ajax call, you get a list of models. For each of these models, add the selected company ID as a property. So you don't have to worry about the index being something unique.
As for the 2nd question, should be a relatively easy thing to do. However, more information is needed.
1. When the save button is hit, are you doing a full postback? or it is also an AJAX call?
2. Why do you not want to opt for a AJAX call to do the update as well? So you can based upon the response, redirect the user to a results page, etc.
If you can create a small sample in a new project, and upload to github, and post the information here. I should be able to take a look and understand better. I will definitely be able to help.
Also try reading this thread, it might help
how to persist partial view model data during postback in asp.net mvc

textarea asp-for doesn't display property

I've been working on a small school project and decided to use .net core (MVC) for the first time. I have a small button I can click which executes the "ipconfig" command in the background and displays the output in a text area. At first my team partner used only a
public string Result;
in ViewModel for the view. In the view it's displayed via
<textarea asp-for="Result"></textarea>
So I decided to make it into a property with default get and set:
public string Result { get; set; }
But when I do that, the output doesn't show up in the textarea if I keep the same approach in the view as my team member did when he used a field instead of a property. Instead I have to do it like this to get it to show up in the textarea:
<textarea>#Model.Result</textarea>
Now I'm asking myself why this happens. Can't I display Properties with asp-for? And what would be better to use, a field or a property as Result?
Many thanks in advance!
In my case this behavior was happening because I did not have a separate closing tag on my textarea. I know your example above has it when using the asp-for method, but in case that was a copy/paste error, you may want to look at that again.
<textarea asp-for="Message" type="text" class="form-control" rows="5" id="MessageInputField" autofocus></textarea>
As opposed to:
<textarea asp-for="Message" type="text" class="form-control" rows="5" id="MessageInputField" autofocus />
This is incredibly annoying 'gotcha' like behavior in my opinion. And maybe if someone wants to tell me why it isn't, then please enlighten me, I'm always willing to learn.
You should actually be using properties, and if <textarea>#Model.Result</textarea> works, <textarea asp-for="Result"></textarea> should as well. The only reason it wouldn't is if the ModelState has a different value for Result (as the latter form actually uses ModelState, whereas the former is obviously using Model directly).
ModelState is composed of values from Request, ViewData/ViewBag, and finally Model, so for example, if you were to do something like ViewBag.Result = String.Empty, that would actually override the value from Model in ModelState.
There's no enough code here to truly diagnose the exact issue, but I would look for other places you might be using "result" (case-insensitive). If you're accepting that as a request param or setting a ViewBag member, etc. that's your problem.
I find this is a bit weird in .Net Core 6, when I want two-way binding - ie displaying the result
With asp-for I have to self close the tag AND add the additional textarea close tag to get the result displayed.
<textarea asp-for="Result" />#Model.Result</textarea>
This works but is not syntactically correct.
Without the self-close tag, the result was not being display
<textarea asp-for="Result">#Model.Result</textarea> #*Does not display the value*#
For display only, this is more correct
<textarea>#Model.Result</textarea>

Disable MVC "must be a number" data type validation for a field

I have a Telerik MVC ComboBox which contains a list of locations. The client wants the end user to be able to directly enter new locations into the list.
Upon submitting the form, it should accept a new value and insert into the locations table, and of course update the LocationID of the record being added to the ID of the newly inserted location.
Read below for code snippets
I've read that the ComboBox does allow you to enter in values that are not in the list, and used the demo (Here)
Code to save location, edit locationID is not a problem. My problem here is that my Combo box contains a list of integer/string value pairs, not string/string. And thus, the issue I have in my code is that if I try and submit a new location name, it tries to validate it and says its not a number.
I need a way to try and suppress this validation, just for the LocationID field, but still protect against null values.
BEGIN EDITS
EDIT: I did find this post, but as the OP there says, the javascript hack is not very extensible, so I really want to avoid it.
EDIT:
I ended up using the javascript hack, its all I've found which works. I plan to encapsulate this in a method or attribute, and post it as an answer.
I found that with this hack, it did not work in Firefox or Chrome if I place the code block inside the document ready event using Telerik().ScriptRegistrar().OnDocumentReady(), the properly window.mvcClientValidationmetadata is somehow cleared when it reaches this event, even though the metadata are properly pushed in originally.
To get around this, I had to manually put the code in its own script block just under the form closing tag (which is where the client-side validation array is rendered).
END EDITS
Additionally, right now I'm binding to my model directly like so:
public JsonResult Create(MyEntity Model)
I'm not sure how that's going to work out when it comes time to do model binding, I'm guessing it may just return an error, and I won't even reach my action method code.
So I guess the idea here would be to use a FormCollection in the method signature, detect for a non-integer LocationID, insert update, and then run UpdateModel()? Of course, welcome to better suggestions.
Thanks!
Code Snippets
Model:
class IntegerValueList
{
public Int16 ID { get; set; }
public string Name { get; set; }
}
var lists = new Dictionary<string, IEnumerable<object>>();
lists["Locations"] = (from record in db.Locations
orderby record.Name
select new IntegerValueList
{
ID = record.LocationID,
Name = record.Name
}).ToList();
Controller:
LocationList = new SelectList(lists["Locations"], "ID", "Name", LocationID);
View:
<td>
<div class="editor-field">
<%: Html.Telerik().ComboBoxFor(model => model.LocationID)
.BindTo(Model.LocationList)
.Filterable(c => c.FilterMode(AutoCompleteFilterMode.Contains))
%>
<%: Html.ValidationMessageFor(model => model.LocationID, "*") %>
</div>
</td>
When I've had to do this in the past, I have used a textbox for the field with a label like "Enter Location", and then a dropdown below it with a label like "Or select...", and then use the onChange event of the dropdown to populate the textbox. Now you are not validating the dropdown.
You could even put a "Select" link next to the textbox and have the dropdown hidden until the link is clicked. The drawbback is that we could end up with San Diego, SanDiego and Sandiego all entered in the location list...but then your customer's requirement doesn't let us explicitly prevent that.

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