I have an HTML table with dynamically generated rows (using MVC3’s EditorFor). Users fill in the data in the table (row by row), then submit the form to the server (via MVC3 HTML form). Users can delete a row by pushing a button that calls $(tableRow).remove() on the TR element, and then calls an async server method that removes the row from the database.
I’ve found that if I have say 5 rows in my table and I delete the third one then submit, the server method receives rows 1 and 2, but looses the other rows (the original 4th and 5th rows).
I’ve tried searching online as to why the postback would receive the first two rows and miss the last two, but all the answers I could find revolved around JQuery posts, which I’m not using.
Any help or direction would be great, please let me know if I need to clarify anything.
EDIT: adding code from my project that applies to the question. if you need more code for context, let me know and I’ll add it.
//////////////// VIEW ////////////////
// model info and initialization logic
@using (Html.BeginForm("EditTimesheet", "ControllerName", FormMethod.Post, new { enctype = "multipart/form-data", id = "editTimesheet" }))
{
@Html.ValidationSummary(true)
<fieldset>
<table width="100%">
<tr>
<td colspan="14" align="right">
// lots of code
</td>
</tr>
@Html.EditorFor(m => m.Rows)
<tr>
<td colspan="14" align="right">
// lots of code
</td>
</tr>
// closing statements
//////////////// EditorFor ////////////////
// model info and initialization logic
<tr class="timesheet-row">
<td>
<a href='#'>
<img src='@Url.Content("~/Content/Images/delete.gif")'
width='17' height='17' style='border: 0;'
onclick="DeleteRow(this, @Model.RowId)" />
</a>
</td>
// other td's
</tr>
//////////////// JS file ////////////////
function DeleteRow(box, rowId)
{
$(box).closest(".timesheet-row").remove();
// HACK: despicable, detestable HACK!
var url = deleteRowUrl;
url += '?rowId=' + rowId;
var ajaxData = {
type: "POST",
url: url,
dataType: "json",
contentType: "application/json; charset=utf-8",
data: null,
success: null,
error: function (error) {
alert("There was an error posting the data to the server: " + error.responseText);
}
};
$.ajax(ajaxData);
}
When you delete a row you are creating holes in the indexes of the names in the collection and the default model binder stops working because you no longer respect the expected format.
So instead of having the following sequential values:
if you delete the third row with
$(tableRow).remove()on the TR element you end up with:See the problem?
Here’s
an articlewhich illustrates how to solve this problem by using a custom helper calledHtml.BeginCollectionItemand which uses GUIDs in the names of the input fields for the collection instead of integer indexes. Also checkout Phil Haacksarticleabout the syntax that the default model binder expects your fields to be named. There’s a section towards the end which is called Non-Sequential Indices in which he covers how this could be done.