I need to create tabs that reveal content when clicked. I have found some code that provides the functionality I need from this W3Schools Tutorial, although I want to avoid using the inline JavaScript to call the openCity() function. In the original code, each tab button was defined like so:
<button class="tablinks" onclick="openCity(event, 'London')">London</button>
This works fine, but I would much rather avoid using JavaScript like this in any code if I can. It looks like the event.currentTarget property is one of the most important parts here for adding the .active class to the tabs when clicked, although I was wondering if there was another way to carry out the same process.
I have tried several different solutions but still have not been able to find the best one. In previous attempts I had a few errors such as an Uncaught TypeError for not being able to access the property of an undefined value. Now, no errors arise, but the page is just stuck on the first tab and the others do not work.
In the latest attempt, I tried looping through each tab and added an event listener to call the function on them individually, but am still new to using JavaScript and am unsure on what else to do. Please excuse any silly mistakes!
Please find the code below (HTML, CSS, and JavaScript).
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
body {
font-family: Arial;
}
.tab {
overflow: hidden;
border: 1px solid #ccc;
background-color: #f1f1f1;
}
.tab button {
background-color: inherit;
float: left;
border: none;
outline: none;
cursor: pointer;
padding: 14px 16px;
transition: 0.3s;
font-size: 17px;
}
.tab button:hover {
background-color: #ddd;
}
.tab button.active {
background-color: #ccc;
}
.tabcontent {
display: none;
padding: 6px 12px;
border: 1px solid #ccc;
border-top: none;
}
</style>
</head>
<body>
<div class="tab">
<button class="tablinks">London</button>
<button class="tablinks">Paris</button>
<button class="tablinks">Tokyo</button>
</div>
<div id="London" class="tabcontent">
<h3>London</h3>
<p>London is the capital city of England.</p>
</div>
<div id="Paris" class="tabcontent">
<h3>Paris</h3>
<p>Paris is the capital of France.</p>
</div>
<div id="Tokyo" class="tabcontent">
<h3>Tokyo</h3>
<p>Tokyo is the capital of Japan.</p>
</div>
<script>
var i, tabcontent, tablinks;
tablinks = document.getElementsByClassName("tablinks");
for (var i = 0; i < tablinks.length; i++) {
var name = tablinks[i].innerHTML;
console.log(name);
tablinks[i].addEventListener('click', openCity(name));
}
function openCity(cityName) {
tabcontent = document.getElementsByClassName("tabcontent");
for (i = 0; i < tabcontent.length; i++) {
tabcontent[i].style.display = "none";
}
for (i = 0; i < tablinks.length; i++) {
tablinks[i].className = tablinks[i].className.replace(" active", "");
}
document.getElementById(cityName).style.display = "block";
this.className += ' active';
// event.currentTarget.className += " active";
}
</body>
I had other ideas without using data attributes, but i think it's a correct way.
Probably code can looks like that:
let handleClick = e => {
Array.from(document.querySelectorAll(".active"), e => e.classList.remove("active")); // remove `active` class from every elements which contains him.
e.target.classList.add("active");
document.querySelector(`div.tabcontent[data-id*="${e.target.dataset.id}"]`).classList.add("active");
};
Array.from(document.getElementsByClassName("tablinks"), btn => btn.addEventListener('click', handleClick, false));
.tab {
overflow: hidden;
border: 1px solid #ccc;
background-color: #f1f1f1;
}
.tab button {
background-color: inherit;
float: left;
border: none;
outline: none;
cursor: pointer;
padding: 14px 16px;
transition: 0.3s;
font-size: 17px;
}
.tab button:hover {
background-color: #ddd;
}
.tab button.active {
background-color: #ccc;
}
.tabcontent {
display: none;
padding: 6px 12px;
border: 1px solid #ccc;
border-top: none;
}
.active {
display: block;
}
<div class="tab">
<button class="tablinks" data-id="1">London</button>
<button class="tablinks" data-id="2">Paris</button>
<button class="tablinks" data-id="3">Tokyo</button>
</div>
<div data-id="1" class="tabcontent">
<h3>London</h3>
<p>London is the capital city of England.</p>
</div>
<div data-id="2" class="tabcontent">
<h3>Paris</h3>
<p>Paris is the capital of France.</p>
</div>
<div data-id="3" class="tabcontent">
<h3>Tokyo</h3>
<p>Tokyo is the capital of Japan.</p>
</div>
Related
I'm using the w3schools how-to on tabs. (https://www.w3schools.com/howto/howto_js_tabs.asp) I noticed that their example does not allow for tabs to be untoggled and hide the text when pressed again. Can someone briefly explain how I would be able to integrate that into my code if I'm following theirs? Thanks.
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
body {font-family: Arial;}
/* Style the tab */
.tab {
overflow: hidden;
border: 1px solid #ccc;
background-color: #f1f1f1;
}
/* Style the buttons inside the tab */
.tab button {
background-color: inherit;
float: left;
border: none;
outline: none;
cursor: pointer;
padding: 14px 16px;
transition: 0.3s;
font-size: 17px;
}
/* Change background color of buttons on hover */
.tab button:hover {
background-color: #ddd;
}
/* Create an active/current tablink class */
.tab button.active {
background-color: #ccc;
}
/* Style the tab content */
.tabcontent {
display: none;
padding: 6px 12px;
border: 1px solid #ccc;
border-top: none;
}
</style>
</head>
<body>
<h2>Tabs</h2>
<p>Click on the buttons inside the tabbed menu:</p>
<div class="tab">
<button class="tablinks" onclick="openCity(event, 'London')">London</button>
<button class="tablinks" onclick="openCity(event, 'Paris')">Paris</button>
<button class="tablinks" onclick="openCity(event, 'Tokyo')">Tokyo</button>
</div>
<div id="London" class="tabcontent">
<h3>London</h3>
<p>London is the capital city of England.</p>
</div>
<div id="Paris" class="tabcontent">
<h3>Paris</h3>
<p>Paris is the capital of France.</p>
</div>
<div id="Tokyo" class="tabcontent">
<h3>Tokyo</h3>
<p>Tokyo is the capital of Japan.</p>
</div>
<script>
function openCity(evt, cityName) {
var i, tabcontent, tablinks;
tabcontent = document.getElementsByClassName("tabcontent");
for (i = 0; i < tabcontent.length; i++) {
tabcontent[i].style.display = "none";
}
tablinks = document.getElementsByClassName("tablinks");
for (i = 0; i < tablinks.length; i++) {
tablinks[i].className = tablinks[i].className.replace(" active", "");
}
document.getElementById(cityName).style.display = "block";
evt.currentTarget.className += " active";
}
</script>
</body>
</html>
Not sure if I understand correctly, but what you describe is not the "expected" behaviour of a tabbed interface. Why would you want content to be hidden? In a tabbed interface (think navigation), there is usually always one tab active, showing it's content, so clicking the active tab should simply do nothing and not hide the content (at least in conventional tabbed interfaces). Maybe you are looking more for an accordion-like behaviour?
In other words, the behaviour of the (notoriously crappy) example from W3Schools is not how a tabbed interface is supposed to work, when initialized, no content is shown, the user needs to click a tab first to see something, that's not good UX... the first tab should already be active!
But if you really want to go for this behaviour, it could be done like this:
function openCity(evt, cityName) {
// Declare all variables
var i, tabcontent, tablinks;
if ( evt.currentTarget.classList.contains('active') ) {
document.getElementById(cityName).style.display = "none";
evt.currentTarget.classList.remove('active');
} else {
// Get all elements with class="tabcontent" and hide them
tabcontent = document.getElementsByClassName("tabcontent");
for (i = 0; i < tabcontent.length; i++) {
tabcontent[i].style.display = "none";
}
// Get all elements with class="tablinks" and remove the class "active"
tablinks = document.getElementsByClassName("tablinks");
for (i = 0; i < tablinks.length; i++) {
tablinks[i].className = tablinks[i].className.replace(" active", "");
}
// Show the current tab, and add an "active" class to the button that opened the tab
document.getElementById(cityName).style.display = "block";
evt.currentTarget.className += " active";
}
}
/* Style the tab */
.tab {
overflow: hidden;
border: 1px solid #ccc;
background-color: #f1f1f1;
}
/* Style the buttons that are used to open the tab content */
.tab button {
background-color: inherit;
float: left;
border: none;
outline: none;
cursor: pointer;
padding: 14px 16px;
transition: 0.3s;
}
/* Change background color of buttons on hover */
.tab button:hover {
background-color: #ddd;
}
/* Create an active/current tablink class */
.tab button.active {
background-color: #ccc;
}
/* Style the tab content */
.tabcontent {
display: none;
padding: 6px 12px;
border: 1px solid #ccc;
border-top: none;
}
<!-- Tab links -->
<div class="tab">
<button class="tablinks" onclick="openCity(event, 'London')">London</button>
<button class="tablinks" onclick="openCity(event, 'Paris')">Paris</button>
<button class="tablinks" onclick="openCity(event, 'Tokyo')">Tokyo</button>
</div>
<!-- Tab content -->
<div id="London" class="tabcontent">
<h3>London</h3>
<p>London is the capital city of England.</p>
</div>
<div id="Paris" class="tabcontent">
<h3>Paris</h3>
<p>Paris is the capital of France.</p>
</div>
<div id="Tokyo" class="tabcontent">
<h3>Tokyo</h3>
<p>Tokyo is the capital of Japan.</p>
</div>
There are different approaches that you could take. You could use if else statements to check whether an element in the for loops contains a specific CSS style or class name so in this case active. Then you may disable it by changing its style through JS when the condition is met.
I'm working with templates, so I can only edit so much of the environment. I have a tabbed navigation set up. Within one of the tabs, I have a div that I want to have a fixed position that is outside/on top of everything else on the page.
This isn't the actual code I'm working with, but it's similar enough that I can use it as an example. The "outsidediv" div is placed in the "London" div. But I want to make it so you see outsidediv even if you click into the Paris or Tokyo tabs.
I've tried using z-index and every kind of position property I can think of to get this to work. I cannot move the outsidediv from where it is.
.tab {
overflow: hidden;
border: 1px solid #ccc;
background-color: #f1f1f1;
}
.tab button {
background-color: inherit;
float: left;
border: none;
outline: none;
cursor: pointer;
padding: 14px 16px;
transition: 0.3s;
font-size: 17px;
}
.tab button:hover {
background-color: #ddd;
}
.tab button.active {
background-color: #ccc;
}
.tabcontent {
display: none;
padding: 6px 12px;
border: 1px solid #ccc;
border-top: none;
}
#outsidediv {
position: fixed;
top: 80px;
right: 50px;
background: pink;
}
<div class="tab">
<button class="tablinks" onclick="openCity(event, 'London')">London</button>
<button class="tablinks" onclick="openCity(event, 'Paris')">Paris</button>
<button class="tablinks" onclick="openCity(event, 'Tokyo')">Tokyo</button>
</div>
<div id="London" class="tabcontent">
<h3>London</h3>
<p>London is the capital city of England.</p>
<div id="outsidediv">This div should appear no matter which tab is selected.</div>
</div>
<div id="Paris" class="tabcontent">
<h3>Paris</h3>
<p>Paris is the capital of France.</p>
</div>
<div id="Tokyo" class="tabcontent">
<h3>Tokyo</h3>
<p>Tokyo is the capital of Japan.</p>
</div>
<script>
function openCity(evt, cityName) {
var i, tabcontent, tablinks;
tabcontent = document.getElementsByClassName("tabcontent");
for (i = 0; i < tabcontent.length; i++) {
tabcontent[i].style.display = "none";
}
tablinks = document.getElementsByClassName("tablinks");
for (i = 0; i < tablinks.length; i++) {
tablinks[i].className = tablinks[i].className.replace(" active", "");
}
document.getElementById(cityName).style.display = "block";
evt.currentTarget.className += " active";
}
</script>
I don't know that there is any other way to do this other than to move the div so that it's outside of the tab, and give it a very high z-index because once you hide a tab with display:none the entire tab (and its descendants) won't be rendered.
If you can't modify the HTML template structure, you could use JavaScript to move it with this one line:
document.body.appendChild(document.getElementById("outsidediv"));
I've also modified/updated your existing JavaScript code so that you aren't using .getElementsByClassName() or using inline HTML event attributes, which makes the code much more efficient and more in line with modern development methodologies. See the comments in the code for details.
/* Just add this class when you want to hide something
and remove when you want to show */
.hidden { display:none; }
.tab {
overflow: hidden;
border: 1px solid #ccc;
background-color: #f1f1f1;
}
.tab button {
background-color: inherit;
float: left;
border: none;
outline: none;
cursor: pointer;
padding: 14px 16px;
transition: 0.3s;
font-size: 17px;
}
.tab button:hover {
background-color: #ddd;
}
.tab button.active {
background-color: #ccc;
}
.tabcontent {
padding: 6px 12px;
border: 1px solid #ccc;
border-top: none;
}
#outsidediv {
position: fixed;
z-index:9999;
top: 80px;
right: 50px;
background: pink;
}
<div class="tab">
<!-- Don't use inline HTML event attributes. Do your JavaScript in JavaScript-->
<button class="tablinks">London</button>
<button class="tablinks">Paris</button>
<button class="tablinks">Tokyo</button>
</div>
<div id="London" class="tabcontent hidden">
<h3>London</h3>
<p>London is the capital city of England.</p>
<div id="outsidediv">This div should appear no matter which tab is selected.</div>
</div>
<div id="Paris" class="tabcontent hidden">
<h3>Paris</h3>
<p>Paris is the capital of France.</p>
</div>
<div id="Tokyo" class="tabcontent hidden">
<h3>Tokyo</h3>
<p>Tokyo is the capital of Japan.</p>
</div>
<script>
// Move the div that should stay on top to just after the tab divs
document.body.appendChild(document.getElementById("outsidediv"));
// Just set your event up at the parent of the buttons
// and use event delegation to leverage event bubbling
document.querySelector(".tab").addEventListener("click", openCity);
function openCity(event) {
// Never, never, never use `getElementsByClassName()`
// and instead, use `.querySelectorAll()`. You can
// then loop through the resulting collection with
// `.forEach()`, which makes looping without indexes
// possible
// Hide all the tab conttent
document.querySelectorAll(".tabcontent").forEach(function(tab){
// Avoid inline styles when possible and just apply/remove classes
tab.classList.add("hidden");
});
// Remove the active tab
document.querySelectorAll(".tablinks").forEach(function(tabLink){
tabLink.classList.remove("active");
});
// Loop over the content again
document.querySelectorAll(".tabcontent").forEach(function(tab){
// If the text of the clicked button matches the text of the tab header...
if(event.target.textContent === tab.querySelector("h3").textContent){
tab.classList.remove("hidden"); // Unhide the tabcontent
event.target.classList.add("active"); // Make the tab active
}
});
}
</script>
You can make an additional function to add the outsidediv. Before adding the element, first check if it exists on the page and if so remove it, then add it to the current active tab element.
Check below:
function openCity(evt, cityName) {
var i, tabcontent, tablinks;
var div = document.getElementById("outsidediv");
if(div !== null) { // if outsidediv exists on the page, remove it
div.remove();
}
tabcontent = document.getElementsByClassName("tabcontent");
for (i = 0; i < tabcontent.length; i++) {
tabcontent[i].style.display = "none";
}
tablinks = document.getElementsByClassName("tablinks");
for (i = 0; i < tablinks.length; i++) {
tablinks[i].className = tablinks[i].className.replace(" active", "");
}
let cityDiv = document.getElementById(cityName); // made cityDiv variable
cityDiv.style.display = "block";
evt.currentTarget.className += " active";
cityDiv.append( makeOutsideDiv() ); // add the outsidediv to the active element
}
function makeOutsideDiv() {
let div = document.createElement("div");
div.innerHTML = "This div should appear no matter which tab is selected.";
div.id = "outsidediv";
return div;
}
.tab {
overflow: hidden;
border: 1px solid #ccc;
background-color: #f1f1f1;
}
.tab button {
background-color: inherit;
float: left;
border: none;
outline: none;
cursor: pointer;
padding: 14px 16px;
transition: 0.3s;
font-size: 17px;
}
.tab button:hover {
background-color: #ddd;
}
.tab button.active {
background-color: #ccc;
}
.tabcontent {
display: none;
padding: 6px 12px;
border: 1px solid #ccc;
border-top: none;
}
#outsidediv {
position: fixed;
top: 80px;
right: 50px;
background: pink;
}
<div class="tab">
<button class="tablinks" onclick="openCity(event, 'London')">London</button>
<button class="tablinks" onclick="openCity(event, 'Paris')">Paris</button>
<button class="tablinks" onclick="openCity(event, 'Tokyo')">Tokyo</button>
</div>
<div id="London" class="tabcontent">
<h3>London</h3>
<p>London is the capital city of England.</p>
<div id="outsidediv">This div should appear no matter which tab is selected.</div>
</div>
<div id="Paris" class="tabcontent">
<h3>Paris</h3>
<p>Paris is the capital of France.</p>
</div>
<div id="Tokyo" class="tabcontent">
<h3>Tokyo</h3>
<p>Tokyo is the capital of Japan.</p>
</div>
I used visibility instead of display, and each inactive tab I am setting position:fixed and sending it way above the rendered area of the page (out of sight and out of mind). Then when the tab is clicked again I'm setting it back to position:relative with no offset.
Here's the fiddle: https://jsfiddle.net/tm1wyax6/1/
<div class="tab">
<button class="tablinks" onclick="openCity(event, 'London')">London</button>
<button class="tablinks" onclick="openCity(event, 'Paris')">Paris</button>
<button class="tablinks" onclick="openCity(event, 'Tokyo')">Tokyo</button>
</div>
<div id="London" class="tabcontent">
<h3>London</h3>
<p>London is the capital city of England.</p>
<div id="outsidediv">This div should appear no matter which tab is selected.</div>
</div>
<div id="Paris" class="tabcontent">
<h3>Paris</h3>
<p>Paris is the capital of France.</p>
</div>
<div id="Tokyo" class="tabcontent">
<h3>Tokyo</h3>
<p>Tokyo is the capital of Japan.</p>
</div>
<script>
function openCity(evt, cityName) {
var i, tabcontent, tablinks;
tabcontent = document.getElementsByClassName("tabcontent");
for (i = 0; i < tabcontent.length; i++) {
tabcontent[i].style.visibility = "hidden";
tabcontent[i].style.position = "fixed";
tabcontent[i].style.top = "-1000px";
}
tablinks = document.getElementsByClassName("tablinks");
for (i = 0; i < tablinks.length; i++) {
tablinks[i].className = tablinks[i].className.replace(" active", "");
}
document.getElementById(cityName).style.visibility = "visible";
document.getElementById(cityName).style.position = "relative";
document.getElementById(cityName).style.top = "0";
evt.currentTarget.className += " active";
}
</script>
CSS:
.tab {
overflow: hidden;
border: 1px solid #ccc;
background-color: #f1f1f1;
}
.tab button {
background-color: inherit;
float: left;
border: none;
outline: none;
cursor: pointer;
padding: 14px 16px;
transition: 0.3s;
font-size: 17px;
}
.tab button:hover {
background-color: #ddd;
}
.tab button.active {
background-color: #ccc;
}
.tabcontent {
visibility: hidden;
padding: 6px 12px;
border: 1px solid #ccc;
border-top: none;
}
#outsidediv {
position: fixed;
top: 80px;
right: 50px;
background: pink;
visibility: visible;
}
I'm using w3schools's vertical tab menu to create a "How it works" menu like this: https://www.canva.com/graphs/
How can I loop through each tab to show the tabcontent div on a timer (while still letting users click the tabs and manually navigate)?
Thank you for any help!
The code from w3schools's vertical tabs:
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
* {box-sizing: border-box}
body {font-family: "Lato", sans-serif;}
/* Style the tab */
.tab {
float: left;
border: 1px solid #ccc;
background-color: #f1f1f1;
width: 30%;
height: 300px;
}
/* Style the buttons inside the tab */
.tab button {
display: block;
background-color: inherit;
color: black;
padding: 22px 16px;
width: 100%;
border: none;
outline: none;
text-align: left;
cursor: pointer;
transition: 0.3s;
font-size: 17px;
}
/* Change background color of buttons on hover */
.tab button:hover {
background-color: #ddd;
}
/* Create an active/current "tab button" class */
.tab button.active {
background-color: #ccc;
}
/* Style the tab content */
.tabcontent {
float: left;
padding: 0px 12px;
border: 1px solid #ccc;
width: 70%;
border-left: none;
height: 300px;
}
</style>
</head>
<body>
<h2>Vertical Tabs</h2>
<p>Click on the buttons inside the tabbed menu:</p>
<div class="tab">
<button class="tablinks" onclick="openCity(event, 'London')" id="defaultOpen">London</button>
<button class="tablinks" onclick="openCity(event, 'Paris')">Paris</button>
<button class="tablinks" onclick="openCity(event, 'Tokyo')">Tokyo</button>
</div>
<div id="London" class="tabcontent">
<h3>London</h3>
<p>London is the capital city of England.</p>
</div>
<div id="Paris" class="tabcontent">
<h3>Paris</h3>
<p>Paris is the capital of France.</p>
</div>
<div id="Tokyo" class="tabcontent">
<h3>Tokyo</h3>
<p>Tokyo is the capital of Japan.</p>
</div>
<script>
function openCity(evt, cityName) {
var i, tabcontent, tablinks;
tabcontent = document.getElementsByClassName("tabcontent");
for (i = 0; i < tabcontent.length; i++) {
tabcontent[i].style.display = "none";
}
tablinks = document.getElementsByClassName("tablinks");
for (i = 0; i < tablinks.length; i++) {
tablinks[i].className = tablinks[i].className.replace(" active", "");
}
document.getElementById(cityName).style.display = "block";
evt.currentTarget.className += " active";
}
// Get the element with id="defaultOpen" and click on it
document.getElementById("defaultOpen").click();
</script>
</body>
</html>
You could use setTimeout to create a timer that will move your tabs. Here is the updated code:
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
* {box-sizing: border-box}
body {font-family: "Lato", sans-serif;}
/* Style the tab */
.tab {
float: left;
border: 1px solid #ccc;
background-color: #f1f1f1;
width: 30%;
height: 300px;
}
/* Style the buttons inside the tab */
.tab button {
display: block;
background-color: inherit;
color: black;
padding: 22px 16px;
width: 100%;
border: none;
outline: none;
text-align: left;
cursor: pointer;
transition: 0.3s;
font-size: 17px;
}
/* Change background color of buttons on hover */
.tab button:hover {
background-color: #ddd;
}
/* Create an active/current "tab button" class */
.tab button.active {
background-color: #ccc;
}
/* Style the tab content */
.tabcontent {
float: left;
padding: 0px 12px;
border: 1px solid #ccc;
width: 70%;
border-left: none;
height: 300px;
}
</style>
</head>
<body>
<h2>Vertical Tabs</h2>
<p>Click on the buttons inside the tabbed menu:</p>
<div class="tab">
<button class="tablinks london" onclick="openCity(event, 'London')" id="defaultOpen">London</button>
<button class="tablinks paris" onclick="openCity(event, 'Paris')">Paris</button>
<button class="tablinks tokyo" onclick="openCity(event, 'Tokyo')">Tokyo</button>
</div>
<div id="London" class="tabcontent">
<h3>London</h3>
<p>London is the capital city of England.</p>
</div>
<div id="Paris" class="tabcontent">
<h3>Paris</h3>
<p>Paris is the capital of France.</p>
</div>
<div id="Tokyo" class="tabcontent">
<h3>Tokyo</h3>
<p>Tokyo is the capital of Japan.</p>
</div>
<script>
// Create a list of classes for each button
var cities = ["london", "paris", "tokyo"];
var cityIndex = 0;
// Time in miliseconds for the rotation
var rotationInterval = 2000;
// This will find the correct button, click on it, and move to the next one
function rotateCity(index) {
var currentCityButton = document.querySelector("." + cities[index]);
currentCityButton.click();
// Recursive call
setTimeout(function(){
if (cityIndex < 2) {
cityIndex++;
} else {
cityIndex = 0;
}
rotateCity(cityIndex);
}, rotationInterval);
}
function openCity(evt, cityName) {
var i, tabcontent, tablinks;
tabcontent = document.getElementsByClassName("tabcontent");
for (i = 0; i < tabcontent.length; i++) {
tabcontent[i].style.display = "none";
}
tablinks = document.getElementsByClassName("tablinks");
for (i = 0; i < tablinks.length; i++) {
tablinks[i].className = tablinks[i].className.replace(" active", "");
}
document.getElementById(cityName).style.display = "block";
evt.currentTarget.className += " active";
}
// Start rotation
rotateCity(cityIndex)
</script>
</body>
</html>
And this is a working example.
I'm trying to refactor this w3schools for a toggleable tabs code because all of us know that using inline JavaScript is a very bad practice so I'm trying to sepearte them as much as possible so I selected the tablinks and I added an event listener but I'm struggling with the city names (take a look to their code and you'll understand what I'm talking about)
any help please and thank you in advance
HTML
<!-- Tab links -->
<div class="tab">
<button class="tablinks" onclick="openCity(event, 'London')">London</button>
<button class="tablinks" onclick="openCity(event, 'Paris')">Paris</button>
<button class="tablinks" onclick="openCity(event, 'Tokyo')">Tokyo</button>
</div>
<!-- Tab content -->
<div id="London" class="tabcontent">
<h3>London</h3>
<p>London is the capital city of England.</p>
</div>
<div id="Paris" class="tabcontent">
<h3>Paris</h3>
<p>Paris is the capital of France.</p>
</div>
<div id="Tokyo" class="tabcontent">
<h3>Tokyo</h3>
<p>Tokyo is the capital of Japan.</p>
</div>
CSS
.tab {
overflow: hidden;
border: 1px solid #ccc;
background-color: #f1f1f1;
}
/* Style the buttons that are used to open the tab content */
.tab button {
background-color: inherit;
float: left;
border: none;
outline: none;
cursor: pointer;
padding: 14px 16px;
transition: 0.3s;
}
/* Change background color of buttons on hover */
.tab button:hover {
background-color: #ddd;
}
/* Create an active/current tablink class */
.tab button.active {
background-color: #ccc;
}
/* Style the tab content */
.tabcontent {
display: none;
padding: 6px 12px;
border: 1px solid #ccc;
border-top: none;
}
JavaScript
function openCity(evt, cityName) {
// Declare all variables
var i, tabcontent, tablinks;
// Get all elements with class="tabcontent" and hide them
tabcontent = document.getElementsByClassName("tabcontent");
for (i = 0; i < tabcontent.length; i++) {
tabcontent[i].style.display = "none";
}
// Get all elements with class="tablinks" and remove the class "active"
tablinks = document.getElementsByClassName("tablinks");
for (i = 0; i < tablinks.length; i++) {
tablinks[i].className = tablinks[i].className.replace(" active", "");
}
// Show the current tab, and add an "active" class to the button that opened the tab
document.getElementById(cityName).style.display = "block";
evt.currentTarget.className += " active";
}
w3schools is a very good source of bad practice, because they often take many shortcuts while trying to highlight the one small example they are giving.
In practice, you would solve this situation by using a data- attribute. If you don't recognize some of the calls, please refer to some of these articles:
https://developer.mozilla.org/en-US/docs/Learn/HTML/Howto/Use_data_attributes
https://developer.mozilla.org/en-US/docs/Glossary/IIFE
https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelectorAll
https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions
// Wrap our code in an IIFE in order to avoid polluting the global namespace
// and to facilitate faster garbage collection
(function(){
// Preload queries for later use
const tabs = document.querySelectorAll('.tablinks');
const content = document.querySelectorAll('.tabcontent');
// iterate tab to create content interaction
tabs.forEach(f => // f will be the tab element in this loop
// Assign click event to each tab
f.addEventListener('click',function(){
// Locate any previously marked active tab element
const prevActive = document.querySelector('.tablinks.active');
// If a previously marked element exists set its classname to default
if(prevActive) prevActive.className = 'tablinks';
// Assign the currently clicked tab element the active class
f.className = 'tablinks active';
// Iterate through the content to look for the data-attribute we used earlier
content.forEach(c => { // c will be the content element in this loop
// if the id of the element matches the data attribute from the tab then show the content
c.style.display = c.id == f.getAttribute("data-city") ? "block" : "none" ;
})
})
);
})();
.tab {
overflow: hidden;
border: 1px solid #ccc;
background-color: #f1f1f1;
}
/* Style the buttons that are used to open the tab content */
.tab button {
background-color: inherit;
float: left;
border: none;
outline: none;
cursor: pointer;
padding: 14px 16px;
transition: 0.3s;
}
/* Change background color of buttons on hover */
.tab button:hover {
background-color: #ddd;
}
/* Create an active/current tablink class */
.tab button.active {
background-color: #ccc;
}
/* Style the tab content */
.tabcontent {
display: none;
padding: 6px 12px;
border: 1px solid #ccc;
border-top: none;
}
<div class="tab">
<button class="tablinks" data-city="London">London</button>
<button class="tablinks" data-city="Paris">Paris</button>
<button class="tablinks" data-city="Tokyo">Tokyo</button>
</div>
<!-- Tab content -->
<div id="London" class="tabcontent">
<h3>London</h3>
<p>London is the capital city of England.</p>
</div>
<div id="Paris" class="tabcontent">
<h3>Paris</h3>
<p>Paris is the capital of France.</p>
</div>
<div id="Tokyo" class="tabcontent">
<h3>Tokyo</h3>
<p>Tokyo is the capital of Japan.</p>
</div>
<button id="London" class="tablinks">London</button>
var btns = document.querySelectorAll('.tab button')
btns.forEach((btn)=>{
btn.addEventListener('click', (event)=>{
openCity(event, btn.id, btn)
})
})
Give each button an id with the city name, then pass the id to the openCity function
You can pass the btn to your openCity function too
I'v tried to create this w3 code on a page works fine. But when I want to add it more then one (f.e. two or three tabs), the tabs doesn't work. What must I change in the script if i click the first tab of the second tab section; it doesn't want to open it in the first tab section.
The second I would like to know just styling it:
if I config the css that the tabcontent would be auto height, the tabs on the left side auto adjust to it (I tried to set all of them to auto height but both side auto heighted oneself different).
The code is that:
function openCity(evt, cityName) {
var i, tabcontent, tablinks;
tabcontent = document.getElementsByClassName("tabcontent");
for (i = 0; i < tabcontent.length; i++) {
tabcontent[i].style.display = "none";
}
tablinks = document.getElementsByClassName("tablinks");
for (i = 0; i < tablinks.length; i++) {
tablinks[i].className = tablinks[i].className.replace(" active", "");
}
document.getElementById(cityName).style.display = "block";
evt.currentTarget.className += " active";
}
// Get the element with id="defaultOpen" and click on it
document.getElementById("defaultOpen").click();
* {box-sizing: border-box}
body {font-family: "Lato", sans-serif;}
/* Style the tab */
.tab {
float: left;
border: 1px solid #ccc;
background-color: #f1f1f1;
width: 30%;
height: 300px;
}
/* Style the buttons inside the tab */
.tab button {
display: block;
background-color: inherit;
color: black;
padding: 22px 16px;
width: 100%;
border: none;
outline: none;
text-align: left;
cursor: pointer;
transition: 0.3s;
font-size: 17px;
}
/* Change background color of buttons on hover */
.tab button:hover {
background-color: #ddd;
}
/* Create an active/current "tab button" class */
.tab button.active {
background-color: #ccc;
}
/* Style the tab content */
.tabcontent {
float: left;
padding: 0px 12px;
border: 1px solid #ccc;
width: 70%;
border-left: none;
height: 300px;
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<p>Click on the buttons inside the tabbed menu:</p>
<div class="tab">
<button class="tablinks" onclick="openCity(event, 'London')" id="defaultOpen">London</button>
<button class="tablinks" onclick="openCity(event, 'Paris')">Paris</button>
<button class="tablinks" onclick="openCity(event, 'Tokyo')">Tokyo</button>
</div>
<div id="London" class="tabcontent">
<h3>London</h3>
<p>London is the capital city of England.</p>
</div>
<div id="Paris" class="tabcontent">
<h3>Paris</h3>
<p>Paris is the capital of France.</p>
</div>
<div id="Tokyo" class="tabcontent">
<h3>Tokyo</h3>
<p>Tokyo is the capital of Japan.</p>
</div>
</body>
</html>