From what I understand, I have an indexation problem. Let's start with the actual code (this is a fragment from the whole page code, but other fragments, which are similar to this, also fail to work):
The cshtml code that creates Generic Object partial views.
<div id="genericObjects">
@if (Model.GenericObjects != null && Model.GenericObjects.Any())
{
for (int i = 0; i < Model.GenericObjects.Count; i++)
{
{ Html.RenderPartial("_GenericObject", Model, new ViewDataDictionary(this.ViewData) { { "GenericIndex", i } }); }
}
}
</div>
The _GenericObject partial view:
model Noodle.Presentation.Models.MashupViewModel
@{
var f = Html.Bootstrap().Misc().GetBuilderFor(new Form().Type(FormType.Horizontal).LabelWidthMd(3));
var index = (int)ViewBag.GenericIndex;
}
<div class="row generic">
<h4>
<span>Object</span>
@Html.Bootstrap().Button().Class("remove-generic pull-right").PrependIcon("glyphicon glyphicon-trash").Text("")
</h4>
@f.FormGroup().TextBoxFor(s => Model.GenericObjects[index].Name).Label().ShowRequiredStar(false)
@f.FormGroup().TextBoxFor(s => Model.GenericObjects[index].ForeignId).Label().ShowRequiredStar(false)
@f.FormGroup().TextBoxFor(s => Model.GenericObjects[index].Value).Label().ShowRequiredStar(false)
</div>
The GetGenericObject method for invoking the partial view:
public PartialViewResult GetGenericObject(int? index)
{
ViewBag.GenericIndex = index;
return PartialView("_GenericObject");
}
Here are the addition (works fine) and deletion jquery methods:
$('#mashup-form').on('click', '.add-generic-object-btn', function () {
var index = $('#genericObjects').children().length;
$('<div>').load(genericObjPath + '?index=' + index, function () {
$('#genericObjects').append($(this).find('.generic')[0].outerHTML);
revalidate();
});
});
AND
$('#mashup-form').on('click', '.remove-generic', function () {
$(this).parent().parent().remove();
});
As for the actual problem, let's say we have three generic objects:
TEST1 TEST2 TEST3
If TEST2 is deleted, the generic objects that come after it are not submitted (although they do appear on the page).
Essentially, what remains on page:
TEST1, TEST3
What is submitted in the model:
TEST1
The rest of the code works just fine. There is no real point in showing the c# method call on submit, as it works perfectly, but receives a model, that does not have the TEST3 object in it.
Now, this seems to be an indexation problem, which I tried to fix by altering the code like this (the idea was taken from another StackOverflow thread [delete table row dynamically using jQuery in asp.net mvc):
$('#mashup-form').on('click', '.remove-generic', function () {
$(this).parent().parent().remove();
var index = 0;
var itemIndex = 0;
$('#genericObjects').each(function () {
var this_row = $(this);
this_row.find('input[name$=".Name"]').attr('name', 'Model.GenericObjects[' + itemIndex + '].Name');
this_row.find('input[name$=".ForeignId"]').attr('name', 'Model.GenericObjects[' + itemIndex + '].ForeignId');
this_row.find('input[name$=".Value"]').attr('name', 'Model.GenericObjects[' + itemIndex + '].Value');
itemIndex++;
});
});
The result was rather bizarre. Again, if we have three Objects TEST1 TEST2 TEST3, by deleting TEST1 and submitting, I got only TEST2 and by only I mean the entire model was wiped, but this object (the model has more objects, this is just a fragment of the page). Also, if TEST2 is deleted, TEST1 is the only object in the view model (everything else is null). If the delete button on the generic objects is not pressed, everything works just fine.
So, if you have any ideas on how to fix this, please tell them. Also, if there is some other info or code you might need, feel free to ask. Admittedly, I might not be able to give it, but I will see what can be done. Have a good one!
AFTER SOLVING ISSUE EDIT:
I marked the answer below, which involves using EditorFor, as the accepted answer, because I find it is well written and could be useful to somebody, who reads this question and cannot make the partial views work at all. However, in my case, I could make the previous system work. I made a very basic stupid mistake, by only specifying the element outside the partial view. So this:
$('#mashup-form').on('click', '.remove-generic', function () {
$(this).parent().parent().remove();
var index = 0;
var itemIndex = 0;
$('#genericObjects').each(function () {
var this_row = $(this);
this_row.find('input[name$=".Name"]').attr('name', 'Model.GenericObjects[' + itemIndex + '].Name');
this_row.find('input[name$=".ForeignId"]').attr('name', 'Model.GenericObjects[' + itemIndex + '].ForeignId');
this_row.find('input[name$=".Value"]').attr('name', 'Model.GenericObjects[' + itemIndex + '].Value');
itemIndex++;
});
});
Essentially became this:
$('#mashup-form').on('click', '.remove-generic', function () {
$(this).parent().parent().remove();
var index = 0;
var itemIndex = 0;
$('#genericObjects div.row.generic div.col-md-12').each(function () {
var this_row = $(this);
this_row.find('input[name$=".Name"]').attr('name', 'GenericObjects[' + itemIndex + '].Name');
this_row.find('input[name$=".ForeignId"]').attr('name', 'GenericObjects[' + itemIndex + '].ForeignId');
this_row.find('input[name$=".Value"]').attr('name', 'GenericObjects[' + itemIndex + '].Value');
itemIndex++;
});
});
As for the nested objects, the indexation approach is very similar to the one in the accepted answer (in EditorFor system), but they have names. So, instead of [0].1.TESTAs[3], you would have TESTCs[0].TESTBs1.TESTAs[3] (this is for an object class TESTC, which containts TESTB class objects, which in turn contain TESTA class objects).
Sadly, I probably can't show the code I made for resetting the indexes (I think it was a rather nice reusable method), but I can outline the core concept. First of all, as long as .each refers to every HTML element created on the necessary, you only have to specify the root of the elements and just use .find, to find every input by their name and change the necessary index in them (you don't have to navigate to them, since you will want to reset all of them anyway).
As for the resetting, I was already using jquery, so I just used the .attr .replace method. The replacing was done via a regex, which I can hopefully somewhat share.
For the first hierarchical level, use this:
(.*?\[)(.*?)\]
and the string to replace by should be "$1" + index + "]"
Level 2 is simply copy paste of the first one:
(.*?\[)(.*?\[)(.*?)\], where (.*?\[) is a single instance of symbols till [
and the string to replace by should be "$1$2" + index + "]"
Further levels are achieved by just copying the (.*?[) whatever amount of times needed and adding accordingly "$1$2$3... group identifiers in the replacement string. This also seems pretty universal (doesn't matter what the object names actually are). You will of course need to specify all the input field names though. Have a good one. Hope this maybe helps someone somehow.
AFTER EDIT 2: I noticed in the comment by Stephen Muecke a rather nice way of dealing with the problem as well. In this particular situation I didn't use it though, as I want to avoid modifying the core system as much as possible. However, I would advise you to have a look at this solution, since this ultimately can make you avoid doing any index resetting whatsoever.