Jeff Finn
Jeff Finn

Reputation: 2055

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

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

Upvotes: 0

Views: 2457

Answers (2)

user1672994
user1672994

Reputation: 10849

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.

Upvotes: 1

Chris Pratt
Chris Pratt

Reputation: 239470

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].

Upvotes: 1

Related Questions