A Partial View passing a collection using the Html.BeginCollectionItem helper
First start by creating a view model to represent what you want to edit. I'm assuming cashAmount
is a monetary value, so therefore should be a decimal (add other validation and display attributes as required)
public class CashRecipientVM
{
public int? ID { get; set; }
public decimal Amount { get; set; }
[Required(ErrorMessage = "Please enter the name of the recipient")]
public string Recipient { get; set; }
}
Then create a partial view (say) _Recipient.cshtml
@model CashRecipientVM
<div class="recipient">
@using (Html.BeginCollectionItem("recipients"))
{
@Html.HiddenFor(m => m.ID, new { @class="id" })
@Html.LabelFor(m => m.Recipient)
@Html.TextBoxFor(m => m.Recipient)
@Html.ValidationMesssageFor(m => m.Recipient)
@Html.LabelFor(m => m.Amount)
@Html.TextBoxFor(m => m.Amount)
@Html.ValidationMesssageFor(m => m.Amount)
<button type="button" class="delete">Delete</button>
}
</div>
and a method to return that partial
public PartialViewResult Recipient()
{
return PartialView("_Recipient", new CashRecipientVM());
}
Then your main GET method will be
public ActionResult Create()
{
List<CashRecipientVM> model = new List<CashRecipientVM>();
.... // add any existing objects that your editing
return View(model);
}
and its view will be
@model IEnumerable<CashRecipientVM>
@using (Html.BeginForm())
{
<div id="recipients">
foreach(var recipient in Model)
{
@Html.Partial("_Recipient", recipient)
}
</div>
<button id="add" type="button">Add</button>
<input type="submit" value="Save" />
}
and will include a script to add the html for a new CashRecipientVM
var url = '@Url.Action("Recipient")';
var form = $('form');
var recipients = $('#recipients');
$('#add').click(function() {
$.get(url, function(response) {
recipients.append(response);
// Reparse the validator for client side validation
form.data('validator', null);
$.validator.unobtrusive.parse(form);
});
});
and the script to delete an item
$('.delete').click(function() {
var container = $(this).closest('.recipient');
var id = container.find('.id').val();
if (id) {
// make ajax post to delete item
$.post(yourDeleteUrl, { id: id }, function(result) {
container.remove();
}.fail(function (result) {
// Oops, something went wrong (display error message?)
}
} else {
// It never existed, so just remove the container
container.remove();
}
});
And the form will post back to
public ActionResult Create(IEnumerable<CashRecipientVM> recipients)
Passing a list to partialview, BeginCollectionItem()
Your _ComponentDetails
view is generating form controls that have name attributes that look like (where ###
is a Guid
)
name="InquiryComponentDetail[###].icd.Height"
which does not match your model because typeof InquiryComponentDetail
does not contain a property named icd
. In order to bind to your model, your name
attribute would need
name="InquiryComponentDetail[###].Height"
To generate the correct html, you will need 2 partials
_ComponentDetailsList.cshtml
(this will be called by the ComponentDts()
method using return PartialView("_ComponentDetailsList", iocdList);
)
@model List<eKnittingData.InquiryComponentDetail>
<div class="cmpCls">
@foreach(var item in Model)
{
Html.RenderPartial("_ComponentDetails", item);
}
</div>
_ComponentDetails.cshtml
@model eKnittingData.InquiryComponentDetail
using (Html.BeginCollectionItem("InquiryComponentDetails"))
{
<div class="innerCmpCls">
@Html.DisplayFor(a => a.DesignCodeId)
@Html.DisplayFor(a => a.QualityReferenceId)
@Html.TextBoxFor(a => a.Height, new { @class="clsHeight clsSameHL"}) // use @class, not Class
@Html.TextBoxFor(a => a.Length, new { Class = "clsLength clsSameHL" })
....
</div>
}
MVC BeginCollectionItem
Your use of Html.BeginCollectionItem("Section")
perpends Section[xxxx]
to the name attribute (where xxxx
is a Guid
) so that you generating inputs with
<input name="Section[xxxx].SectionId" .... />
which posts back to a model containing a collection property named Sections
.
Since you already have a model with that property, you can change the POST method to
[HttpPost]
public ActionResult SOPTopTemplateBuilder(SOPTopTemplateBuilderViewModel soptop)
other options include
- Using your existing POST method and omitting the "Section" prefix
usingHtml.BeginCollectionItem("")
which will generatename="[xxxx].SectionId"
- Changing the POST method signature to
public ActionResult
(where the
SOPTopTemplateBuilder(IEnumerable<Section> section)
name of the parameter matches the name of the prefix) - Using a
BindAttribute
to 'strip' the prefix from the form valuespublic ActionResult SOPTopTemplateBuilder([Bind(Prefix = "Section")]IEnumerable<Section> soptop)
As a side note, your editing data, so you should always use a view model (say public class SectionViewModel
) rather than using data models in your view. - What is ViewModel in MVC?
MVC 5 BeginCollectionItem with Partial CRUD
You do not need to use BeginCollectionItem in order to achieve this. From having to look into it myself and trying to use it for a similar issue, it appears it was created for problems of this nature with earlier versions of MVC.
Use Partial Views to display and update the list. One partial view to display and iterate through the list of objects, and another to create a new object which upon post back to update the list will show the newly created object in the partial view with the list.
I posted a similar question on here which should solve your issue, click here
Hope this helps.
Update
The reason your delete doesn't work is because you can't call JS from Partial View, put it in the main view (@section Script). Also I think you got a bit muddled with your class and id keywords in your divs, have a look below.
So you should have:
Partial View
@model MvcTest.Models.Employee
@using (Html.BeginCollectionItem("Employees"))
{
<div id="employeeRow" class="employeeRow">
@Html.LabelFor(m => m.Name)
@Html.EditorFor(m => m.Name)
@Html.LabelFor(m => m.Telephone)
@Html.EditorFor(m => m.Telephone)
@Html.LabelFor(m => m.Mobile)
@Html.EditorFor(m => m.Mobile)
@Html.LabelFor(m => m.JobTitle)
@Html.EditorFor(m => m.JobTitle)
<a href="#" id="deleteRow" class="deleteRow" onclick="deleteFunction()">Delete</a>
</div>
}
Main View
@model MvcTest.Models.Company
@{
ViewBag.Title = "Index";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>Company</h2>
<div>
@Html.LabelFor(m => m.Name)
@Html.EditorFor(m => m.Name)
</div>
<fieldset>
<legend>Employees</legend>
<div id="new-Employee">
@foreach (var Employee in Model.Employees)
{
Html.RenderPartial("_Employee", Employee);
}
</div>
<div>
<input type="button" id="addemployee" name="addemployee" value="Add Employee"/>
<br/>
</div>
<br/>
@section Scripts
{
<script type="text/javascript">
$('#addemployee').on('click', function () {
$.ajax({
async: false,
url: '/Company/AddNewEmployee'
}).success(function (partialView) {
$('#new-Employee').append(partialView);
});
});
$("#deleteRow").live("click", function () {
$(this).parents("#employeeRow:first").remove();
return false;
});
</script>
}
</fieldset>
<div>
<input type="submit" value="Submit" />
</div>
Not Receiving Multiple Partial View data on Postback
A form only posts back the name/value pairs of its successful controls, and your not generating the form controls inside the <form>
tags.
Change the code to move the @Html.Partial()
code inside the <form>
(simplified)
@model IEnumerable<TDLProject.Models.BookViewModel>
@using (Html.BeginForm())
{
@foreach (var model in Model)
{
@Html.Partial("_DynamicPartialView", model)
}
<input type="button" id="addBook" name="addBook" value="Add Book" />
<input type="submit" id="submitButton" value="Submit" />
}
You also need to change the POST method signature to
public ActionResult Contact(IEnumerable<BookViewModel> books)
to match the name you use in the @using (Html.BeginCollectionItem("books"))
method.
Related Topics
How to Get Jquery to Perform a Synchronous, Rather Than Asynchronous, Ajax Request
JavaScript Equivalent of Python'S Zip Function
How to Send a Cross-Domain Post Request Via JavaScript
When Should I Use Curly Braces For Es6 Import
Create and Save a File With JavaScript
Why Is Setstate in Reactjs Async Instead of Sync
Generate Random String/Characters in JavaScript
Format Number to Always Show 2 Decimal Places
How to Empty an Array in JavaScript
Listening For Variable Changes in JavaScript
How to Access React Instance (This) Inside Event Handler
Difference Between a Function Expression VS Declaration in JavaScript
Parse Query String in JavaScript
How to Add 30 Minutes to a JavaScript Date Object
Trigger a Button Click With JavaScript on the Enter Key in a Text Box
Browser Detection in JavaScript