112
votes

I'm using Twitter Bootstrap and its "tabs".

I have the following code:

<ul class="nav nav-tabs">
    <li class="active"><a data-toggle="tab" href="#add">add</a></li>
    <li><a data-toggle="tab" href="#edit" >edit</a></li>
    <li><a data-toggle="tab" href="#delete" >delete</a></li>
</ul>

The tabs work properly, but the in the URL is not added the #add, #edit, #delete.

When I remove data-toggle the URL changes, but the tabs don't work.

Any solutions for this?

15
Plugin that fixes this: github.com/timabell/jquery.stickytabsTim Abell
This is a similar question as stackoverflow.com/q/18999501/873282koppor

15 Answers

277
votes

Try this code. It adds tab href to url + opens tab based on hash on page load:

$(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);
  });
});
49
votes

There are three components necessary for a complete solution:

  1. Show the correct tab when the page is loaded if there is a hash in the URL.
  2. Changing the hash in the URL when the tab is changed.
  3. Change the tab when the hash changes in the URL (back / forward buttons), and make sure the first tab is cycled correctly.

I don't think any of the comments here, nor the accepted answer, handle all of these scenarios.

As there's quite a bit involved, I think it's neatest as a small jQuery plugin: https://github.com/aidanlister/jquery-stickytabs

You can call the plugin like so:

$('.nav-tabs').stickyTabs();

I've made a blog post for this, http://aidanlister.com/2014/03/persisting-the-tab-state-in-bootstrap/

30
votes

Using data-toggle="tab" asks the plugin to handle the tabs, which while doing it calls preventDefault on the event (code on github).

You can use your own tab activation, and let the event go through :

$('.nav-tabs a').click(function (e) {
    // No e.preventDefault() here
    $(this).tab('show');
});

And you must remove the attribute data-toggle="tab"

Check the doc if you have doubts.

12
votes

As your anchor elements already change the url hash, listening to onhashchange event is another good option. As @flori already mentioned, this method works nice with the browser back button.

var tabs$ = $(".nav-tabs a");

$( window ).on("hashchange", function() {
    var hash = window.location.hash, // get current hash
        menu_item$ = tabs$.filter('[href="' + hash + '"]'); // get the menu element

    menu_item$.tab("show"); // call bootstrap to show the tab
}).trigger("hashchange");

Finally, triggering the event just after you define the listener will also help showing the right tab on page load.

3
votes

My solution to your problem:

First add new data-value attribute to each a link, like this:

<ul class="nav nav-tabs">
    <li class="active">
        <a data-toggle="tab" href="#tab-add" data-value="#add">add</a>
    </li>
    <li>
        <a data-toggle="tab" href="#tab-edit" data-value="#edit">edit</a>
    </li>
    <li>
        <a data-toggle="tab" href="#tab-delete" data-value="#delete">delete</a>
    </li>
</ul>

Second, change ids of tabs, e.g. from add to tab-add , also update hrefs to include tab- prefix:

<div class="tab-content">
    <div class="tab-pane" id="tab-add">Add panel</div>
    <div class="tab-pane" id="tab-edit">Edit panel</div>
    <div class="tab-pane" id="tab-delete">Panel panel</div>
</div>

And finally js code:

<script type="text/javascript">
    $(function () {
        var navTabs = $('.nav-tabs a');
        var hash = window.location.hash;
        hash && navTabs.filter('[data-value="' + hash + '"]').tab('show');

        navTabs.on('shown', function (e) {
            var newhash = $(e.target).attr('data-value');
            window.location.hash = newhash;
        });
    })
</script>

Here is jsFiddle - but it does not work because of iframe used by jsFiddle, but you can read source of my solution.

3
votes

Same problem with CakePHP with Bootstrap 3 Tabs, plus when i send with external URL and anchor, it jumps to the section losing my menu, after review many solutions made this...

thanks to: http://ck.kennt-wayne.de/2012/dec/twitter-bootstrap-how-to-fix-tabs

$(document).ready(function()
 {  
//Redirecciona al tab, usando un prefijo al cargar por primera vez, al usar redirect en cake #_Pagos    
//Redirecciona al tab, usando #Pagos
/* Automagically jump on good tab based on anchor; for page reloads or links */
 if(location.hash) 
  {
     $('a[href=' + location.hash + ']').tab('show');         
  }


//Para evitar el bajar al nivel del tab, (al mostrarse el tab subimos)
$('a[data-toggle="tab"]').on('shown.bs.tab', function(e) 
{
    location.hash = $(e.target).attr('href').substr(1);
    scrollTo(0,0);      
});



//Actualiza el URL con el anchor o ancla del tab al darle clic
 /* Update hash based on tab, basically restores browser default behavior to
 fix bootstrap tabs */
$(document.body).on("click", "a[data-toggle]", function(event) 
  {
    location.hash = this.getAttribute("href");
  });


//Redirecciona al tab, al usar los botones de regresar y avanzar del navegador.
/* on history back activate the tab of the location hash if exists or the default tab if no hash exists */   
$(window).on('popstate', function() 
{
  //Si se accesa al menu, se regresa al tab del perfil (activo default), fixed conflict with menu
  //var anchor = location.hash || $("a[data-toggle=tab]").first().attr("href");
  var anchor = location.hash;
  $('a[href=' + anchor + ']').tab('show');

});   

});//Fin OnReady
2
votes

