Sign up ×
Stack Overflow is a community of 4.7 million programmers, just like you, helping each other. Join them; it only takes a minute:

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

share|improve this question
1  
It works just fine in fiddle. – 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 R 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 <form>, and it still works: updated fiddle. – Regent Sep 23 '14 at 11:51

3 Answers 3

up vote 1 down vote accepted

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 ++;
            });
        }
    }
share|improve this answer
    
Thanks a lot, it really solves the problem. – Gyuzal R Sep 23 '14 at 12:29

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();
share|improve this answer

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

share|improve this answer
    
onclick="function_name()" is outdated way for handling events. You wrote code line inside onclick - it's even worse... – Regent Sep 23 '14 at 11:38
    
id didn't help in my case. – Gyuzal R Sep 23 '14 at 11:42

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.