MVC How can I dynamicaly add input fields to a view model

171
November 23, 2016, at 4:39 PM

Hi I have a form that uses the following viewmodel

    public class MainViewModel
    {
        public int Id { get; set; }
        public List<LotViewModel> Lots { get; set; }
        [DisplayName("Number of Lots")]
        public int NumberOfFields { get; set; }
    }

NumberOfFields is used in a for loop to render LotViewModels. The user selects a number from 1 -> 4 from a drop down. There is a JS function that then uses AJAX that calls a Controller method to render a partial view containing the correct number of fields. This JS function is triggered when the drop down changes.

This is the controller method

[HttpPost]
public PartialViewResult AddLotFields(int numberOfFields)
{
    var viewModel = new MainViewModel
    {
        Lots = new List<LotViewModel>(),
        NumberOfFields = numberOfFields
    };
    return PartialView("~/Areas/Admin/Views/MainController/_LotForm.cshtml", viewModel);
}

And this is the Javascript

$(function() {
$("#numberOfLotsDD").change(function(event) {
    $.ajax({
        url: window.g_baseUrl + "MainController/AddLotFields/",
        data: {
            numberOfFields: $(this).val()
        },
        cache: false,
        type: "POST",
        dataType: "html",
        success: function(data, textStatus, XMLHttpRequest) {
            $("#LotFields").html(data);
        }
    });
});
});

This works fine but when you submit the form the viewModel doesn't contain any elements in Lots.

The partial view that renders the input fields is

@model MainViewModel
@for (var i = 0; i < Model.NumberOfFields; i++)
{
    Model.Lots.Add(new LotViewModel());
    <div class="row">
        <div class="col-md-6">
            <div class="form-group ">
                @Html.LabelFor(model => model.Lots.Last().BatchIdLocation)
                @Html.TextBoxFor(model => model.Lots.Last().BatchIdLocation, new { @class = "form-control", @placeholder = "Enter Batch Id tag" })
                @Html.ValidationMessageFor(model => model.Lots.Last().BatchIdLocation)
            </div>
        </div>
        <div class="col-md-6">
            <div class="form-group ">
                @Html.LabelFor(model => Model.Lots.Last().QuantityLocation)
                @Html.TextBoxFor(model => Model.Lots.Last().QuantityLocation, new { @class = "form-control", @placeholder = "Enter Quantity tag" })
                @Html.ValidationMessageFor(model => Model.Lots.Last().QuantityLocation)
            </div>
        </div>  
    </div>
}

I've messed around with a few ways of trying to save this form but none of it has worked.

I know if I'm really stuck I could use JQuery to send the form input values to my controller. But I would like to avoid that if possible and keep the solution confined to c# and razor

Answer 1

Your field names need to be in the form of Lots[N].PropertyName, but your field names are being generated as just PropertyName.

Instead of using model.Lots.Last(), use the actual index, i.e. model.Lots[i].

Answer 2

Suggestion - You can create the items in your collection at server side itselft instead of passing a flag to view to generate numbers of items.

[HttpPost]
public PartialViewResult AddLotFields(int numberOfFields)
{
    var lots = new List<LotViewModel>();
    for (int index = 0; index < numberOfFields; index++)
    {
        lots.Add(new LotViewModel());
    }
    return PartialView("~/Areas/Admin/Views/MainController/_LotForm.cshtml", lots);
}

This will also give you ability to initialize the view model properties with external factor dependent values.

So the changes in view would be as follows :

@model List<LotViewModel>
@for (var i = 0; i < Model.Count; i++)
{
    <div class="row">
        <div class="col-md-6">
            <div class="form-group ">
                @Html.LabelFor(model => model.Lots[i].BatchIdLocation)
                @Html.TextBoxFor(model => model.Lots[i].BatchIdLocation, new { @class = "form-control", @placeholder = "Enter Batch Id tag" })
                @Html.ValidationMessageFor(model => model.Lots[i].BatchIdLocation)
            </div>
        </div>
        <div class="col-md-6">
            <div class="form-group ">
                @Html.LabelFor(model => model.Lots[i].QuantityLocation)
                @Html.TextBoxFor(model => model.Lots[i].QuantityLocation, new { @class = "form-control", @placeholder = "Enter Quantity tag" })
                @Html.ValidationMessageFor(model => model.Lots[i].QuantityLocation)
            </div>
        </div>  
    </div>
}

The above change will also create the appropriate name at html for each field so that at form post, data post appropriately.

READ ALSO
Confirm Popup in ASP.NET with Yes No

Confirm Popup in ASP.NET with Yes No

I need a confirm popup which shows Yes and No.

213
scrollTop a div when user starts scrolling, and then let users free to scroll

scrollTop a div when user starts scrolling, and then let users free to scroll

I have 2 divs on a webpage. [nnn]First div is #pattern, and the other div is #top.

220
Vertically fixed slider to content below

Vertically fixed slider to content below

I have an image slider on top of my website. It's here on my hosting page with top slider.

212