Reputation: 5483
I want to control two differnt tab contents with a single tab navigation.
In Bootstra 3 I had a solution with comma separated data targets (like in this example: https://stackoverflow.com/a/19719859/1788961).
But in Bootsrap 4, this way is not working anymore for tabs.
The collapse component could work with multiple targets, but I couldn't use it for the tabs: https://getbootstrap.com/docs/4.0/components/collapse/#multiple-targets
Is there an other way to do this?
Here's my code:
<div class="B">
<div class="container">
<div class="tab-content" id="ueberTabA">
<div class="tab-pane fade" id="panel_a_first" role="tabpanel" aria-labelledby="first-tab">
A First
</div>
<div class="tab-pane fade show active" id="panel_a_second" role="tabpanel" aria-labelledby="second-tab">
A Second
</div>
<div class="tab-pane fade" id="panel_a_third" role="tabpanel" aria-labelledby="third-tab">
A Third
</div>
</div>
</div>
</div>
<div class="container">
<ul class="nav nav-tabs" id="ueberTab" role="tablist">
<li class="nav-item"><a class="nav-link" id="first-tab" data-target="#panel_b_first, #panel_a_first" data-toggle="tab" href="#first" role="tab" aria-controls="first" aria-selected="false">first</a></li>
<li class="nav-item"><a class="nav-link active" id="second-tab" data-target="#panel_b_second, #panel_a_second" data-toggle="tab" href="#second" role="tab" aria-controls="second" aria-selected="true">second</a></li>
<li class="nav-item"><a class="nav-link" id="third-tab" data-target="#panel_b_thrid, #panel_a_third" data-toggle="tab" href="#third" role="tab" aria-controls="third" aria-selected="false">Unser third</a></li>
</ul>
<div class="tab-content" id="ueberTabB">
<div class="tab-pane fade" id="panel_b_first" role="tabpanel" aria-labelledby="first-tab">
B First
</div>
<div class="tab-pane fade show active" id="panel_b_second" role="tabpanel" aria-labelledby="second-tab">
B Second
</div>
<div class="tab-pane fade" id="panel_b_thrid" role="tabpanel" aria-labelledby="third-tab">
B Third
</div>
</div>
</div>
Upvotes: 8
Views: 14176
Reputation: 534
I guess it's worth to widen a bit Carol Skelly's answer to more than 2 target tab panels and keep built-in hide/show animation working. BS 5 exposes only fade animation so let's write a proof of concept for it.
Besides, BS 5 provides a showcase with nav links, that you may get rid of if you need more complex layouts.
So here is a proof of concept with minimal markup :
/**
* Allow a bootstrap single nav tab to target multiple tab panels
*/
document.querySelectorAll( '[data-bs-toggle="tab"][data-bs-target]' ).forEach( ( navTab ) => {
const targetPanes = document.querySelectorAll( navTab.dataset.bsTarget )
if( targetPanes.length > 1 ) {
navTab.addEventListener( 'show.bs.tab', ( e ) => {
const activeNav = navTab.closest( '[role="tablist"]' ).querySelector( '.active[role="tab"]' )
const activePanes = document.querySelectorAll( activeNav.dataset.bsTarget )
// Hide other panes
activePanes.forEach( ( activePane ) => {
// Ensure selector targets only tab panels
if( activePane.matches( '[role="tabpanel"]' ) ) {
const transProperty = activePane.classList.contains( 'fade' ) ? 'opacity' : null
const outTransDuration = transProperty ? utils.toMilliseconds( utils.getComputedTransitions( activePane, '', transProperty ).duration ) : 0
// Remove class making pane visible
activePane.classList.remove( 'show' )
// Wait for transition...
setTimeout( () => {
// Then hide pane
activePane.classList.remove( 'active' )
targetPanes.forEach( ( targetPane ) => {
// Ensure selector targets only tab panels
if( targetPane.matches( '[role="tabpanel"]' ) ) {
const transProperty = targetPane.classList.contains( 'fade' ) ? 'opacity' : null
const inTransDuration = transProperty ? utils.toMilliseconds( utils.getComputedTransitions( targetPane, '', transProperty ).duration ) : 0
// Show pane
targetPane.classList.add( 'active' )
// Can't figure why, but this ensures animation of target panes beyond first lasts as long as animation of first
setTimeout( () => {
targetPane.classList.add( 'show' )
}, 0 )
}
} )
}, outTransDuration )
}
} )
} )
}
} )
/**
* Utils to get transition duration of a CSS property
*/
const utils = {
getComputedTransitions: ( elem, pseudo, property ) => {
const computedStyles = window.getComputedStyle( elem, pseudo )
const delays = computedStyles.getPropertyValue( 'transition-delay' ).split( ', ' )
const durations = computedStyles.getPropertyValue( 'transition-duration' ).split( ', ' )
const properties = computedStyles.getPropertyValue( 'transition-property' ).split( ', ' )
const timingFunctions = computedStyles.getPropertyValue( 'transition-timing-function' ).split( ', ' )
let transitions = {}
for( let i in properties ) {
transitions[properties[i] || 'all'] = {
duration: durations[i],
delay: delays[i],
timingFunction: timingFunctions[i],
}
}
if( property ) {
return transitions[property] ? transitions[property] : transitions['all']
}
return transitions
},
toMilliseconds: ( string ) => {
if( string.match( /^[0-9.]+s$/g ) ) {
return parseFloat( string ) * 1000
}
if( string.match( /^[0-9.]+ms$/g ) ) {
return parseFloat( string )
}
return string
}
}
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet"/>
<div role="tablist">
<button type="button" class="active" data-bs-toggle="tab" data-bs-target=".pane-first" role="tab" aria-controls="pane-first" aria-selected="true">Tab first</button>
<button type="button" data-bs-toggle="tab" data-bs-target=".pane-second" role="tab" aria-controls="pane-first" aria-selected="false">Tab second</button>
</div>
...
<div class="tab-content">
<div class="tab-pane fade pane-first active show" role="tabpanel">Block 1, pane first</div>
<div class="tab-pane fade pane-second" role="tabpanel">Block 1, pane second</div>
</div>
...
<div class="tab-content">
<div class="tab-pane fade pane-first active show" role="tabpanel">Block 2, pane first</div>
<div class="tab-pane fade pane-second" role="tabpanel">Block 2, pane second</div>
</div>
Fade transitions are not really synchronous. I guess native BS feature interferes when block 1 tabpanels toggle.
Upvotes: 0
Reputation: 362390
Bootstrap 5 (update 2021)
This is not possible with Tabs "out of the box". However, JS could be used to show secondary tab-panes...
document.querySelectorAll('button[data-bs-toggle="tab"]').forEach((t,i)=>{
t.addEventListener('show.bs.tab', function (e) {
let targetClass = t.dataset.bsTarget
var pane = document.querySelector('#secondTabContent '+targetClass)
var sibling = document.querySelector('#secondTabContent .tab-pane.active')
// hide 2nd pane sibling
sibling.classList.remove('show')
sibling.classList.remove('active')
// show 2nd pane
pane.classList.add('show')
pane.classList.add('active')
})
})
Bootstrap 5 - multiple Tab panes from single Nav
Bootstrap 4 (original answer)
This is not possible in Bootstrap 4.0.0. This is currently an open issue, and a possible "idea" for Bootstrap 4.1.
https://github.com/twbs/bootstrap/issues/19964
Upvotes: 6
Reputation: 31
I've actually found a way to make this work. It's not the most beautiful solution but it does make the job.
Here is what you'll need to do:
$(document).on('click', '#ueberTab a', function(e) {
otherTabs = $(this).attr('data-secondary').split(',');
for(i= 0; i<otherTabs.length;i++) {
nav = $('<ul class="nav d-none" id="tmpNav"></ul>');
nav.append('<li class="nav-item"><a href="#" data-toggle="tab" data-target="' + otherTabs[i] + '">nav</a></li>"');
nav.find('a').tab('show');
}
});
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script><div class="B">
<div class="container">
<div class="tab-content" id="ueberTabA">
<div class="tab-pane fade" id="panel_a_first" role="tabpanel" aria-labelledby="first-tab">
A First
</div>
<div class="tab-pane fade show active" id="panel_a_second" role="tabpanel" aria-labelledby="second-tab">
A Second
</div>
<div class="tab-pane fade" id="panel_a_third" role="tabpanel" aria-labelledby="third-tab">
A Third
</div>
</div>
</div>
</div>
<div class="container">
<ul class="nav nav-tabs" id="ueberTab" role="tablist">
<li class="nav-item"><a class="nav-link" id="first-tab" data-target="#panel_b_first" data-secondary="#panel_a_first" data-toggle="tab" href="#first" role="tab" aria-controls="first" aria-selected="false">first</a></li>
<li class="nav-item"><a class="nav-link active" id="second-tab" data-target="#panel_b_second" data-secondary="#panel_a_second" data-toggle="tab" href="#second" role="tab" aria-controls="second" aria-selected="true">second</a></li>
<li class="nav-item"><a class="nav-link" id="third-tab" data-target="#panel_b_thrid" data-secondary="#panel_a_third" data-toggle="tab" href="#third" role="tab" aria-controls="third" aria-selected="false">Unser third</a></li>
</ul>
<div class="tab-content" id="ueberTabB">
<div class="tab-pane fade" id="panel_b_first" role="tabpanel" aria-labelledby="first-tab">
B First
</div>
<div class="tab-pane fade show active" id="panel_b_second" role="tabpanel" aria-labelledby="second-tab">
B Second
</div>
<div class="tab-pane fade" id="panel_b_thrid" role="tabpanel" aria-labelledby="third-tab">
B Third
</div>
</div>
</div>
Upvotes: 3