2

I have a View that contains a set of tabs each rendering a different partial view. After reading the documentation and W3Schools samples of these bootstrap tabs, I am unable to work out a way that makes the active tab remain active on Postback. All of the examples I've seen are using older versions of .Net and don't apply either.

Here is my code.
My controller action:

public IActionResult DisplayCharts(DashboardChartsByMonthModel model)
{
        //...do stuff
        model.MonthOrQuarterChart = monthChart;
        model.UserChart = userChart;
        return View(model);
}

Within the view @Scripts section:

<script>
   $(function(){
        var hash = window.location.hash;
        hash && $('ul.nav a[href="' + hash + '"]').tab('show');

        $('.nav-tabs a').click(function (e) {
            $(this).tab('show');
            var scrollmem = $('body').scrollTop() || $('html').scrollTop();
            window.location.hash = this.hash;
            $('html,body').scrollTop(scrollmem);
        });
    });
</script>
<script>
    $('a[data-toggle="tab"]').on('shown.bs.tab',
        function(e) {
            switch ($(e.target).attr('href')) {
            case '#tab1':
                drawMonthAndQuarterChart();
                break;

            case '#tab2':
                    drawUserChart();
                break;

            }
        });
</script>

Within the body:

<ul class="nav nav-tabs" role="tablist" id="myTabs">
<li class="nav-item">
    <a class="nav-link active" id="tab1-tab" data-toggle="tab" href="#tab1" role="tab" aria-controls="tab1" aria-selected="true">By Month & Quarter</a>
</li>
<li class="nav-item">
    <a class="nav-link" id="tab2-tab" data-toggle="tab" href="#tab2" role="tab" aria-controls="tab2" aria-selected="false">By Leasing / Billing Rep</a>
</li>

<div class="tab-content" id="myTabContent">
    <div class="tab-pane active" id="tab1" role="tabpanel" aria-labelledby="tab1-tab">
        <form method="post">
        ...model fields, etc.
        <button type="submit" class="btn btn-success">Submit</button>
            <div id="chart_monthandquarter_div"></div>
        </form>
    <div class="tab-pane" id="tab2" role="tabpanel" aria-labelledby="tab2-tab">
        <form method="post">
         ....
        </form>

And the same would go on for tab 2, tab 3, etc. Each tab has its own form with its own button that submits the model and when the page reloads after submit, I can swap between both tabs and see my charts rendering just fine however it keeps making the first tab active any time I post.

One of the several things I've tried was working out some of the older examples with a hidden field that I've seen some people use but I was unable to get that to work.

So with the code presented, how can I make it so that the current tab that you are viewing when you click submit is the one that's active on postback?

Mkalafut
  • 729
  • 2
  • 13
  • 34
  • What are you returning from the HttpPost action methods, to which the form is being submitted ? – Shyju Oct 26 '17 at 19:43
  • @Shyju `return View(model);` Just refreshing the page with the updated model based on my selectlist values. – Mkalafut Oct 26 '17 at 20:01
  • You might want to consider using a Javascript framework like VueJS. They make these sort of problems trivial to solve. – Matt LaCrosse Oct 27 '17 at 15:36
  • @MattLaCrosse What is the overhead of adding something like this? Is it as simple as including a Nuget package and using a script or something? Could you please provide more info or an example if able? – Mkalafut Oct 27 '17 at 15:57
  • @Mkalafut If you're using NodeJS it's usually as simple as including the correct library/configuration in your build setup. Then using the framework as needed. For instance, my ASP.NET Core Webapp uses Aurelia for the front-end UI with Webpack. The .NET is for the API backend. – Matt LaCrosse Oct 27 '17 at 16:13

1 Answers1

5

When user submits a form, you should follow the P-R-G pattern. So after saving, you should return a redirect response which issue a totally new GET request to load the page from scratch.

When issuing the redirect response, you can add a querystring speciific to the tab you want to select. For example, for the below sample markup, you can send the href value without the # as the querystring value and in the view read this querystring value and set to a js variable. In the document ready event, you can explicitly enable the tab you want with that.

<ul class="nav nav-tabs" role="tablist" id="myTabs">
    <li role="presentation" class="active"><a href="#home" aria-controls="home" role="tab" data-toggle="tab">Home</a></li>
    <li role="presentation"><a href="#profile" aria-controls="profile" role="tab" data-toggle="tab">Profile</a></li>
    <li role="presentation"><a href="#messages" aria-controls="messages" role="tab" data-toggle="tab">Messages</a></li>
    <li role="presentation"><a href="#settings" aria-controls="settings" role="tab" data-toggle="tab">Settings</a></li>
</ul>

And in your HttpPost action

public IActionResult UpdateProfile(YourViewModel model)
{
  // to do  :Save
   return RedirectToAction("Index", new { t = "profile" });
}

And in the view, you inject IHttpContextAccessor implementation so that we can access Request and then querystrings

@inject IHttpContextAccessor HttpContextAccessor

<!-- your code for the tabs goes here-->

<script>
    $(function() {    
        var t = '@HttpContextAccessor.HttpContext.Request.Query["t"]';
        if (t.length) {
            $('#myTabs a[href="#' + t + '"]').tab('show'); 
        }
    });
</script>
Shyju
  • 214,206
  • 104
  • 411
  • 497
  • So essentially I just need to change my Return to a RedirectToAction and pass in this string name for what I make the ID of my tabs, then in the view my the javascript function will know from that value which tab becomes active? – Mkalafut Oct 26 '17 at 20:19
  • Yes. Whenever you submit a form, you should follow PRG pattern, Read the link from the answer, which explains why we need it. – Shyju Oct 26 '17 at 20:24
  • You can test it by navigating to your page with the querystring like `?t=profile` assuming your tab markup is like what i have in the answer. ( The ul has an id "myTabs") – Shyju Oct 26 '17 at 20:28
  • 1
    Understood. Thank you, I will give it a try! – Mkalafut Oct 26 '17 at 20:30
  • Okay so I want to make sure I'm doing this correctly. Right now my controller just has a single action that returns View(model); My understanding is I am adding a new new controller action that the form will post to, that will then call the existing action with the new parameter? I've updated the original post with my controller action. – Mkalafut Oct 26 '17 at 20:39
  • 1
    I assume each tab has it's own form (Profile / Settings etc) and you probably want to have it's own action method to handle the form submit of that. If you are submitting from all the tabs to a single action method, you can include a hidden element in each of those forms with the value which we need for querystring (``) and add a new parameter named `tab` to your http post action method and use that to generate your querystring (`return RedirectToAction("Index",new { t=tab});`) – Shyju Oct 26 '17 at 20:56
  • Edited the original post a bit. I'm unsure where to go from here, but following the Bootstrap documentation I now have a reference to what the active tab is inside my URL. It's still not refreshing correctly though and I'd like to be able to use 1 controller action. – Mkalafut Oct 27 '17 at 14:38
  • are you calling the `.tab('show');` method as in my answer ? – Shyju Oct 27 '17 at 18:58
  • Yes. In the 2nd code block of the edited question it's on the 4th line. – Mkalafut Oct 27 '17 at 19:52
  • I do not see the line i have in the answer there (where i am reading the querystring and using that for jQuery selector and calling `.tab` method on that – Shyju Oct 27 '17 at 20:16
  • to inject the IHttpContextAccessor you will also need to inject the aspnetcore.http like so: /@/using Microsoft.AspNetCore.Http; /@/inject IHttpContextAccessor HttpContextAccessor – SunnySonic Jun 26 '18 at 21:21
  • best way to do it i've seen so far!! THUMBS UP! – SunnySonic Jun 26 '18 at 21:24