Pakk
Pakk

Reputation: 1339

Complex Nested Partial View Ajax Post to MVC 5 Controller

TL;DR: I just need to figure out why i can't serialize() the partial view's parent div and receive the model. Coding this out manually will take forever as there are many parent partials I'd have to use the same logic on.

More info: I've tried EditorTemplate for binding purposes, but unfortunately there is no easy way to use them as variable lists as far as I've searched.

Begin:

Models

public class ContactModel 
{                
    public List<ContactDetailModel> Contacts { get; set; }
    ....


public class ContactDetailModel
{
    public ContactView Contact { get; set; }
    public PhoneModel PhoneModel { get; set; }
    ...

public class PhoneModel
{
    public int ContactId { get; set; }
    public int IsPrimaryPhoneNumberId { get; set; }
    public List<PhoneView> Phones { get; set; }
    public List<EmailPhoneTypeView> EmailPhoneTypes { get; set; }
...

To select & post inputs from just this partial view, I've implemented a variable class, and its relative template prefix to keep the MVC binding for the partial view.

@{
 var phoneClass = "phone" + @Model.Contacts[index].Contact.ContactId;
 var phoneTemplatePrefix = "Contacts[" + index + "].PhoneModel";
 }

This is ran inside of a loop, increasing indexes as needed to keep binding.

<div class="@phoneClass">
     @Html.Partial("_ContactPhone", Model.Contacts[index].PhoneModel, new ViewDataDictionary() { TemplateInfo = new TemplateInfo() { HtmlFieldPrefix = phoneTemplatePrefix } })
</div>

The partial I'm attempting to post. (Strongly Typed partial for PhoneModel)

@{var addNavigationClass = "AddContactPhone" + Model.ContactId;}

for (var phoneIndex = 0; phoneIndex < Model.Phones.Count(); phoneIndex++)
{
    @Html.HiddenFor(model => model.Phones[phoneIndex].ContactPhoneId)
    @Html.DropDownListFor...
    @Html.TextBoxFor(model => model.Phones[phoneIndex].PhoneNumber)
    <a href="#" class="removeMemberPhone">Trash</a>
    @Html.RadioButtonFor(model => model.IsPrimaryPhoneNumberId, Model.Phones[phoneIndex].ContactPhoneId) Primary</label>
}

Inside the View's click function

var model = $('.phone' + '@Model.ContactId' + ' :input').serialize();
console.log('model', model);

$.ajax({
   url: '/Contact/AddPhone',
   type: 'POST',
   data: model,
   success: function (data) {
     console.log(data.length);
    }
    ....

The log's output

model Contacts%5B1%5D.PhoneModel.Phones%5B0%5D.ContactPhoneId=3907&Contacts%5B1%5D.PhoneModel.Phones%5B0%5D.EmailPhoneTypeId=1&..........

My model never has any values in my controller (I've abbreviated ContactPhoneModel to PhoneModel in the above code)...

enter image description here

Upvotes: 1

Views: 1101

Answers (2)

Pakk
Pakk

Reputation: 1339

Here's a bit of (unrefined, and early) code that I've written, that will take properly formatted serializeArray() data and re base the arrays for a good post to the MVC controller.

Top 2 lines will show how to call it once in your project.

Upvotes: 0

Slicksim
Slicksim

Reputation: 7172

This is more pseudo code than actual code, you will need to tidy this up a bit.

Since you aren't posting back the full page, it seems overkill to jump through the Mvc model binders hoops when binding to a collection. If it was me doing this, I'd alter your click handler to the following:

var model = {};
$('.phone' + '@Model.ContactId' + ' :input').each(function(){
    model[/[^\.]+$/.exec($(this).prop("name"))[0]] = $(this).val();
};
console.log('model', model);

$.ajax({
   url: '/Contact/AddPhone',
   type: 'POST',
   data: model,
   contentType: "application/json; charset=UTF-8",
   success: function (data) {
      console.log(data.length);
    }

I would look at using editortemplates as mentioned in the comments, they will take some of the pain away of managing indexes and the like

Upvotes: 1

Related Questions