47

What is the best way of making these tabs persist?

http://twitter.github.com/bootstrap/javascript.html#tabs

To add some context, this is for a Ruby on Rails application. I'm passing an array [tab1, tab2] to my view, rendering both tabs and using the Bootstrap tab plugin to toggle their visibility.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
DanS
  • 17,550
  • 9
  • 53
  • 47

8 Answers8

85

This code selects the right tab depending on the #hash and adds the right #hash when a tab is clicked. (this uses jQuery)

In CoffeeScript:

$(document).ready ->
    if location.hash != ''
         $('a[href="'+location.hash+'"]').tab('show')

    $('a[data-toggle="tab"]').on 'shown', (e) ->
        location.hash = $(e.target).attr('href').substr(1)

Or in JavaScript:

$(document).ready(function() {
    if (location.hash !== '') $('a[href="' + location.hash + '"]').tab('show');
    return $('a[data-toggle="tab"]').on('shown', function(e) {
      return location.hash = $(e.target).attr('href').substr(1);
    });
});
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Sucrenoir
  • 2,994
  • 1
  • 27
  • 31
  • 8
    Thanks much for this answer. I made some tweaks to it in order to avoid two issues: 1) Switching tabs was adding to navigation history and 2) Scroll position was lost on back nav due to fragment navigation. https://gist.github.com/josheinstein/5586469 – Josh May 15 '13 at 19:09
  • 23
    This will not work in bootstrap 3. Use `shown.bs.tab` instead of `shown` for the event name. [Check here](http://getbootstrap.com/javascript/#tabs) – zentralmaschine Oct 26 '13 at 01:02
  • 21
    great answer, but if you want to avoid "jumping" of the page when you start clicking on your tabs, use this code instead of "return" `$('a[data-toggle="tab"]').on('shown.bs.tab', function(e) { if(history.pushState) history.pushState(null, null, '#'+$(e.target).attr('href').substr(1)); else location.hash = '#'+$(e.target).attr('href').substr(1); });` – d-wade Jan 29 '14 at 21:35
  • 6
    I made the Bootstrap 3 change, added it to my Rails app and it worked. Between Rails, gems and StackOverflow I feel like I barely write any actual code anymore. – Ryan Bosinger Apr 08 '14 at 21:51
36

I wanted to improve the best answer here...

Credit goes to Sucrenoir, but if you want to avoid jumping on the page when you change tabs, use this improved code:

$(document).ready(function() {

    // Show active tab on reload
    if (location.hash !== '') $('a[href="' + location.hash + '"]').tab('show');

    // Remember the hash in the URL without jumping
    $('a[data-toggle="tab"]').on('shown.bs.tab', function(e) {
       if(history.pushState) {
            history.pushState(null, null, '#'+$(e.target).attr('href').substr(1));
       } else {
            location.hash = '#'+$(e.target).attr('href').substr(1);
       }
    });
});
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
d-wade
  • 621
  • 7
  • 9
  • 1
    Best answer for bootstrap 3 – AshHimself Dec 23 '14 at 13:41
  • 1
    Copy, paste and relax. Thanks – Bojan Kogoj Dec 16 '15 at 15:57
  • while @Sucrenoir answer is a good one and I have used that to solve my own issue, this answer supersedes it because somehow on Sucrenoir's answer the page does not update the `#` on the first tab change. Only the first. However this code, by @dootzky *does* update the reference on the first and every tab click. Cheers – Martin Feb 01 '16 at 22:08
  • I am getting an error `TypeError: $(...).attr(...) is undefined` for this line `history.pushState(null, null, '#' + $(e.target).attr('href').substr(1));` – Musakkhir Sayyed Sep 28 '16 at 13:05
  • When navigating tabs, then pressing browser back button, it does not load the previously selected tab. – karns Jul 10 '20 at 19:38
14

Here is another way to solve the problem.

First add a line to the click event to show the hash in the address bar:

$('#myTab').on('click', 'a', function (e) {
  e.preventDefault();

  // Add this line
  window.location.hash = $(this).attr('href');

  $(this).tab('show');
})

Then make sure that the right tab is activated onload by adding this part to your document ready call.

if(window.location.hash){
   $('#myTab').find('a[href="'+window.location.hash+'"]').tab('show');
}

All together you can write this:

// Cache the ID
var navbox = $('#myTab');

// Activate tab on click
navbox.on('click', 'a', function (e) {
  var $this = $(this);

  // Prevent the ***default*** behavior
  e.preventDefault();

  // Set the hash to the address bar
  window.location.hash = $this.attr('href');

  // Activate the clicked tab
  $this.tab('show');
})

// If we have a hash in the address bar
if(window.location.hash){

  // Show right tab on load (read hash from address bar)
  navbox.find('a[href="'+window.location.hash+'"]').tab('show');
}
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
HaNdTriX
  • 28,732
  • 11
  • 78
  • 85
  • Is there a way to make this use the back and forward browser buttons?? – Jack Eccleshall Sep 03 '13 at 14:15
  • 1
    Did you test it? It should change the history based on the changed hashes in the url. https://developer.mozilla.org/en-US/docs/Web/Guide/DOM/Manipulating_the_browser_history – HaNdTriX Sep 03 '13 at 15:03
  • Yes...The script changes the browser URL relevant to the tab selected but it doesn't actually change the tab itself. it still stays as the one it is on when the browser back arrow is pressed. – Jack Eccleshall Sep 03 '13 at 16:03
  • 3
    Ok you need to listen to the ```hashchange```event. Quick&Dirty: [Demo](http://jsfiddle.net/handtrix/GrBzD/3/show) | [Code](http://jsfiddle.net/handtrix/GrBzD/3/) – HaNdTriX Sep 03 '13 at 16:24
7

I wanted to improve the best two answers here.. :)

Credit goes to Sucrenoir and d-wade.

Because the history API is used in code, you can't use onchangehash (https://developer.mozilla.org/en-US/docs/Web/API/WindowEventHandlers/onhashchange). This code add the functionality of back button (https://developer.mozilla.org/cs/docs/Web/API/WindowEventHandlers/onpopstate).

// Show active tab on reload
if (location.hash !== '')
    $('a[href="' + location.hash + '"]').tab('show');

// Remember the hash in the URL without jumping
$('a[data-toggle="tab"]').on('shown.bs.tab', function(e) {
    if(history.pushState) {
        history.pushState(null, null, '#'+$(e.target).attr('href').substr(1));
    }
    else {
        location.hash = '#'+$(e.target).attr('href').substr(1);
    }
});

// Remember to back button
window.onpopstate = function(e) {
    $('a[href="' + location.hash + '"]').tab('show');
};
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Pavel Berka
  • 119
  • 2
  • 4
  • 1
    I have been having issues with the previous answers to make the hash append in the URL when a tab is shown... This answer worked perfectly out-of-the-box for me – Aaron Lavers Jun 23 '17 at 08:01
2

Tested for Bootstrap 4, minimalist (two lines) code without history push, that works on any page with nav-tabs.

<script type="text/javascript">
    $(document).ready(function(){

        // Store the currently selected tab in the hash value
        $('a[data-toggle="tab"]').on('shown.bs.tab', function (e) { location.replace($(e.target).attr("href")); });

        // Switch to the currently selected tab when loading the page
        $('.nav-tabs a[href="' + window.location.hash + '"]').tab('show');
    });
</script>
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Awsom3D
  • 920
  • 7
  • 22
1

Another modified version if you don't want tab clicks to be added to the history, but also don't want the page jumping up and down:

$(document).ready(function () {

  if (location.hash !== '') {
    $('a[href="' + location.hash + '"]').tab('show');
  }

  $("a[data-toggle='tab']").on("shown.bs.tab", function (e) {
    var hash = $(e.target).attr("href");
    if (hash.substr(0,1) == "#") {
      var position = $(window).scrollTop();
      location.replace("#" + hash.substr(1));
      $(window).scrollTop(position);
    }
  });

});
1

Execute the following code after DOM load:

$('a[data-toggle=tab]').on('click', function () {
    history.pushState(null, null, $(this).attr('href'));
});


if (window.location.hash) {
    $('a[data-toggle=tab][href="' + window.location.hash + '"]').tab('show');
}

However, this leads to poor UI experience, since the currently active tab will be shown first, and then it will be switched to a tab from location.hash.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Artur INTECH
  • 6,024
  • 2
  • 37
  • 34
1

You can get the URL fragment (that's the part of the URL after #) on load using window.location.hash, and specifically set that tab as visible:

if (window.location.hash) {
    $(window.location.hash).tab('show')
}
Rory McCrossan
  • 331,213
  • 40
  • 305
  • 339
  • The hash won't be sent to the server, so not sure how i'd use this on it's own. – DanS Mar 13 '12 at 20:10
  • It won't be sent to the server as it has no need to be - the example above is javascript. For example, if you have an `a` element which sends the browser to `yoursite.com/about#company` the `#company` tab will be displayed. On refresh, the URL will be checked for a fragment, and if there is one will display the tab with the matching `id` by default. – Rory McCrossan Mar 13 '12 at 20:59
  • I edited my question to remove the 'persist a refresh' as it was misleading. My real problem is that I have pagination links and when I change page in tab2 it reverts back to tab1 – DanS Mar 13 '12 at 21:07