Splitting one view model across multiple forms on one page

Question

I've got an ASP.NET MVC 5 app that uses more of an MVVM approach to MVC: it has "view models" (VM) for the Entity Framework models.

Our scenario:

  • We have a VM with an accompanying edit view with about 7 Bootstrap tabs.
  • Each tab contains its own @using (Html.BeginForm(...)) { ... }.
  • When the user switches tabs, we use AJAX to save its data (if it's valid).

Our challenge:

The POST Edit(...) action expects common hidden forms to be submitted with each AJAX call. We end up having duplicate hidden fields, as they're repeated for each form:

@Html.HiddenFor(model => model.ChildVm.Application_No) 
@Html.HiddenFor(model => model.ChildVm.FormSubmitted)
// other hidden fields...

We run into issues when setting some of these fields, some of whose values are unique to each form. Because we're breaking HTML rules by having duplicate IDs, we have to use some ugly tricks to set their values in jQuery:

// Razor helper method to fetch ASP.NET ID for control
var formSubmittedId = '@Html.ClientIdFor(m => m.ChildVm.FormSubmitted)';

// Tab change handler
$('a[data-toggle="tab"]').on('shown.bs.tab', function (e) {
    var form = $(e.relatedTarget).attr('href');
    var formId = $(e.relatedTarget).attr('href').replace('#', '');
    var previousTab = $(form + '-form');
    $(previousTab).find('#' + formSubmittedId).val(formId);
    //....
});

The above works but it's ugly. How do you include form fields from the same view model across multiple forms on the same CSHTML without duplicate IDs? If I give the fields different IDs, wouldn't that break the Edit's POST action, which uses the IDs from the client to match them to their respective properties in the view model server side?

Thanks.


Show source
| jquery   | asp.net-mvc   | forms   | razor   2017-09-05 20:09 1 Answers

Answers to Splitting one view model across multiple forms on one page ( 1 )

  1. 2017-09-06 06:09

    Forms post back the name/value pairs of its successful form controls, not the value of their id attributes. The only purpose of id attributes is for use in javascript/jquery and css selectors.

    To prevent the invalid html (duplicate id attrbutes), you an just remove the attribute using (no ugly scripts required)

    @Html.HiddenFor(model => model.ChildVm.Application_No, new { id = "" }) 
    

    However in general, you should avoid hidden inputs in your view (except for those related to the ID of your record, although that value is better added as a route value to your anyway). If you need the values of your other hidden inputs in the POST method, then your should be getting your record again based on the ID.

    If for some reason, you do need to post back all those additional values in each ajax post, a better solution would be to include those inputs once only inside a separate <form> element and use the .serialize() method to combine the values, for example

    <form id="hiddeninputs">
        @Html.HiddenFor(model => model.ChildVm.Application_No)
        .... // other hidden inputs
    <form>
    @using (Html.BeginForm(....))
    {
        .... // form controls for 1st tab
    }
    @using (Html.BeginForm(....))
    {
        .... // form controls for 2nd tab
    }
    

    and then in the script to post the form

    $('form').submit(function() {
        var formdata = $(this).serialize() + '&' + $('#hiddeninputs').serialize;
        $.ajax({
            ....
            data: formdata,
            ....
        });
        return false; // cancel the default submit
    

    You could even remove the hidden inputs altogether, and construct a object to post back

    var hiddeninputs = {
        'ChildVm.Application_No': '@Model.ChildVm.Application_No',
        ....
    }
    

    and use

    var formdata = $(this).serialize() + '&' + $.param(hiddeninputs, true);
    

Leave a reply to - Splitting one view model across multiple forms on one page

◀ Go back