How to Add new Row Dynamically in ASP.Net MVC 5

2019-05-25 06:10发布

问题:

I am looking for help on how to add a new row of LineItems to an Invoice in a create Razor view of an ASP.Net MVC 5 application. I have read almost all similar questions but none have addressed what I thought was a simple use case.

Here is my Invoice model class

public class Invoice
    {
        public int Id { get; set; }
        public int InvoiceNumber { get; set; }
        public List<LineItem> LineItems { get; set; }
        public Client Customer { get; set; }
        public DateTime DateCreated { get; set; }        
        public decimal Total { get; set; }            


        public Invoice()
        {
            LineItems = new List<LineItem>();
        }

Take note that this invoice contains a List of LineItems and each line Item is a simple object. And a List of line items is created in the Invoice constructor. Here is the LineItem model class

public class LineItem
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string Description { get; set; }
        public int Quantity { get; set; }
        public decimal Price { get; set; }        
        public decimal Total { get; set; }

    }

The generated ASP.Net MVC 5 Razor views did not recognize the LineItems list of the object and did not create any entry for it. I want to dynamically add a row to the table below and I want to make that row an instance of Line items.

Here is the Table showing the invoice

<table class="table table-condensed" id="invoiceTable"> 
      <thead>
         <tr id="invoiceTableHead">
             <td><strong>Item Name</strong></td>
             <td class="text-center"><strong>Item Description</strong></td>
             <td class="text-center"><strong>Item Price</strong></td>
             <td class="text-center"><strong>Item Quantity</strong></td>
             <td class="text-right"><strong>Total</strong></td>
          </tr>
       </thead>

     <tbody>

And here is my attempt at using JQuery to append a row to this table dynamically and I that is where I am stuck, any help or pointers that will be greatly appreciated.

  <script type="text/javascript">
    $("#lineItemButton").click(function () {
        debugger;
        // Create elements dynamically
        var newRow = "<tr><td>'@Html.TextBoxFor(x => x.LineItems, new { ??? What do int public here)'</td></tr>";

        // Add the new dynamic row after the last row
            $('#invoiceTable tr:last').after(newRow);
        });

    </script>

回答1:

I would not do this sort of thing dynamically by modifying the dom in the manner you describe. My preference would be to generate all of the necessary code in the razor view as if it was always there and then simply toggle the visibility of the row itself. This way the textbox is rendered properly as a form element when the view is generated, and you still have full access to modify the table with jQuery pending any AJAX requests.

On a side note, the behavior you're describing makes it sound like you're attempting to add more client side behaviors and reduce the number/size of the round trips to the server. If this is true, I would suggest exploring a JavaScript MVVM framework like Knockout, Ember or Angular.



回答2:

You can create dynamic rows, but from my experience they will not bind to the model. I have a drop down that the user selects an asset number and clicks an 'Add' button that adds a new row, dynamically, to the table.

What I did was create a hidden row in a table to use a template.

   <table class="table table-bordered table-condensed table-hover" id="lineItemTable" name="assetTable">
                <thead>
                    <tr>
                        <th class="text-center">Item #</th>
                        <th class="text-center">Asset</th>
                        <th class="text-center">Condition</th>
                        <th class="text-center">Description 1</th>
                        <th class="text-center">Description 2</th>
                        <th class="text-center">Inventory Num</th>
                        <th class="text-center">Serial Number</th>
                    </tr>
                </thead>
                <tbody>
                    <tr hidden>
                        <td>
                            <label id="row"></label>

                        </td>
                        <td>
                            <input asp-for="TransferLineItem.AssisAsset" class="form-control" value=@ViewBag.AssisAsset />
                        </td>
                        <td>
                            <select asp-for="TransferLineItem.Condition" class="form-control" asp-items="@ViewBag.Conditions"></select>

                        </td>
                        <td>
                            <input asp-for="TransferLineItem.AssetDescription1" class="form-control" value=@ViewBag.AssetDescription1 />
                        </td>
                        <td>
                            <input asp-for="TransferLineItem.AssetDescription2" class="form-control" value=@ViewBag.AssetDescription2 />
                        </td>
                        <td>
                            <input asp-for="TransferLineItem.InventoryNum" class="form-control"  />
                        </td>
                        <td>
                            <input asp-for="TransferLineItem.SerialNumber" class="form-control" value=@ViewBag.SerialNum />
                        </td>
                    </tr>
                </tbody>
            </table>

When the add button is clicked I use jQuery to clone the hidden table row and append the table with the new row. I append the id of each control with '_[row number]' so that each control had a unique id number.

    //clones the first row of the table
var newRow = $("#lineItemTable tbody tr").first().clone();

//removes the 'hidden' attribute so it will be visible when added  to the table
newRow.removeAttr("hidden");

//add/append new row to the table
$("tbody").append(newRow);

//get row number which will be appended to the id of each control in this row
//for example if this were  the second row then the id of the asset field would be something like asset_2.  
//note  that since there is already a hidden row in the table, we subtract 1 from the row number
var rowNum = "_" + ($("#lineItemTable tbody tr").length-1);

//loop through the input controls and add the new id value
newRow.find("input").each(function () {

    // get id of the input control
    var ctrl = $(this).attr("id");

    //concatenate the row number to the id
    var newId = ctrl + rowNum;

    //assign new id to control
    $(this).attr("id", newId);

});

To save the data in the html table, I use jQuery to create an array of name-value pairs for each row, and pass that to a function in the controller.

 //get table
var tbl = document.getElementById("lineItemTable");

//array to hold the json objects
var jsonArray = [];

//iterate through the fields and put values in the json object
for (var i = 1, r = tbl.rows.length-1; i < r; i++)
{

    var jsonObj = {
        asset: $("#TransferLineItem_AssisAsset_" + i).val(),
        condition: $("#TransferLineItem_Condition_" + i).val(),
        assetDescription1: $("#TransferLineItem_AssetDescription1_" + i).val(),
        assetDescription2: $("#TransferLineItem_AssetDescription2_" + i).val(),
        InventoryNum: $("#TransferLineItem_InventoryNum_" + i).val(),
        serialNumber: $("#TransferLineItem_SerialNumber_" + i).val()
    };

    //put json object in array
    jsonArray.push(jsonObj);

}


//pass json array to controller function to save line items
$.ajax({
    type: "GET",
    url: "Create?handler=SaveTransferLineItems",
    contentType: "application/json; charset=utf-8'",
    data: { jsonObj: JSON.stringify(jsonArray) },
    success: function () {
        showModal("btn-success", "Form Saved", "Your new transfer form was successfully saved.");
        },
    failure: function () {
        showModal("btn-danger", "Save Failed", "Your form could not be saved, please contact site support");
      }
});

In the controller function, I convert the name value pairs to a list of type 'TransferLineItem', a bound model. I can iterate over the list and use context to save to the database.

 dynamic _json = JsonConvert.DeserializeObject<List<TransferLineItem>>(jsonObj);

        foreach (TransferLineItem  item in _json)
        {

            try
            {
                _context.TransferLineItem.Add(item);


                int x = await _context.SaveChangesAsync();

                if (x != 1)
                {
                    ModalMessage = "Could not save items, starting at " + TransferLineItem.Asset;
                    return Page();
                }

            }
            catch (Exception ex)
            {
                ModalType = "btn-danger";
                ModalTitle = "Save Failed";
                ModalMessage = ex.Message;
                return Page();
            }


        }