So here's my situation.
Let's say I have a view called TheView.cshtml.
TheView.cshtml
has a ViewModel called TheViewModel.cs
. In TheViewModel.cs
, resides a List of an object (TheObject
) called TheObjectList
.
I have an editor template for TheObject
called TheObject.cshtml
. Using this editor template, I can simply display all of the items in the TheObjectList
with @Html.EditorFor(model => model.TheObjectList)
.
However, now I want to add objects to this list dynamically. I have an AJAX function, which calls a simple partial view to give the user a blank row to add a new "TheObject
", however, any new TheObject
I add dynamically is not considered part of the original TheObjectList
.
This is because each item in the original TheObjectList
is created with a certain prefix based on its index in the original list, whereas each new dynamic TheObject is created without a prefix, thus Razor does not see it as part of the list.
Is there a way around this?
TheView.cshtml
@model Models.ViewModels.TheViewModel
<table id="Table">
<tbody>
@Html.EditorFor(m => m.TheObjectList);
</tbody>
</table>
<button id="AddObject" type="button" class="btn btn-primary">Add Object</button>
TheViewModel.cs
public class TheViewModel
{
public List<TheObject> TheObjectList { get; set; }
}
AddObject Controller Method
public IActionResult AddObject()
{
return PartialView("_EmptyRow", new TheObject());
}
AJAX Method to Add Object
$('#AddObject').click(function () {
$.ajax({
url: 'AddObject',
cache: false,
success: function (data) {
$('#Table > tbody').append(data);
},
error: function (a, b, c) {
alert(a + " " + b + " " + c);
}
});
});
You basically needs to generate/return markup which looks same as what the editor template generates for your form fields except for the element index . You need to pass the index from client side which will be a part of the form field name.
Let's assume your editor template looks like below and your TheObject has a GroupName
property
@model TheObject
<div>
@Html.HiddenFor(x => x.GroupName)
@Html.TextBoxFor(s=>s.GroupName,new {@class="thingRow"})
</div>
Now when you render your page with your current code, Editor template will generate input fields like this
<input class="thingRow" id="TheObjectList_0__GroupName"
name="TheObjectList[0].GroupName" type="text" value="Something">
where 0
will be replaced with the index of the items in your TheObjectList
collection.
Now let's say you already have 5 items in the collection, So when user clicks add button, you want to generate markup like above except 0
will be replaced with 5
(for the sixth item). So let's update the ajax call to include the current number of items.
$('#AddObject').click(function () {
var i = $(".thingRow").length;
$.ajax({
url: 'AddObject?index=' + i,
success: function (data) {
$('#Table > tbody').append(data);
},
error: function (a, b, c) {
console.log(a, b, c);
}
});
});
That means, we need to accept the index value in our action method. Since we need to pass this index value from action method to our view to build the input field name value, I added a property to your class called Index
public ActionResult AddObject(int index)
{
return PartialView("_EmptyRow", new TheObject { Index = index});
}
Now in your _EmptyRow partial view,
@model TheObject
<input id="TheObjectList_@(Model.Index)__GroupName"class="thingRow"
name="TheObjectList[@(Model.Index)].GroupName" type="text" value=""/>
Now when you submit the form, model binding will work for these dynamically added items, assuming you have your Table inside a form.
@model TheViewModel
@using (Html.BeginForm())
{
<table id="Table">
<tbody>
@Html.EditorFor(m => m.TheObjectList);
</tbody>
</table>
<button id="AddObject" type="button" class="btn btn-primary">Add Object</button>
<button type="submit" class="btn btn-primary">Save</button>
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With