9

I have this table and shows values from the server. How I can with jquery add a new row in this table if I want to send values from the table to the Server

<table id="Products" class="Products">
    <tr>
        <th>ProductId</th>
        <th>Productname</th>
        <th>Quantity</th>
        <th>UnitPrice</th>
    </tr>

    @for (int i = 0; i < Model.NorthOrderDetails.Count; i++)
    {
        <tr>
            @Html.HiddenFor(m => m.NorthOrderDetails[i].ProductID)
            @Html.HiddenFor(m => m.NorthOrderDetails[i].ProductName)
            <td>@Html.DisplayFor(m => m.NorthOrderDetails[i].ProductID)</td>

            <td>@Html.DisplayFor(m => m.NorthOrderDetails[i].ProductName)</td>

            <td><input type="hidden" name="NorthOrderDetails.Index" value="@i" /> </td>

            <td> @Html.TextBoxFor(m => m.NorthOrderDetails[i].Quantity) </td>

            <td> @Html.TextBoxFor(m => m.NorthOrderDetails[i].UnitPrice, String.Format("{0}", Model.NorthOrderDetails[i].UnitPrice))</td>
            <td>
                <button type="button" class="delete" data-id="@Model.NorthOrderDetails[i].ProductID">Delete</button></td>
        </tr>
    }
</table>    
10
  • 1
    I previously gave you this link showing 2 possible methods. What do you not understand? Commented Mar 13, 2015 at 9:06
  • @StephenMuecke Need I use javascript to dynamically create indexed inputs for post back? Commented Mar 13, 2015 at 9:14
  • You could post back the form, save everything, redirect to the GET method, add a new item to the collection and recreate the view all over again, but you will get far better performance if you use javascript to add the element to the DOM. Commented Mar 13, 2015 at 9:19
  • @StephenMuecke. Ok. I understand. I want to use jquery for this purpose, but is there more nice method for it than this Commented Mar 13, 2015 at 9:21
  • Look at option 1 in link in my first comment. Using ajax to call a method that returns a partial in conjunction with BeginCollectionItem is as easy as it gets (and it means you can simply your main view) Commented Mar 13, 2015 at 9:32

1 Answer 1

20

You can use the BeginCollectionItem html helper a partial view for your model to generate collection items with unique indexers and allow binding to the collection on post back. Note you have not indicate the type of property NorthOrderDetails, but form your previous questions, I assume its NorthOrderDetails.cs

Create a partial for NorthOrderDetails.cs

Views/YourController/_NorthOrderDetails.cshtml

@model NorthOrderDetailscs
@using(Html.BeginCollectionItem()) 
{
  <tr>
    @Html.HiddenFor(m => m.ProductName)
    <td>@Html.DisplayFor(m => m.ProductID)</td>
    <td>@Html.DisplayFor(m => m.ProductName)</td>
    <td>@Html.TextBoxFor(m => m.Quantity)</td>
    <td>@Html.TextBoxFor(m => m.UnitPrice)</td>
    <td>
      <button type="button" class="delete" data-id="@Model.ProductID">Delete</button>
      @Html.HiddenFor(m => m.ProductID)
      @Html.HiddenFor(m => m.ProductName)
    </td>
  </tr>
}

Side notes:

  1. Do not include the hidden input for the Index
  2. An <input> is not a valid child of a <tr> element (place the hidden inputs inside a <td> element
  3. String.Format("{0}", Model.NorthOrderDetails[i].UnitPrice does absolutely nothing (If you wanted to to format it as (say) currency, then it would be @Html.TextBoxFor(m => m.UnitPrice. "{0:C}"))

You can now remove the for loop in the main view and replace with

<table id="Products" class="Products">
  <thead>
    <tr>
      <th>ProductId</th>
      <th>Productname</th>
      <th>Quantity</th>
      <th>UnitPrice</th>
    </tr>
  </thead>
  <tbody>
    @foreach(var item in Model.NorthOrderDetails)
    {
      @Html.Partial("_NorthOrderDetails", item)
    }
  </tbody>
</table>

This will generate a bit of extra html (the indexers for the existing items are a Guid rather than an int) but it means you only have one place to maintain your code.

Then in the main view, add the following script for your 'Add' button

<button type="button" id="add">Add</button>
<script>
  var tableBody = $('#Products tbody');
  var url = '@Url.Action("Add", "YourControllerName")'; // adjust to suit
  $('#add').click(function() {
    $.get('url, function(response) {
      tableBody.append(response);
    });
  });
</script>

And the controller method

public PartialViewResult Add()
{
  var model = new NorthOrderDetailscs();
  return PartialView("_NorthOrderDetails");
}

Finally, if you are using client side validation (jquery-validate-unobtrusive.js), and @Html.ValidationMessageFor() with your properties, then you need re-parse the validator each time you add new elements to the DOM as explained in this answer.

Sign up to request clarification or add additional context in comments.

2 Comments

Some problems appeared. unfortunately
+1 Thanks - this helped immensely for a project I'm working on. A few snags I run into: 1) I had to change Html.BeginCollectionItem() to Html.BeginCollectionItem("PropertyNameOfList"). 2) Appending directly to the tableBody as you have done dropped the tr wrapper. I had to use a variant of $('<tr />').html(response).appendTo(tableBody); to get this rendering as expected.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.