114

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?

Nope
  • 22,147
  • 7
  • 47
  • 72
Ivanka Todorova
  • 9,964
  • 16
  • 66
  • 103

15 Answers15

282

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);
  });
});
tomaszbak
  • 8,247
  • 4
  • 44
  • 37
  • +1 -- This is really helpful! I made a working demo of using this for the latest Bootstrap 3 - http://bootply.com/78032 – Carol Skelly Aug 30 '13 at 12:17
  • 1
    It work very nice. +1. But what is the reason of "hash && $.." from selector is show tab, but what does mean "hash &&". Thank – richardhell Nov 08 '13 at 23:19
  • 4
    `&&` means `AND` so if the left of that equates to true (`hash` is not 0, null or empty) and the right equates to true then do something, however the line ends with a `;` so there is nothing to do before `;` and it doesn't matter if a tab is shown or not and it doesn't matter what `.tab('show')` returns, it will still try. When evaluating logic in this way, if the first part of the equation (before `&&`) is false, there is no need to continue evaluating, so the tab won't be shown. You could achieve the same effect like this: `if(hash){ $('ul.nav a[href="' + hash + '"]').tab('show'); }` – vahanpwns Feb 04 '14 at 22:58
  • 1
    TL;DR: `a && b;` means try `a` and then only if its true, try `b`. Strings are true if they are not empty. – vahanpwns Feb 04 '14 at 23:06
  • 9
    How would I make it so that it just reloads the page at the top when I use the "#" url? Right now it scrolls down to where the tabs start. I'd just like to scroll top to the actual page – kibaekr Feb 28 '14 at 11:46
  • I'm also having the same issue as @kibaekr. Maybe because the hash in the URL makes the browser think it's an anchor link, and so it scrolls down to where `#hash` appears. I haven't found a workaround yet. – nnyby Apr 30 '15 at 22:11
  • A description of that problem is here: https://github.com/ccnmtl/dmt/pull/401#issue-72297915 – nnyby Apr 30 '15 at 22:24
  • I've edited the answer, but also see: https://github.com/ccnmtl/dmt/pull/585/files for a fix to the scrolling issue I described. – nnyby Nov 18 '15 at 15:19
  • Here's the most complete fix for the scrolling problem: `$('ul.nav a').on('shown.bs.tab', function() { $(window).scrollTop(0); });` – nnyby Jan 25 '16 at 18:43
  • Great solution. I also wanted to remove the hash if the user navigates to the first tab, which I did by replacing `window.location.hash = this.hash;` with `if ($(this).is($(this).parent().parent().children(':first-child').children())) { removeHash(); } else { window.location.hash = this.hash; }`, utilizing the `removeHash()` solution from here: https://stackoverflow.com/a/5298684/1179207 – dougmacklin Aug 08 '17 at 10:25
  • 1
    works fine but had to replace "$('.nav-tabs a').click(" by "$('#navtabs a').on('shown.bs.tabs," on Bootstrap 3 – Zzirconium Feb 08 '18 at 12:54
  • This code snippet potentially introduces cross-site scripting vulnerabilities. See: https://cwe.mitre.org/data/definitions/80.html https://cwe.mitre.org/data/definitions/79.html For instance, this will raise an alert: `https://example.com/#'">` – Sean Lovell Aug 04 '21 at 16:37
49

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/

isherwood
  • 58,414
  • 16
  • 114
  • 157
Aidan
  • 4,150
  • 2
  • 20
  • 16
  • I gave it it's own git repo: https://github.com/timabell/jquery.stickytabs/ - many thanks for the plugin, works a treat. – Tim Abell Nov 14 '14 at 11:49
  • +1 for this solution as it also works on back/next browsing which @tomaszback' solution does not. Thanks! – bitfidget Mar 11 '15 at 01:17
  • This is great and works well, but not when I load the page with a hash in the URL. Will keep digging around to make sure it's something I've done wrong (highly likely). However, I'm going to give it a +1 – MattBoothDev May 03 '18 at 13:07
31

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.

Sherbrow
  • 17,279
  • 3
  • 64
  • 77
13

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.

tomloprod
  • 7,472
  • 6
  • 48
  • 66
halilb
  • 4,055
  • 1
  • 23
  • 31
3

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.

psulek
  • 4,308
  • 3
  • 29
  • 37
3

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

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);
});
Flori
  • 671
  • 7
  • 12
2

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);
});
alexandre-rousseau
  • 2,321
  • 26
  • 33
1

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');
});
Fred
  • 1,021
  • 5
  • 13
  • 29
1

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.

Abhimanyu
  • 2,173
  • 2
  • 28
  • 44
0

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.

Mohamad
  • 34,731
  • 32
  • 140
  • 219
0

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
   });
});
Fuxy
  • 91
  • 6
  • Rejected was not working for me (maybe it was just Firefox version). Anyway, I made change to var scrollmem = $('body').scrollTop() || $('html').scrollTop(); Thanks! – tomaszbak Mar 23 '16 at 14:03
  • 1
    Yes. Tell me about it the moment you said that i realised it was a browser thing an quickly went into chrome to make sure. I sure wish there was a way to comment on edit reviews. – Fuxy Mar 23 '16 at 14:21
0

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');
    }
  });
});
Adam Hey
  • 1,512
  • 1
  • 20
  • 24
0

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' );
            }
        );
    }

simonl
  • 191
  • 1
  • 3
  • 12
-2

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>
Nope
  • 22,147
  • 7
  • 47
  • 72
Ray
  • 29
  • 1
  • 5
    -1 I do not see how this answer adds any value to the already previous answers given 3 Month ago with the exact same code in the answer. Therefore not a useful answer in my humble opinion. – Nope Dec 04 '12 at 20:19