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>    
Prasad Akmar
  • 145
  • 1
  • 2
  • 10
user4523894
  • 143
  • 1
  • 1
  • 10
  • 1
    I previously gave you [this link](http://stackoverflow.com/questions/28019793/submit-same-partial-view-called-multiple-times-data-to-controller/28081308#28081308) showing 2 possible methods. What do you not understand? –  Mar 13 '15 at 09:06
  • @StephenMuecke Need I use javascript to dynamically create indexed inputs for post back? – user4523894 Mar 13 '15 at 09: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. –  Mar 13 '15 at 09:19
  • @StephenMuecke. Ok. I understand. I want to use jquery for this purpose, but is there more nice method for it than [this](http://stackoverflow.com/questions/26542509/validate-dynamically-added-fields/26542591#26542591) – user4523894 Mar 13 '15 at 09: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) –  Mar 13 '15 at 09:32
  • @StephenMuecke and if I use BeginCollectionItem, Can I add new row in empty table using ajax to call a method that returns me some data for displaying? – user4523894 Mar 13 '15 at 09:44
  • Yes. The partial just need to be based on the `NorthOrderDetails` class. –  Mar 13 '15 at 09:48
  • @StephenMuecke But I use NorthOrder' class in this view. And this class contains collection of NorthOrderDetails. Can I use and NortOrder, and NorthOrderDetails classes for one view? – user4523894 Mar 13 '15 at 10:20
  • The main view uses `NorthOrder` but the partial uses `NorthOrderDetails`. Have a look at the link in my first comment and try it out. If you still having problems understanding, I will post an answer tomorrow. –  Mar 13 '15 at 10:29
  • @StephenMuecke, I use BeginCollectionItem. I will wait your answer which u want to write – user4523894 Mar 13 '15 at 20:39

1 Answers1

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.

Community
  • 1
  • 1
  • Some problems appeared. unfortunately – user4523894 Mar 19 '15 at 08:12
  • 2
    +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 `$('').html(response).appendTo(tableBody);` to get this rendering as expected. – Matt Griffiths Mar 08 '17 at 15:20