A Partial View Passing a Collection Using the Html.Begincollectionitem Helper

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

  1. Using your existing POST method and omitting the "Section" prefix
    using Html.BeginCollectionItem("") which will generate
    name="[xxxx].SectionId"
  2. Changing the POST method signature to public ActionResult
    SOPTopTemplateBuilder(IEnumerable<Section> section)
    (where the
    name of the parameter matches the name of the prefix)
  3. Using a BindAttribute to 'strip' the prefix from the form values
    public 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



Leave a reply



Submit