How to add an item to a list in a ViewModel using Razor and .NET Core?
Asked Answered
B

1

16

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);
        }
     });
});
Bybee answered 30/3, 2016 at 18:54 Comment(5)
How are you adding the result of AddObject to the existing form ?Saransarangi
@Saransarangi I've updated the OP with the code. It's an AJAX method which adds the empty row as a table rowBybee
Refer also the answers here and hereSalutation
@StephenMuecke Unfortunately "BeginCollectionItem" is not supported in DNX Core :( Otherwise this would have saved me a huge headache and a lot of hoursBybee
Then use the client template option (much better performance anyway)Salutation
S
30

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>
}
Saransarangi answered 30/3, 2016 at 19:31 Comment(3)
Oh my god if I could send you a 12 pack of beer I would. I was stuck on this literally all day and your answer is so obvious I can't believe I didn't think of it. Thanks a ton - I wish I could give you more reputation. Thanks again!!Bybee
Hey man one more question - maybe you can help me out here. The add works great, but let's say I want to delete from the list. If I delete one from the middle of the list, every other item after it also gets deletedBybee
For deletion you need to write some javascript, which updates the indexes of all following elements... or as a second option: just add an hidden input element with deleted = true, and process the deletion after the form was submittedNemhauser

© 2022 - 2024 — McMap. All rights reserved.