asp.net-mvc-5formsbootstrap-tabsmultiple-forms

Restore the active bootstrap tab after postback when page has multiple forms


I have an ASP.Net MVC 5 project that has a page with 2 bootstrap tabs. Each tab has two forms for a total of 4 forms on the page. My viewmodel has a superset of all the fields in the 4 forms.

When I post a form, I want the response view to display the same tab that was showing when the form was posted. This SO answer Remain bootstrap tab after postback c# indicates that I should set the active tab in a hidden field, return it in the view model, then restore it on postback.

This SO answer asp.net MVC 4 multiple post via different forms indicates that returned fields must be within the form I'm returning. This presents a problem since the hidden field needs to be in the scope of all the forms.

On postback, how do I display the same tab that was showing when the form was posted when I have multiple forms on the page?


Solution

  • I solved this problem by removing the 4 forms and replacing it with a single form that spanned the previous 4. To get the form to post-back to the proper controller/action, I wrote some javascript that is invoked when one of the 4 submit buttons is activated.

    It sets the new form's action to the appropriate controller/action and then submits the form. This way, there is only 1 form and 1 hidden field to hold the active tab and the correct action still gets invoked on post-back. A test program with 2 tabs and (for simplicity) only 2 actions is here:

    View:

        @model MultiPostDestinations.Models.HomeVM
    @{
        ViewBag.Title = "Home Page";
        Layout = "~/Views/Shared/_Layout.cshtml";
    }
    <br/><br /><br /><br />
    <div class="row">
        <div class="col-md-4">
            @using (Html.BeginForm("SubmitForm", "Home", FormMethod.Post, new { id = "submitForm", enctype = "multipart/form-data" })) {
                @Html.HiddenFor(m => m.ActiveTab)
                <div id="Tabs" role="tabpanel" class="container" style="width: 1000px">
                    <!-- Nav tabs -->
                    <ul class="nav nav-tabs" role="tablist">
                        <li class="active"><a href="#details" aria-controls="details" role="tab" data-toggle="tab">Details</a></li>
                        <li><a href="#history" aria-controls="history" role="tab" data-toggle="tab">Historic Model Analysis</a></li>
                    </ul>
                    <!-- Tab panes -->
                    <div class="tab-content">
                        <div id="details" role="tabpanel" class="tab-pane fade in active">
                            <h2>Details Tab</h2>
                            <h4>label: @Model.F1</h4><br /><br />
                            @Html.DisplayFor(m => m.F1)<br /><br />
                            @Html.EditorFor(m => m.F1)<br /><br />
                            @Html.ActionLink("ActionLink Submit form", "SubmitGet", "Home", new { @class = "btn btn-default" })<br /><br />
                            <input type="submit" value="input SubmitForm" class="btn btn-default" /><br /><br />
                            <input id="submitButton1" type="button" value="javascript input to /Home/Submit1" class="btn btn-default" /><br /><br />
                        </div>
                        <div id="history" role="tabpanel" class="tab-pane fade">
                            <h2>Historic Model Analysis tab</h2>
                            <h4>label: @Model.F2</h4><br /><br />
                            @Html.DisplayFor(m => m.F2)<br /><br />
                            @Html.EditorFor(m => m.F2)<br /><br />
                            <input type="submit" value="input SubmitForm" class="btn btn-default" /><br /><br />
                            <input id="submitButton2" type="button" value="javascript input to /Home/Submit2" class="btn btn-default" /><br /><br />
                        </div>
                    </div>
                </div>
            }
    
        </div>
    </div>
    
    @section scripts {
        <script type="text/javascript">
            $(document).ready(function () {
                $('#submitButton1').click(function (e) {
                    e.preventDefault(); // recommended in SO, does not appear to be required
                    $('#submitForm').attr('action', '/Home/Submit1');
                    $('#submitForm').submit();
                });
                $('#submitButton2').click(function (e) {
                    e.preventDefault();     // recommended in SO, does not appear to be required
                    $('#submitForm').attr('action', '/Home/Submit2');
                    $('#submitForm').submit();  // previous doesn't work - why?
                });
    
                var lastActiveTab = ($('#ActiveTab').val() !== '') ? $('#ActiveTab').val() : 'details';
                $('#Tabs a[href="#' + lastActiveTab + '"]').tab('show');
                $("#Tabs a").click(function () {
                    $('#ActiveTab').val($(this).attr("href").replace("#", ""));
                });
    
            });
        </script>
    }
    

    Here is the Controller with 2 actions:

    using MultiPostDestinations.Models;
    using System.Web.Mvc;
    
    namespace MultiPostDestinations.Controllers {
        public class HomeController : Controller {
            public ActionResult Index() {
                var vm = new HomeVM() { F1 = "Index-F1", F2 = "Index-F2", ActiveTab = "" };
                return View("Index", vm);
            }
    
            [HttpPost]
            public ActionResult Submit1(HomeVM vm) {
                System.Diagnostics.Debug.WriteLine("HomeVM.Submit1: F1={0}, F2={1}", vm.F1 ?? string.Empty, vm.F2 ?? string.Empty);
                // ModelState.Clear();         // uncomment if you want Html.EditorFor() fields to update on postback
                return View("Index", vm);
            }
    
            [HttpPost]
            public ActionResult Submit2(HomeVM vm) {
                System.Diagnostics.Debug.WriteLine("HomeVM.Submit2: F1={0}, F2={1}", vm.F1 ?? string.Empty, vm.F2 ?? string.Empty);
                //ModelState.Clear();         // uncomment if you want Html.EditorFor() fields to update on postback
                return View("Index", vm);
            }
    
        }
    }
    

    And finally the view-model:

        namespace MultiPostDestinations.Models {
        public class HomeVM {
            public string ActiveTab { get; set; }
            public string F1 { get; set; }
            public string F2 { get; set; }
    
        }
    }