There is also a simple way to react on hash changes (e.g. back button). Simply add an hashchange event listener to tomaszbak solution:

$(function(){
  // Change tab on load
  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();
    window.location.hash = this.hash;
    $('html,body').scrollTop(scrollmem);
  });

  // Change tab on hashchange
  window.addEventListener('hashchange', function() {
    var changedHash = window.location.hash;
    changedHash && $('ul.nav a[href="' + changedHash + '"]').tab('show');
  }, false);
});
2
votes

I understood that OP said using JQuery but you can simply use vanilla JS to do that:

document.querySelectorAll('ul.nav a.nav-link').forEach(link => {
    link.onclick = () => window.history.pushState(null, null, link.attributes.href.value);
});
1
votes

I am using Bootstrap 3.3.* and none of the above solution helped me, even marked as answer one. I just added below script and worked.

$(function(){
    var hash = document.location.hash;
    if (hash) {
       $('.navbar-nav a[href="' + hash + '"]').tab('show');
    }
    $('a[data-toggle="tab"]').on('click', function (e) {
       history.pushState(null, null, $(this).attr('href'));
    });
});

Hope this helps you.

0
votes

Most simple, assuming you're using the documented data-toggle="tab" syntax described here:

$(document).on('shown.bs.tab', function(event) {
  window.location.hash = $(event.target).attr('href');
});
0
votes

For those using Ruby on Rails or any other server side script, you will want to use the anchor option on paths. This is because once the page loads, it does not have the URL hash available. You will want to supply the correct tab via your link or form submission.

<%= form_for @foo, url: foo_path(@foo, anchor: dom_id(foo)) do |f| %>
# Or
<%= link_to 'Foo', foo_path(@foo, anchor: dom_id(foo)) %>

If you are using a prefix to prevent the window from jumping to the id:

<%= form_for @foo, url: foo_path(@foo, anchor: "bar_#{dom_id(foo)}") do |f| %>

Then you CoffeeScript:

  hash = document.location.hash
  prefix = 'bar_'
  $('.nav-tabs a[href=' + hash.replace(prefix, '') + ']').tab 'show' if hash
  $('.nav-tabs a').on 'shown.bs.tab', (e) ->
    window.location.hash = e.target.hash.replace '#', '#' + prefix

Or JavaScript:

var hash, prefix;

hash = document.location.hash;
prefix = 'bar_';

if (hash) {
  $('.nav-tabs a[href=' + hash.replace(prefix, '') + ']').tab('show');
}

$('.nav-tabs a').on('shown.bs.tab', function(e) {
  window.location.hash = e.target.hash.replace('#', '#' + prefix);
});

This should work in Bootstrap 3.

0
votes

Thanks to @tomaszbak for the original solution however since my edits got rejected and I'm unable to comment on his post yet I will leave my slightly improved and more readable version here.

It does basically the same thing but if fixes the cross browser compatibility issue I had with his.

$(document).ready(function(){
   // go to hash on window load
   var hash = window.location.hash;
   if (hash) {
       $('ul.nav a[href="' + hash + '"]').tab('show');
   }
   // change hash on click
   $('.nav-tabs a').click(function (e) {
       $(this).tab('show');
       if($('html').scrollTop()){
           var scrollmem = $('html').scrollTop(); // get scrollbar position (firefox)
       }else{
           var scrollmem = $('body').scrollTop(); // get scrollbar position (chrome)
       }
       window.location.hash = this.hash;
       $('html,body').scrollTop(scrollmem); // set scrollbar position
   });
});
0
votes

Here's an option using history.pushState so that you can use your browser history to go back to a previous tab

  $(document).ready(function() {
  // add a hash to the URL when the user clicks on a tab
  $('a[data-toggle="tab"]').on('click', function(e) {
    history.pushState(null, null, $(this).attr('href'));
  });
  // navigate to a tab when the history changes
  window.addEventListener("popstate", function(e) {
    var activeTab = $('[href=' + location.hash + ']');
    if (activeTab.length) {
      activeTab.tab('show');
    } else {
      $('.nav-tabs a:first').tab('show');
    }
  });
});
0
votes

None of the above worked for me, so here's my how I got mine working:

  1. Add class="tab-link" to all links needing this functionality
  2. Add the following code to your javascript:
    window.addEventListener
    (
        'popstate',
        function( event )
        {
            tab_change();
        }
    );

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

        $( '.tab-link' ).click
        (
            function( e )
            {
                $( this ).tab( 'show' );

                var scrollmem = $( 'body' ).scrollTop() || $( 'html' ).scrollTop();
                window.location.hash = this.hash;
                $( 'html,body' ).scrollTop( scrollmem );

                $( 'ul.nav-tabs a[href="' + window.location.hash + '"]' ).tab( 'show' );
            }
        );
    }

-2
votes

This also fixes an extremely obscure tab a href not firing when using jquery and bootstrap

<script src="//ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script> 

<script type="text/javascript">
$('.nav-tabs a').click(function (e) {
    // No e.preventDefault() here.
    $(this).tab('show');
});
</script>

<script src="//netdna.bootstrapcdn.com/twitter-bootstrap/2.2.1/js/bootstrap.min.js"></script>