Skip to content

Commit 1662f11

Browse files
authored
[WEB-2260] Code tabs refactor (DataDog#14260)
* first pass * role navigation * js first pass refactor * Refactors jquery to vanilla js * fixes console error, wrap script in function for async loading * remove role navigation from markup
1 parent c663ab2 commit 1662f11

File tree

5 files changed

+130
-104
lines changed

5 files changed

+130
-104
lines changed

assets/scripts/components/async-loading.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { updateTOC, buildTOCMap } from './table-of-contents';
2-
import codeTabs from './codetabs';
2+
import initCodeTabs from './codetabs';
33
import { redirectToRegion } from '../region-redirects';
44
import { initializeIntegrations } from './integrations';
55
import { initializeSecurityRules } from './security-rules';
@@ -184,7 +184,7 @@ function loadPage(newUrl) {
184184

185185
// sets query params if code tabs are present
186186

187-
codeTabs();
187+
initCodeTabs();
188188

189189
const regionSelector = document.querySelector('.js-region-select');
190190

assets/scripts/components/codetabs.js

Lines changed: 109 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -1,96 +1,122 @@
11
import { getQueryParameterByName } from '../helpers/browser';
22

3-
function codeTabs() {
4-
const tab = getQueryParameterByName('tab');
5-
6-
if ($('.code-tabs').length > 0) {
7-
// page load set code tab titles
8-
$('.code-tabs .tab-content')
9-
.find('.tab-pane')
10-
.each(function () {
11-
const navTabsMobile = $(this)
12-
.closest('.code-tabs')
13-
.find('.nav-tabs-mobile .dropdown-menu');
14-
const navTabs = $(this).closest('.code-tabs').find('.nav-tabs');
15-
const title = $(this).attr('title');
16-
const lang = $(this).data('lang');
17-
navTabs.append(
18-
`<li><a href="#" data-lang="${lang}">${title}</a></li>`
19-
);
20-
navTabsMobile.append(
21-
`<a class="dropdown-item" href="#" data-lang="${lang}">${title}</a>`
22-
);
23-
});
24-
25-
// clicking a tab open them all
26-
$('.code-tabs .nav-tabs a').click(function (e) {
27-
e.preventDefault();
28-
29-
// find all
30-
const lang = $(this).data('lang');
31-
$('.code-tabs .nav-tabs').each(function () {
32-
const navtabs = $(this);
33-
const links = $(this).find('a:first');
34-
const langLinks = $(this).find(`a[data-lang="${lang}"]`);
35-
if (langLinks.length) {
36-
langLinks.each(function () {
37-
activateTab($(this));
38-
});
39-
} else if (navtabs.find('.active').length === 0) {
40-
// set first lang selected if nothing selected
41-
links.each(function () {
42-
activateTab($(this));
43-
});
3+
const initCodeTabs = () => {
4+
const codeTabsList = document.querySelectorAll('.code-tabs')
5+
const tabQueryParameter = getQueryParameterByName('tab')
6+
7+
const init = () => {
8+
renderCodeTabElements()
9+
addEventListeners()
10+
activateTabsOnLoad()
11+
}
12+
13+
/**
14+
* Renders code tabs on the page at run time
15+
*/
16+
const renderCodeTabElements = () => {
17+
if (codeTabsList.length > 0) {
18+
codeTabsList.forEach(codeTabsElement => {
19+
const navTabsElement = codeTabsElement.querySelector('.nav-tabs')
20+
const tabContent = codeTabsElement.querySelector('.tab-content')
21+
const tabPaneNodeList = tabContent.querySelectorAll('.tab-pane')
22+
23+
tabPaneNodeList.forEach(tabPane => {
24+
const title = tabPane.getAttribute('title')
25+
const lang = tabPane.getAttribute('data-lang')
26+
const li = document.createElement('li')
27+
const anchor = document.createElement('a')
28+
anchor.dataset.lang = lang
29+
anchor.href = '#'
30+
anchor.innerText = title
31+
li.appendChild(anchor)
32+
navTabsElement.appendChild(li)
33+
})
34+
})
35+
}
36+
}
37+
38+
/**
39+
* Adds active class to the selected tab and shows the related tab pane.
40+
* @param {object} tabAnchorElement - Reference to the HTML DOM node representing the anchor tag within each tab.
41+
*/
42+
const activateCodeTab = (tabAnchorElement) => {
43+
const activeLang = tabAnchorElement.getAttribute('data-lang')
44+
45+
if (activeLang) {
46+
codeTabsList.forEach(codeTabsElement => {
47+
const tabsList = codeTabsElement.querySelectorAll('.nav-tabs li')
48+
const tabPanesList = codeTabsElement.querySelectorAll('.tab-pane')
49+
const activeLangTab = codeTabsElement.querySelector(`a[data-lang="${activeLang}"]`)
50+
const activePane = codeTabsElement.querySelector(`.tab-pane[data-lang="${activeLang}"]`)
51+
52+
if (activeLangTab && activePane) {
53+
// Hide all tab content and remove 'active' class from all tab elements.
54+
tabsList.forEach(tab => tab.classList.remove('active'))
55+
tabPanesList.forEach(pane => pane.classList.remove('active', 'show'))
56+
57+
// Show the active content and highlight active tab.
58+
activeLangTab.closest('li').classList.add('active')
59+
activePane.classList.add('active', 'show')
4460
}
45-
});
46-
47-
const url = window.location.href
48-
.replace(window.location.hash, '')
49-
.replace(window.location.search, '');
50-
window.history.replaceState(
51-
null,
52-
null,
53-
`${url}?tab=${lang}${window.location.hash}`
54-
);
55-
});
56-
57-
// mobile tabs trigger desktop ones
58-
$('.code-tabs .nav-tabs-mobile .dropdown-menu a').click(function (e) {
59-
e.preventDefault();
60-
const ctabs = $(this).parents('.code-tabs');
61-
const lang = $(this).data('lang');
62-
const desktopTab = ctabs.find(`.nav-tabs a[data-lang="${lang}"]`);
63-
if (desktopTab) {
64-
desktopTab.click();
65-
}
66-
});
61+
62+
const currentActiveTab = codeTabsElement.querySelector('.nav-tabs li.active')
63+
64+
// If we got this far and no tabs are highlighted, activate the first tab in the list of code tabs.
65+
if (!currentActiveTab) {
66+
const firstTab = tabsList.item(0)
67+
const firstTabActiveLang = firstTab.querySelector('a').dataset.lang
68+
const firstTabPane = codeTabsElement.querySelector(`.tab-pane[data-lang="${firstTabActiveLang}"]`)
69+
firstTab.classList.add('active')
70+
firstTabPane.classList.add('active', 'show')
71+
}
72+
})
73+
}
74+
75+
updateUrl(activeLang)
76+
}
6777

68-
if (tab !== '') {
69-
const selectedLanguageTab = document.querySelector(`a[data-lang="${tab}"]`);
78+
const activateTabsOnLoad = () => {
79+
if (tabQueryParameter) {
80+
const selectedLanguageTab = document.querySelector(`a[data-lang="${tabQueryParameter}"]`);
7081

7182
if (selectedLanguageTab) {
72-
selectedLanguageTab.click();
73-
} else {
74-
document.querySelector('.code-tabs .nav-tabs li a').click();
83+
selectedLanguageTab.click()
7584
}
7685
} else {
77-
document.querySelector('.code-tabs .nav-tabs li a').click();
86+
if (codeTabsList.length > 0) {
87+
const firstTab = document.querySelectorAll('.code-tabs .nav-tabs a').item(0)
88+
activateCodeTab(firstTab)
89+
}
7890
}
7991
}
80-
}
8192

82-
function activateTab(el) {
83-
const tab = el.parent();
84-
const tabIndex = tab.index();
85-
const tabPanel = el.closest('.code-tabs');
86-
const tabPane = tabPanel.find('.tab-pane').eq(tabIndex);
87-
tabPanel.find('.active').removeClass('active');
88-
tab.addClass('active');
89-
tabPane.addClass('active');
90-
tabPane.addClass('show');
91-
el.closest('.code-tabs')
92-
.find('.nav-tabs-mobile .title-dropdown')
93-
.text(tab.text());
93+
const addEventListeners = () => {
94+
const allTabLinksNodeList = document.querySelectorAll('.code-tabs li a')
95+
96+
allTabLinksNodeList.forEach(link => {
97+
link.addEventListener('click', () => {
98+
event.preventDefault()
99+
activateCodeTab(link)
100+
})
101+
})
102+
}
103+
104+
/**
105+
* Append the active lang to the URL as a query parameter.
106+
*/
107+
const updateUrl = (activeLang) => {
108+
const url = window.location.href
109+
.replace(window.location.hash, '')
110+
.replace(window.location.search, '');
111+
112+
window.history.replaceState(
113+
null,
114+
null,
115+
`${url}?tab=${activeLang}${window.location.hash}`
116+
);
117+
}
118+
119+
init()
94120
}
95121

96-
export default codeTabs;
122+
export default initCodeTabs;

assets/scripts/datadog-docs.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { initializeIntegrations } from './components/integrations';
22
import { initializeSecurityRules } from './components/security-rules';
33
import { updateTOC, buildTOCMap, onScroll, closeMobileTOC } from './components/table-of-contents';
4-
import codeTabs from './components/codetabs';
4+
import initCodeTabs from './components/codetabs';
55
import configDocs from './config/config-docs';
66
import { loadPage } from './components/async-loading';
77
import { updateMainContentAnchors, gtag } from './helpers/helpers';
@@ -91,7 +91,7 @@ $(document).ready(function () {
9191
}
9292

9393
if (document.querySelector('.code-tabs')) {
94-
codeTabs();
94+
initCodeTabs();
9595
}
9696
});
9797

assets/styles/pages/_global.scss

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -550,7 +550,8 @@ h5 {
550550

551551
& > ul {
552552
list-style-type: none;
553-
padding: 0;
553+
padding-top: 6px;
554+
554555
li {
555556
padding-bottom: 6px;
556557

@@ -568,19 +569,25 @@ h5 {
568569
}
569570
&.active {
570571
a {
571-
color: $ddpurple;
572-
border-bottom: 1px solid $ddpurple;
573-
font-weight: 600;
572+
box-shadow: 2px 5px 12px rgba(0, 0, 0, 0.1);
573+
border-radius: 3px;
574+
background-color: $ddpurple;
575+
color: white;
574576
}
575577
}
576578
a {
577-
color: $ddgray;
579+
background: #ffffff;
580+
border: transparent;
581+
padding: 6px 10px 7px 10px;
578582
font-weight: 600;
579-
padding: 10px 22px 10px 22px;
580-
border-bottom: 1px solid $ddgray;
583+
font-size: 16px;
584+
border-radius: 3px;
581585
display: inline-block;
586+
margin-right: 0.625rem;
587+
582588
&:hover {
583-
color: $ddpurple;
589+
box-shadow: 2px 5px 12px rgba(0, 0, 0, 0.1);
590+
border: none;
584591
}
585592
}
586593
}
@@ -595,7 +602,7 @@ h5 {
595602
padding-left: 0 !important;
596603
}
597604
.tab-content {
598-
padding-top: 26px;
605+
padding-top: 12px;
599606
h2 > a,
600607
h3 > a,
601608
h4 > a,

layouts/shortcodes/tabs.html

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,4 @@
11
<div class='code-tabs'>
2-
<ul class="nav nav-tabs d-none d-sm-flex"></ul>
3-
<ul class="nav nav-tabs-mobile d-block d-sm-none">
4-
<li class="nav-item dropdown">
5-
<a class="nav-link dropdown-toggle title-dropdown" data-toggle="dropdown" href="#" role="button" aria-haspopup="true" aria-expanded="false">Dropdown</a>
6-
<div class="dropdown-menu">
7-
</div>
8-
</li>
9-
</ul>
2+
<ul class="nav nav-tabs d-flex"></ul>
103
<div class="tab-content">{{ .Inner }}</div>
114
</div>

0 commit comments

Comments
 (0)