Reputation: 563
I'm playing around with a Javascript tab system.
As the window width falls below a certain breakpoint the tab content inserts after the links for an accordion layout, and the process is reversed as you increase the window width.
Everything works as intended as you reduce the window size, but breaks as you increase the window width. The following code is causing the issue tabBody.appendChild( tabBodyItems[k] );
.
Only 2 of the 3 .c-Tabs_BodyItem
elements append???
HTML
<div class="c-Tabs" id="js-Tabs">
<div class="o-Container">
<div class="o-Row c-Tabs_Head">
<div class="o-Col-sm-4 c-Tabs_HeadItem">
<a href="" class="c-Tabs_Link">Link 1</a>
</div>
<div class="o-Col-sm-4 c-Tabs_HeadItem is-active">
<a href="" class="c-Tabs_Link">Link 2</a>
</div>
<div class="o-Col-sm-4 c-Tabs_HeadItem">
<a href="" class="c-Tabs_Link">Link 3</a>
</div>
</div>
<div class="c-Tabs_Body">
<div class="c-Tabs_BodyItem">
<ul>
<li>Lorem ipsum dolor sit amet, consectetur adipisicing elit.</li>
<li>Lorem ipsum dolor sit amet, consectetur adipisicing elit.</li>
</ul>
</div>
<div class="c-Tabs_BodyItem is-active">
<ul>
<li>Lorem ipsum dolor sit amet, consectetur adipisicing elit.</li>
<li>Lorem ipsum dolor sit amet, consectetur adipisicing elit.</li>
</ul>
</div>
<div class="c-Tabs_BodyItem">
<ul>
<li>Lorem ipsum dolor sit amet, consectetur adipisicing elit.</li>
<li>Lorem ipsum dolor sit amet, consectetur adipisicing elit.</li>
</ul>
</div>
</div>
</div>
</div>
Javascript
I won't show the Javascript for the actual tabbing system; this isn't causing the issue.
function insertAfter( el, referenceNode )
{
referenceNode.parentNode.insertBefore( el, referenceNode.nextSibling );
}
function debounce( func, wait, immediate )
{
var timeout;
return function() {
var context = this, args = arguments;
var later = function() {
timeout = null;
if (!immediate) func.apply(context, args);
};
var callNow = immediate && !timeout;
clearTimeout(timeout);
timeout = setTimeout(later, wait);
if (callNow) func.apply(context, args);
};
}
var tabHeadItems = document.getElementsByClassName( 'c-Tabs_HeadItem' );
var tabBody = document.querySelector( '.c-Tabs_Body' );
var tabBodyItems = document.getElementsByClassName( 'c-Tabs_BodyItem' );
var isTabs = true;
function setTab()
{
if ( window.matchMedia( '(min-width: 768px)' ).matches && isTabs === false )
{
for( var k = 0, lenK = tabBodyItems.length; k < lenK; k++ )
{
tabBody.appendChild( tabBodyItems[k] );
}
isTabs = true;
}
else if ( window.matchMedia( '(max-width: 767px)' ).matches && isTabs === true )
{
for( var l = 0, lenL = tabBodyItems.length; l < lenL; l++ )
{
insertAfter( tabBodyItems[l], tabHeadItems[l] );
}
isTabs = false;
}
}
setTab();
var debounceSetTab = debounce(function() {
setTab();
}, 250 );
window.addEventListener( 'resize', debounceSetTab );
Upvotes: 4
Views: 998
Reputation: 781721
The reason is that getElementsByClassName
returns a live NodeList
. This means that as the DOM changes, the content of the NodeList
changes to reflect it. The order of the elements tabBodyItems
is based on their positions in the DOM, so when you move the elements around in the DOM, their indexes in tabBodyItems
change to reflect this. This is happening while you're looping through the collection in your for
loop, so you're skipping elements because they get renumbered.
The simplest fix to this is to use document.querySelectorAll
instead of document.getElementsByClassName
. This returns a static NodeList
.
function insertAfter( el, referenceNode )
{
referenceNode.parentNode.insertBefore( el, referenceNode.nextSibling );
}
function debounce( func, wait, immediate )
{
var timeout;
return function() {
var context = this, args = arguments;
var later = function() {
timeout = null;
if (!immediate) func.apply(context, args);
};
var callNow = immediate && !timeout;
clearTimeout(timeout);
timeout = setTimeout(later, wait);
if (callNow) func.apply(context, args);
};
}
var tabHeadItems = document.querySelectorAll( '.c-Tabs_HeadItem' );
var tabBody = document.querySelector( '.c-Tabs_Body' );
var tabBodyItems = document.querySelectorAll( '.c-Tabs_BodyItem' );
var isTabs = true;
function setTab()
{
if ( window.matchMedia( '(min-width: 768px)' ).matches && isTabs === false )
{
for( var k = 0, lenK = tabBodyItems.length; k < lenK; k++ )
{
tabBody.appendChild( tabBodyItems[k] );
}
isTabs = true;
}
else if ( window.matchMedia( '(max-width: 767px)' ).matches && isTabs === true )
{
for( var l = 0, lenL = tabBodyItems.length; l < lenL; l++ )
{
insertAfter( tabBodyItems[l], tabHeadItems[l] );
}
isTabs = false;
}
}
setTab();
var debounceSetTab = debounce(function() {
setTab();
}, 250 );
window.addEventListener( 'resize', debounceSetTab );
<div class="c-Tabs" id="js-Tabs">
<div class="o-Container">
<div class="o-Row c-Tabs_Head">
<div class="o-Col-sm-4 c-Tabs_HeadItem">
<a href="" class="c-Tabs_Link">Link 1</a>
</div>
<div class="o-Col-sm-4 c-Tabs_HeadItem is-active">
<a href="" class="c-Tabs_Link">Link 2</a>
</div>
<div class="o-Col-sm-4 c-Tabs_HeadItem">
<a href="" class="c-Tabs_Link">Link 3</a>
</div>
</div>
<div class="c-Tabs_Body">
<div class="c-Tabs_BodyItem" id="body1">
<ul>
<li>Lorem ipsum dolor sit amet, consectetur adipisicing elit.</li>
<li>Lorem ipsum dolor sit amet, consectetur adipisicing elit.</li>
</ul>
</div>
<div class="c-Tabs_BodyItem is-active" id="body2">
<ul>
<li>Lorem ipsum dolor sit amet, consectetur adipisicing elit.</li>
<li>Lorem ipsum dolor sit amet, consectetur adipisicing elit.</li>
</ul>
</div>
<div class="c-Tabs_BodyItem"id="body3">
<ul>
<li>Lorem ipsum dolor sit amet, consectetur adipisicing elit.</li>
<li>Lorem ipsum dolor sit amet, consectetur adipisicing elit.</li>
</ul>
</div>
</div>
</div>
</div>
Upvotes: 4