Search code examples
javascriptasp.netasp.net-mvcasp.net-corerazor

Append form field with model inside for loop (ASP.NET Razor Views)


This is my first question here at stackoverflow. Hehehe.

I have an edit page which I need to display all the existing fields and I have an add button which I can add a new field.

Here is the source code for edit in which where the displaying of data happens (Edit.cshtml)

                                <div class="nutrition-field">
                            @for (int i = 0; i < Model.NutritionFacts.Count(); i++ )
                            {
                                <div class="nutrition-div form-group row">
                                    <div class="col-lg-4">
                                        <label>Nutrients name</label>
                                        <input type="text" class="nutrients-name form-control" asp-for="@Model.NutritionFacts.ToList()[i].NutritionName">
                                        <span asp-validation-for="@Model.NutritionFacts.ToList()[i].NutritionName" class="text-danger"></span>
                                    </div>                                  
                                    <div class="col-lg-4">
                                        <label>Per 100g</label>
                                        <input type="text" class="gram form-control" asp-for="@Model.NutritionFacts.ToList()[i].Gram">
                                        <span asp-validation-for="@Model.NutritionFacts.ToList()[i].Gram" class="text-danger"></span>
                                    </div>                                  
                                    <div class="col-lg-4">
                                        <label>% Daily value</label>
                                        <div class="d-flex">
                                            <input type="text" class="percentage-value form-control mr-5 w-50" asp-for="@Model.NutritionFacts.ToList()[i].PercentageDailyValue">
                                            <span asp-validation-for="@Model.NutritionFacts.ToList()[i].PercentageDailyValue" class="text-danger"></span>
                                            <a href="javascript:void(0)" class="delete-btn btn btn-fixed-height btn-secondary font-weight-bold font-size-sm px-8">
                                                Delete
                                            </a>
                                        </div>
                                    </div>
                                </div>
                            }   
                            </div>

And here is the javascript for the add button:

            $(addNutrition).click(function() {
                $.ajax({
                url: "/Product/AddNutrition",
                cache: false,
                success: function (html) {
                    $('.nutrition-field').append(html)
                }
            });
        });

When you click the button it will go to the controller and here is the code for the Add Nutrition Controller:

       public IActionResult AddNutrition()
    {
        return PartialView("~/Views/Product/PartialViews/_AddNutrition.cshtml", new ProductNutritionFactsDto());
    }

Here is the Partial View:

@model ProjectName.Dto.ProductNutritionFactsDto

<div class="nutrition-div form-group row">
    <div class="col-lg-4">
        <label>Nutrients name</label>
        <input type="text" class="nutrients-name form-control" asp-for="NutritionName">
        <span asp-validation-for="NutritionName" class="text-danger"></span>
    </div>                                  
    <div class="col-lg-4">
        <label>Per 100g</label>
        <input type="text" class="gram form-control" asp-for="Gram">
        <span asp-validation-for="Gram" class="text-danger"></span>
    </div>                                  
    <div class="col-lg-4">
        <label>% Daily value</label>
        <div class="d-flex">
            <input type="text" class="percentage-value form-control mr-5 w-50" asp-for="PercentageDailyValue">
            <span asp-validation-for="PercentageDailyValue" class="text-danger"></span>
            <a href="javascript:void(0)" class="delete-btn btn btn-fixed-height btn-secondary font-weight-bold font-size-sm px-8">
                Delete
            </a>
        </div>
    </div>
</div>

It's working but I wanted it to be inside the for loop so the counting of index will continue and it will have a unique ID. What is happening is it is creating a new model and the validations are not working when adding many fields.

I would really appreciate your help everyone. Thank you!


