3

I have a table where I add and remove rows dynamically:

@model AHBReports.Models.AdjustmentModel
@using (Html.BeginForm())
{
    <table id="container">
         @Html.EditorFor(model => model.Adjustments)
    </table>
    <div >
        <input type="button" id="btnAdd" value="+ Add" />
        <input type="submit" value="Submit" />
    </div>
}

EditorTemplate:

@model AHBReports.Models.Adjustment
<tr>
    <td>@Html.HiddenFor(x => x.Id, new { @class = "iHidden" })</td>
    <td>@Html.AutocompleteFor(model => model.BRANCH, "GetBranches", "Report700H")</td>
    <td>@Html.EditorFor(model => model.Amount)</td>
    <td><a onclick="removeRow(this)">x</a></td>
</tr>

Script for table manipulation:

<script type="text/javascript">

function removeRow(selector)
{
    if ($('#container tr').length > 1) 
    {
        $(selector).closest('tr').remove();
    }       
}
$(document).ready(function () {       

    $("#btnAdd").click(function (e) {           

        var ind = $("#container tr:last").find("input.iHidden").val();
        var itemIndex = parseInt(ind);
        itemIndex++;
        console.log(itemIndex);
        e.preventDefault();
        var newItem = $("<tr>"+
            "<td><input id='Adjustments_" + itemIndex + "__Id' type='hidden' value='"+itemIndex+"' class='iHidden'  name='Adjustments[" + itemIndex + "].Id' /></td>" +
            "<td><input type='text' id='Adjustments_" + itemIndex + "__BRANCH' name='Adjustments[" + itemIndex + "].BRANCH' data-autocomplete-url='@Url.Action("GetBranches", "Report700H")'/></td>" +
            "<td><input type='text' id='Adjustments_" + itemIndex + "__Amount' name='Adjustments[" + itemIndex + "].Amount'/></td>" +
            "<td><a onclick='removeRow(this)'>x</a></td>" +
            "</tr>");
        $("#container").append(newItem);


    });

});

My add/delete functions work fine visually in my view, as well as when I accept the collection in my POST method:

 public ActionResult Adjust(AdjustmentModel model)
{
       //working with model.Adjustments
}

I receive correct values. However, when I try to delete some row, which is in the middle of the table and then sumbit the form, I receive only elements that were above deleted row, for example:

id  branch amount
0   aaa    500
1   bbb    200
2   ccc    300 --deleted this row
3   ddd    400

Collection receives:

id  branch amount
0   aaa    500
1   bbb    200

So, the last row is missing. What am I doing wrong??

Thanks a lot

Gyuzal
  • 1,581
  • 10
  • 52
  • 99
  • 1
    It works just fine in [fiddle](http://jsfiddle.net/9wf7qu2L/). – Regent Sep 23 '14 at 11:32
  • I believe what Gyuzal is saying is that his JQuery code removes the right row but after the submit to the server any row below the one selected for deletion is also removed right? – Gjohn Sep 23 '14 at 11:34
  • @Gjohn you are right, (and i'm female :)) – Gyuzal Sep 23 '14 at 11:35
  • Well, I didn't notice the `form`. Actually because instead of rendered HTML (which is important and should be shown) there is `@using (Html.BeginForm())` and so on... – Regent Sep 23 '14 at 11:44
  • Well, lets just wrap previous code with `
    `, and it still works: [updated fiddle](http://jsfiddle.net/9wf7qu2L/1/).
    – Regent Sep 23 '14 at 11:51

3 Answers3

5

When the row you deleted it containts model's input and input has name and id based on index.

So when you delete row you have to update input's name and id that are in row after the deleted row.. or regenerate row next all from deleted row with new index name .

Replace your delete function with this one

function removeRow(selector) {
        if ($('#container tr').length > 1) {
            $(selector).closest('tr').remove();
            var itemIndex =0;
            $('#container tr').each(function(){
                var this_row = $(this);
                this_row.find('input[name$=".BRANCH"]').attr('name','Adjustments[' + itemIndex + '].BRANCH');//replace name of input that ends the name BRANCH
                this_row.find('input[name$=".Amount"]').attr('name','Adjustments[' + itemIndex + '].Amount');
                this_row.find('input[name$=".Id"]').attr('name', 'Adjustments[' + itemIndex + '].Id');
                itemIndex ++;
            });
        }
    }
sangram parmar
  • 8,462
  • 2
  • 23
  • 47
2

The indexer for collections must start at zero and be consecutive unless you use an Index property where the value of Index is equal to the indexer. For example

<input ... name="Adjustments[0].ID" ..>
<input ... name="Adjustments[2].ID" ..>

wont post back correctly. But if you add an Index property for the object

<input ... name="Adjustments[0].ID" ..>
<input ... name="Adjustments[0].Branch" ..>
<input ... name="Adjustments[0].Index" value="0"..>

<input ... name="Adjustments[2].ID" ..>
<input ... name="Adjustments[2].Branch" ..>
<input ... name="Adjustments[2].Index" value="2"..>

Then the collection will post back correctly

Since you don't have access to the indexer in the EditorTemplate, you will need to generate the controls in a for loop in the main page

for (int i = 0; i < Model.Adjustments.Count; i++)
{
  var name = string.Format("Adjustments[{0}].Index", i);
  @Html.HiddenFor(m => m[i].ID)
  ....
  <input type=hidden name="@name" value="@i" />
}

You will also need to modify your script to include the hidden input for the Index property. Rather than basing the value of itemIndex on the number of existing rows, base it on a unique value. For example

$("#btnAdd").click(function (e) {
  var itemIndex = (new Date()).getTime();
0

u can give the row an unique id.

var newItem = $("<tr id='row"+itemIndex+"'>"+
            "<td><input id='Adjustments_" + itemIndex + "__Id' type='hidden' value='"+itemIndex+"' class='iHidden'  name='Adjustments[" + itemIndex + "].Id' /></td>" +
            "<td><input type='text' id='Adjustments_" + itemIndex + "__BRANCH' name='Adjustments[" + itemIndex + "].BRANCH' data-autocomplete-url='@Url.Action("GetBranches", "Report700H")'/></td>" +
            "<td><input type='text' id='Adjustments_" + itemIndex + "__Amount' name='Adjustments[" + itemIndex + "].Amount'/></td>" +
            "<td><a onclick='$('#row"+ itemIndex +").remove();'>x</a></td>" +
            "</tr>');

Actually this works fine for a smiliar page which i've created.

Greetings

Bernd
  • 730
  • 1
  • 5
  • 13