I’ve created a test app to learn more about asp.net mvc. The app is supposed to allow the use to add an “unlimited” number of inputs via jQuery using partial views and editor templates. I’ve managed to get the adding of the new input elements but I’m having issues preserving the entered data from the user when adding newer input elements.
My strongly-typed view
@model TestApp.Models.ItemViewModel
@using (Html.BeginForm())
{
@Html.ValidationSummary(true)
<div class="editor-label">
@Html.LabelFor(model => model.ItemName)
</div>
<div class="editor-field">
@Html.EditorFor(model => model.ItemName)
@Html.ValidationMessageFor(model => model.ItemName)
</div>
<div class="editor-label">
Click to add rates
</div>
<div class="editor-field">
<a href="#" id="NewRate">Add Rate</a>
<input type="hidden" value="0" id="rateCounter" />
</div>
<br />
<span id="itemRates"></span>
<p>
<input type="submit" value="Save" />
</p>
}
My javascript
When the user clicks on the NewRate element above, I fire this jQuery to get the partial view. I also keep track of the number of rows created by the user via a hidden element rateCounter which I increment everytime the click event fires.
$("#NewRate").click(function () {
var count = parseInt($("#rateCounter").val());
$("#rateCounter").val(count + 1);
$.ajax({
type: 'GET',
url: '/Item/AddRate',
data: {
rateCount: $("#rateCounter").val()
},
success: function (data) {
$('#itemRates').html(data);
}
});
});
My controller
This accepts the number of rows to create and passes to the partial view.
public PartialViewResult AddRate(int rateCount)
{
var itemVM = new ItemViewModel { Rates = new List<ItemRatesViewModel>() };
for (int i = 0; i < RateCount; i++)
{
itemVM.Rates.Add(new ItemRatesViewModel());
}
return PartialView("_ItemRates", itemVM);
}
My strongly-typed partial view
The partial view just uses an editor template
@model TestApp.Models.ItemViewModel
@Html.EditorFor(model => model.Rates)
My editor template
The editor template basically displays each rate
@model TestApp.Models.ItemRatesViewModel
<tr>
<td>
@Html.EditorFor(model => model.Rate)
@Html.ValidationMessageFor(model => model.Rate)
</td>
<td>
@Html.EditorFor(model => model.StartDate)
@Html.ValidationMessageFor(model => model.StartDate)
</td>
<td>
@Html.EditorFor(model => model.EndDate)
@Html.ValidationMessageFor(model => model.EndDate)
</td>
</tr>
On the first click
Using Google Chrome’s developer tools, I see that the response is like this. Which should be correct. When I submit the form, the model binder picks up the entered data.
<tr>
<td>
<input class="text-box single-line" data-val="true" data-val-number="The field Rate must be a number." data-val-required="*" id="Rates_0__Rate" name="Rates[0].Rate" type="text" value="0.00" />
<span class="field-validation-valid" data-valmsg-for="Rates[0].Rate" data-valmsg-replace="true"></span>
</td>
<td>
<input data-datepicker-future="True" data-val="true" data-val-required="*" id="Rates_0__StartDate" name="Rates[0].StartDate" type="text" value="01/01/0001" />
<span class="field-validation-valid" data-valmsg-for="Rates[0].StartDate" data-valmsg-replace="true"></span>
</td>
<td>
<input data-datepicker-future="True" data-val="true" data-val-required="*" id="Rates_0__EndDate" name="Rates[0].EndDate" type="text" value="01/01/0001" />
<span class="field-validation-valid" data-valmsg-for="Rates[0].EndDate" data-valmsg-replace="true"></span>
</td>
</tr>
On the second click
The entered data on the first set of input element (index [0]) is discarded and replaced by newly initialized one since I am using $('#itemRates').html(data); in my javascript instead of $('#itemRates').append(data);. Below is what I get. When I submit the form, the model binder picks up the entered data correctly in the Rates collection of the view model.
<tr>
<td>
<input class="text-box single-line" data-val="true" data-val-number="The field Rate must be a number." data-val-required="*" id="Rates_0__Rate" name="Rates[0].Rate" type="text" value="0.00" />
<span class="field-validation-valid" data-valmsg-for="Rates[0].Rate" data-valmsg-replace="true"></span>
</td>
<td>
<input data-datepicker-future="True" data-val="true" data-val-required="*" id="Rates_0__StartDate" name="Rates[0].StartDate" type="text" value="01/01/0001" />
<span class="field-validation-valid" data-valmsg-for="Rates[0].StartDate" data-valmsg-replace="true"></span>
</td>
<td>
<input data-datepicker-future="True" data-val="true" data-val-required="*" id="Rates_0__EndDate" name="Rates[0].EndDate" type="text" value="01/01/0001" />
<span class="field-validation-valid" data-valmsg-for="Rates[0].EndDate" data-valmsg-replace="true"></span>
</td>
</tr>
<tr>
<td>
<input class="text-box single-line" data-val="true" data-val-number="The field Rate must be a number." data-val-required="*" id="Rates_1__Rate" name="Rates[1].Rate" type="text" value="0.00" />
<span class="field-validation-valid" data-valmsg-for="Rates[1].Rate" data-valmsg-replace="true"></span>
</td>
<td>
<input data-datepicker-future="True" data-val="true" data-val-required="*" id="Rates_0__StartDate" name="Rates[1].StartDate" type="text" value="01/01/0001" />
<span class="field-validation-valid" data-valmsg-for="Rates[1].StartDate" data-valmsg-replace="true"></span>
</td>
<td>
<input data-datepicker-future="True" data-val="true" data-val-required="*" id="Rates_0__EndDate" name="Rates[1].EndDate" type="text" value="01/01/0001" />
<span class="field-validation-valid" data-valmsg-for="Rates[1].EndDate" data-valmsg-replace="true"></span>
</td>
</tr>
FINALLY, my question
Is there a way to get just the 2nd row (index [1]) in the generated response then use jQuery’s append instead of replacing the whole html with the new rows? What is the correct way of doing this? I know I’m close but a little guidance would go a long way. 🙂
I probably wouldn’t be going back to the server and getting the whole view with all the rows this way.
The way MVC parses the sent data once posting the form is the array of elements sent back (specified by the number in [] brackets in the name attribute of the elements.
The way I have done it in the past is cloned the row by jquery and used a regex function to replace the[0] with [1] and cleared the values out also. Then just append it.
This will also save you from doing a call to the server every time the add is clicked.