Solution

  • I saw you use @Model.NutritionFacts.ToList()[i].PropertyName in main view and ProductNutritionFactsDto in partial view. Assume that your main view accept a model contains nested List<ChildModel> called ProductNutritionFactsDto.

    For your scenario, you need pass the count to ajax and use ViewBag to store the count and use it in the partial view:

    Main View:

    model TestViewModel
    <button id="addNutrition" type="button">Add</button>
    <form method="post">
        <div class="nutrition-field">
            @for (int i = 0; i < Model.NutritionFacts.Count(); i++)
            {
                <div class="nutrition-div form-group row">
                    <div class="col-lg-4">
                        <label>Nutrients name</label>
                        <input type="text" class="nutrients-name form-control" asp-for="@Model.NutritionFacts.ToList()[i].NutritionName">
                        <span asp-validation-for="@Model.NutritionFacts.ToList()[i].NutritionName" class="text-danger"></span>
                    </div>
                    <div class="col-lg-4">
                        <label>Per 100g</label>
                        <input type="text" class="gram form-control" asp-for="@Model.NutritionFacts.ToList()[i].Gram">
                        <span asp-validation-for="@Model.NutritionFacts.ToList()[i].Gram" class="text-danger"></span>
                    </div>
                    <div class="col-lg-4">
                        <label>% Daily value</label>
                        <div class="d-flex">
                            <input type="text" class="percentage-value form-control mr-5 w-50" asp-for="@Model.NutritionFacts.ToList()[i].PercentageDailyValue">
                            <span asp-validation-for="@Model.NutritionFacts.ToList()[i].PercentageDailyValue" class="text-danger"></span>
                            <a href="javascript:void(0)" class="delete-btn btn btn-fixed-height btn-secondary font-weight-bold font-size-sm px-8">
                                Delete
                            </a>
                        </div>
                    </div>
                </div>
            }
        </div>
        <input type="submit" value="post" />
    </form>
    
    @section Scripts
    {
    <script>
        $("#addNutrition").click(function () {
            $.ajax({
                url: "/Product/AddNutrition?index=" + $(".nutrients-name").length,
                cache: false,
                success: function (html) {
                    $('.nutrition-field').append(html)
                }
            });
        });
    </script>
    }
    

    Controller:

    public IActionResult AddNutrition(int index)
    {
        ViewBag.Index = index;
        return PartialView("~/Views/Product/PartialViews/_AddNutrition.cshtml", new ProductNutritionFactsDto());
    }
    

    Partial View:

    Get the ViewBag.Index and add name attribute with this index.

    @model ProductNutritionFactsDto
    @{ 
        var i = (int)ViewBag.Index;
    }
    <div class="nutrition-div form-group row">
        <div class="col-lg-4">
            <label>Nutrients name</label>
            <input type="text" class="nutrients-name form-control" asp-for="NutritionName" name="[@i].NutritionName">
            <span asp-validation-for="NutritionName" class="text-danger"></span>
        </div>
        <div class="col-lg-4">
            <label>Per 100g</label>
            <input type="text" class="gram form-control" asp-for="Gram"  name="[@i].Gram">
            <span asp-validation-for="Gram" class="text-danger"></span>
        </div>
        <div class="col-lg-4">
            <label>% Daily value</label>
            <div class="d-flex">
                <input type="text" class="percentage-value form-control mr-5 w-50" asp-for="PercentageDailyValue" name="[@i].PercentageDailyValue">
                <span asp-validation-for="PercentageDailyValue" class="text-danger"></span>
                <a href="javascript:void(0)" class="delete-btn btn btn-fixed-height btn-secondary font-weight-bold font-size-sm px-8">
                    Delete
                </a>
            </div>
        </div>
    </div>
    

    Testing model:

    public class TestViewModel
    {
        public List<ProductNutritionFactsDto> NutritionFacts { get; set; }
    }
    public class ProductNutritionFactsDto
    {
        [Required]
        public string NutritionName { get; set; }
        public string Gram { get; set; }
        public string PercentageDailyValue { get; set; }
    }
    

    Besides, notes for further coding(about posting the data to backend):

    asp-for="@Model.NutritionFacts.ToList()[i].PropertyName" will generate the name attribute: name="[i].PropertyName", so if you want to post all the ProductNutritionFactsDto models in the page, be sure the backend parameter should be List<ProductNutritionFactsDto>.

    [HttpPost]
    public IActionResult Index(List<ProductNutritionFactsDto> model)
    {
        if(ModelState.IsValid)
        {
           //....
        }
        return View(new TestViewModel() { NutritionFacts=model});
    }
    

    If you just want to pass any one ProductNutritionFactsDto model in the page separately, be sure add name attribute(name="PropertyName") to override the asp-for generated name and the backend parameter should be ProductNutritionFactsDto.