JavaScript toggle - show some, and remove some text - javascript

I want to show text when one of the elements are 'clicked', but hide the others.
So when I press 'Home' the other elements are on 'hide' and, at the same time, change the active class.
Please help.
function toggle(obj) {
var obj = document.getElementById(obj);
if (obj.style.display == "block") obj.style.display = "none";
else obj.style.display = "block";
}
<div id="navbar">
<a class="active" href="javascript:void(0)" onClick="toggle('Home')">Home</a>
Project
Contact
</div>
<div class="informasjon" id="Home" style="display:none;">
bla bla bla
</div>
<div class="informasjon1" id="Contact" style="display:none;">
<br>
blabla
</div>
<div class="informasjon1" id="Project" style="display:none;">
Different bla bla bla
</div>

You can hide all the elements on each click, then show the clicked one. Also, I will suggest you to avoid inline event handler by using addEventListener. You can use data- attribute to keep the reference of the id of the respective element.
Demo:
.active{
color: red;
}
<div id="navbar">
<a class="active" href="javascript:void(0)" data-id="Home">Home</a>
Project
Contact
</div>
<div class="informasjon" id="Home">
bla bla bla
</div>
<div class="informasjon" id="Contact" style="display:none;">
<br>
blabla
</div>
<div class="informasjon" id="Project" style="display:none;">
Different bla bla bla
</div>
<script>
var links = document.querySelectorAll('#navbar a');
links.forEach(function(el){
el.addEventListener('click', toggle)
})
function toggle() {
links.forEach(el => el.classList.remove('active'));
this.classList.add('active');
var dataEl = document.querySelectorAll('.informasjon');
dataEl.forEach(el => el.style.display = "none");
var id = this.getAttribute('data-id');
var obj = document.getElementById(id);
obj.style.display = "block";
}
</script>

The easiest, and therefore arguably the best, solution would be to avoid using JavaScript for the most part, and use CSS.
I've made some changes to your HTML – removing the JavaScript from your <a> elements, replacing the <div id="navbar"> with a semantic <nav> element, and placing the <a> elements within an <ol>, also I've added a class to your 'content' elements because I couldn't decide if the <div id="Home"> element was meant to have the class of informasjon or if it was a typo given the remaining elements were of class informasjon1 – so the approach below takes advantage of the CSS :target pseudo-class to style the element that is targetted by the fragment-identifier in the URL, the part following the # character:
*,
::before,
::after {
box-sizing: border-box;
font-family: Ubuntu, Roboto, Calibri, Helvetica, Arial, sans-serif;
line-height: 1.5;
margin: 0;
padding: 0;
}
nav ol {
list-style-type: none;
display: flex;
justify-content: space-around;
}
li a {
background-color: lightblue;
border-radius: 0.3em;
color: #000;
display: block;
padding: 0.2em 1em;
}
div.content-unit {
display: none;
}
div.content-unit:target {
display: block;
}
.active {
background: palegreen;
}
<nav>
<ol>
<li>
Home
</li>
<li>
Project
</li>
<li>
Contact
</li>
</ol>
</nav>
<div class="informasjon content-unit" id="Home">
This is in the 'home' area
</div>
<div class="informasjon1 content-unit" id="Contact">
This is in the 'contact' area
blabla
</div>
<div class="informasjon1 content-unit" id="Project">
This is in the 'project' area
</div>
As CSS works with the majority of the functionality, there are a couple aspects that require JavaScript; on page-load we need to show the correct 'content' to match the 'active' button on page-load (assuming that a hash isn't reliably set on page-load), and we need to add the 'active' class to the correct <a> when the links are followed.
First, a simple JavaScript function to show the 'active' <a> element:
// defining a named function, using Arrow syntax, passing in the
// Event Object from the (later) use of EventTarget.addEventListener():
const showActive = (e) => {
// here we're searching the <nav> element to find all elements
// with the class of 'active', and then iterating over that
// NodeList with NodeList.prototype.forEach():
document.querySelectorAll('nav .active').forEach(
// here we again use Arrow syntax, and pass in a
// reference to the current element of the NodeList;
// we then use the classList API to remove the 'active'
// class from the element's class-list:
(activeElem) => activeElem.classList.remove('active')
)
// we're binding the event-handler (this function) to the <a>
// elements, but an <a> element can have descendant elements,
// so here we find the EventObject.currentTarget which is the
// element to which the event-listener was bound (the <a>)
// and we again use the classList API to add the 'active' class:
e.currentTarget.classList.add('active');
}
// here we use document.querySelectorAll() to find all <a> elements
// within the <nav> element, and then use NodeList.prototype.forEach()
// to iterate over that NodeList:
document.querySelectorAll('nav a').forEach(
// here we again use an Arrow function, to bind the showActive()
// function (note the deliberate lack of parentheses) as the
// event-handler for the 'click' event:
(aElem) => aElem.addEventListener('click', showActive)
);
*,
::before,
::after {
box-sizing: border-box;
font-family: Ubuntu, Roboto, Calibri, Helvetica, Arial, sans-serif;
line-height: 1.5;
margin: 0;
padding: 0;
}
nav ol {
list-style-type: none;
display: flex;
justify-content: space-around;
}
li a {
background-color: lightblue;
border-radius: 0.3em;
color: #000;
display: block;
padding: 0.2em 1em;
}
div.content-unit {
display: none;
}
div.content-unit:target {
display: block;
}
.active {
background: palegreen;
}
<nav>
<ol>
<li>
Home
</li>
<li>
Project
</li>
<li>
Contact
</li>
</ol>
</nav>
<div class="informasjon content-unit" id="Home">
This is in the 'home' area
</div>
<div class="informasjon1 content-unit" id="Contact">
This is in the 'contact' area
blabla
</div>
<div class="informasjon1 content-unit" id="Project">
This is in the 'project' area
</div>

I have changed the class of Home for this to work, but hopefully it helps you in case this isn't exactly what you need.
function toggle(obj) {
let arrClass = document.getElementsByClassName("informasjon1");
let object=document.getElementById(obj);
let navbar = document.getElementById("navbar");
for (let i = 0; i < navbar.childNodes.length; i++) {
if(navbar.childNodes[i].innerHTML == obj) navbar.childNodes[i].className = "active";
else navbar.childNodes[i].className = "inactive";
}
for (let i = 0; i < arrClass.length; i++) {
if (arrClass[i].id == object.id) arrClass[i].style.display = "block";
else arrClass[i].style.display = "none";
}
}
<div id="navbar">
<a class="active" href="javascript:void(0)" onClick="toggle('Home')">Home</a>
Project
Contact
</div>
<div class="informasjon1" id="Home" style="display:none;">
bla bla bla
</div>
<div class="informasjon1" id="Contact" style="display:none;"><br>
blabla
</div>
<div class="informasjon1" id="Project" style="display:none;">
Different bla bla bla
</div>

Related

Javascript - Automate duplicating tags from parent div to child div based on class snippet

I would like to automate duplicating a class from a parent div to each separate child span based on a word.
As an example:
parent div contains the following classes: grid-item tag-street-style tag-slender tag-classic tag-navy tag-grey tag-white is-loaded
I would like to duplicate any classes within the parent div with the precursor "tag-" and place them into each separate child span. In this case, the parent div contains the classes with the initial "tag-" word: tag-street-style tag-slender tag-classic tag-navy tag-grey tag-white
Some other parent divs will contain other classes that contain the initial word "tag-"
The "tag-" classes can be different in other parent divs but there will always be 5 "tag-" classes. As an example, a different parent div may contain the following classes with the initial "tag-" word: tag-smart-style tag-casual tag-modern tag-red tag-black tag-green
I already have a code snippet but this is locked in to 5 specific "tag-" classes. Here is the code:
let classMap = {
"tag-street-style": ".colour-tag-1",
"tag-slender": ".colour-tag-2",
"tag-navy": ".colour-tag-3",
"tag-grey": ".colour-tag-4",
"tag-white": ".colour-tag-5",
};
I would like the first "tag-" class identified within the parent div to be duplicated into "colour-tag-1" within the first child span.
Then I would like the second "tag-" class identified within the parent div to be duplicated into "colour-tag-2" within the second child span.
Then I would like the third "tag-" class identified within the parent div to be duplicated into "colour-tag-3" within the third child span.
Then I would like the fourth "tag-" class identified within the parent div to be duplicated into "colour-tag-4" within the fourth child span.
Then I would like the fifth "tag-" class identified within the parent div to be duplicated into "colour-tag-5" within the fifth child span.
$(document).ready( function() {
$(".grid-item .grid-meta-wrapper").each(function(e){
$(this).append('<div class="product-view-item-colour-tags"><span class="product-view-item-colour-tag colour-tag-1">tag1</span><span class="product-view-item-colour-tag colour-tag-2">tag2</span><span class="product-view-item-colour-tag colour-tag-3">tag3</span><span class="product-view-item-colour-tag colour-tag-4">tag4</span><span class="product-view-item-colour-tag colour-tag-5">tag5</span></div>');
});
let classMap = {
"tag-street-style": ".colour-tag-1",
"tag-slender": ".colour-tag-2",
"tag-navy": ".colour-tag-3",
"tag-grey": ".colour-tag-4",
"tag-white": ".colour-tag-5",
};
for (let cls in classMap) {
document.querySelector(classMap[cls]).classList.add(cls);
}
});
<style>
.tag-street-style {
background-color: purple;
}
.tag-slender {
background-color: red;
}
.tag-navy {
background-color: blue;
}
.tag-grey {
background-color: grey;
}
.tag-white {
background-color: black;
color: white;
}
.tag-red {
background-color: black;
color: red;
}
.tag-black {
background-color: white;
color: black;
}
.tag-green {
background-color: black;
color: green;
}
</style>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="grid-item tag-street-style tag-slender tag-classic tag-navy tag-grey tag-white is-loaded">
<section class="grid-meta-wrapper">
<div class="grid-main-meta">
<div class="grid-title"> T-Shirt </div>
<div class="grid-prices">
<div class="product-price">
<span>12.99</span>
</div>
</div>
</div>
<div class="grid-meta-status"></div>
</section>
</div>
<br><br><br>
<div class="grid-item tag-smart-style tag-casual tag-modern tag-red tag-black tag-green is-loaded">
<section class="grid-meta-wrapper">
<div class="grid-main-meta">
<div class="grid-title"> T-Shirt </div>
<div class="grid-prices">
<div class="product-price">
<span>12.99</span>
</div>
</div>
</div>
<div class="grid-meta-status"></div>
</section>
</div>
First, here is a testable solution:
$(document).ready( function() {
$(".grid-item .grid-meta-wrapper").each(function(e){
$(this).append('<div class="product-view-item-colour-tags"><span class="product-view-item-colour-tag colour-tag-1">tag1</span><span class="product-view-item-colour-tag colour-tag-2">tag2</span><span class="product-view-item-colour-tag colour-tag-3">tag3</span><span class="product-view-item-colour-tag colour-tag-4">tag4</span><span class="product-view-item-colour-tag colour-tag-5">tag5</span></div>');
});
let classes = [...document.getElementById("tag-specifier").classList].filter(item => item.indexOf("tag-") === 0);
for (let index = 0; index < classes.length; index++) {
let currentItem = document.querySelector(".colour-tag-" + (index + 1));
if (currentItem !== null) {
currentItem.classList.add(classes[index]);
}
}
});
.tag-street-style {
background-color: purple;
}
.tag-slender {
background-color: red;
}
.tag-navy {
background-color: blue;
}
.tag-grey {
background-color: grey;
}
.tag-white {
background-color: black;
color: white;
}
.tag-red {
background-color: black;
color: red;
}
.tag-black {
background-color: white;
color: black;
}
.tag-green {
background-color: black;
color: green;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="tag-specifier" class="grid-item tag-street-style tag-slender tag-classic tag-navy tag-grey tag-white is-loaded">
<section class="grid-meta-wrapper">
<div class="grid-main-meta">
<div class="grid-title"> T-Shirt </div>
<div class="grid-prices">
<div class="product-price">
<span>12.99</span>
</div>
</div>
</div>
<div class="grid-meta-status"></div>
</section>
</div>
Now, let's understand it:
I have added the id of tag-specifier to the element which has the classes, so the code will have an easy time finding the classes to work with
document.getElementById("tag-specifier").classList is returning an object of key-value pairs where the keys are indexes (starting from 0) and the values are class names
I convert the result of classList into an array via [...document.getElementById("tag-specifier").classList] because I intend to use the filter() function of the array, alternatively I could have written a loop with similar effect
.filter() is being called for the newly converted array. This function takes a callback (more on that below) that determines which items we are interested about from the array and returns an array that contains only the items that the callback found interesting
a callback is a function that is scheduled to be executed at some future point of time
in our case, the callback of .filter() is a function which will be executed for each elements of the array and will evaluate them whether they are interesting
our callback is item => item.indexOf("tag-") === 0, which is a function (we use the arrow operator => to differentiate the parameter, which is item and the actual function body, which is item.indexOf("tag-") === 0), that is, we are only interested about items whose name starts with tag-
after the call for .filter(), the value assigned to classes is an array of class names that only holds valuable class names from our perspective, that is, class names starting with tag-
we loop classes using a variable we create for this purpose, named index
we search for the element that corresponds to the selector of ".colour-tag-" + (index + 1). The reason for the index + 1 is that Javascript arrays are 0-indexed and your tag indexes start from 1
note that (index + 1) is enclosed into parantheses. The reason for this is that + is an operator that acts both as concatenator and numeric addition and evaluates from left-to-right, that is, without the paranthesis around (index + 1) the result of ".colour-tag-" + index + 1 would be looking like .colour-tag-01 instead of .colour-tag-2
we check whether currentItem exists, so we program defensively, so, if any anomaly occurs, we intend our code to handle it gracefully
if currentItem existed, then we add the current class, which is classes[index]
EDIT
The initial solution I have implemented was assuming that we deal with a single such case, while your problem included multiple similar cases on the same page. To solve this issue, I have added an extra layer to the solution, querying the roots of all relevant subtrees in HTML and using them as the context of their respective problem-spaces.
Here is a snippet that illustrates it (yes, the first 3 tags will be unstyled, but this is not due to the logic of the code, but it is rather due to the styling specification of the structure):
$(document).ready( function() {
$(".grid-item .grid-meta-wrapper").each(function(e){
$(this).append('<div class="product-view-item-colour-tags"><span class="product-view-item-colour-tag colour-tag-1">tag1</span><span class="product-view-item-colour-tag colour-tag-2">tag2</span><span class="product-view-item-colour-tag colour-tag-3">tag3</span><span class="product-view-item-colour-tag colour-tag-4">tag4</span><span class="product-view-item-colour-tag colour-tag-5">tag5</span><span class="product-view-item-colour-tag colour-tag-6">tag6</span></div>');
});
for (let context of $(".list-grid .grid-item")) {
let idDeclaration = context.id;
let classes = [...context.classList].filter(item => item.indexOf("tag-") === 0);
for (let index = 0; index < classes.length; index++) {
let currentItem = context.querySelector(".colour-tag-" + (index + 1));
if (currentItem !== null) {
currentItem.classList.add(classes[index]);
}
}
}
});
.product-view-item-colour-tags .tag-navy {
background-color: blue;
}
.product-view-item-colour-tags .tag-grey {
background-color: grey;
}
.product-view-item-colour-tags .tag-white {
background-color: black;
color: white;
}
.product-view-item-colour-tags .tag-red {
color: red;
}
.product-view-item-colour-tags .tag-black {
background-color: white;
color: black;
}
.product-view-item-colour-tags .tag-green {
color: green;
}
.product-view-item-colour-tags .tag-yellow {
background-color: black;
color: yellow;
}
.product-view-item-colour-tags .tag-orange {
color: orange;
}
.product-view-item-colour-tags .tag-pink {
color: pink;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="list-grid">
<div id="thumb-product-3-9" class="grid-item tag-street-style tag-slender tag-classic tag-navy tag-grey tag-white is-loaded">
<section class="grid-meta-wrapper">
<div class="grid-main-meta">
<div class="grid-title"> T-Shirt 1</div>
<div class="grid-prices">
<div class="product-price">
<span>9.99</span>
</div>
</div>
</div>
<div class="grid-meta-status"></div>
</section>
</div>
<br>
<div id="thumb-product-3-12" class="grid-item tag-social-style tag-thicker tag-premium tag-green tag-black tag-red is-loaded">
<section class="grid-meta-wrapper">
<div class="grid-main-meta">
<div class="grid-title"> T-Shirt 2</div>
<div class="grid-prices">
<div class="product-price">
<span>12.99</span>
</div>
</div>
</div>
<div class="grid-meta-status"></div>
</section>
</div>
<br>
<div id="thumb-product-3-1" class="grid-item tag-outdoor-style tag-warmer tag-premium tag-pink tag-white tag-orange is-loaded">
<section class="grid-meta-wrapper">
<div class="grid-main-meta">
<div class="grid-title"> T-Shirt 3</div>
<div class="grid-prices">
<div class="product-price">
<span>14.99</span>
</div>
</div>
</div>
<div class="grid-meta-status"></div>
</section>
</div>
<br>
<div id="thumb-product-3-4" class="grid-item tag-street-style tag-slender tag-casual tag-green tag-yellow tag-red is-loaded">
<section class="grid-meta-wrapper">
<div class="grid-main-meta">
<div class="grid-title"> T-Shirt 4</div>
<div class="grid-prices">
<div class="product-price">
<span>15.99</span>
</div>
</div>
</div>
<div class="grid-meta-status"></div>
</section>
</div>
</div>

Two Column Accordion with Separate Full Width Divs

The intension is to have a two column accordion, without limiting the "expand" field to the left or right column. The catch is that there will be multiple on one page. This is already created, but only button 1 is working. With the way my JS is going, it will get very very repetitive - I am looking for assistance with re-writing the JS to be multiple click friendly. Fiddle: https://codepen.io/ttattini/pen/abLzaaY
EDIT: It would also be perfect if one dropdown would close as the next is opened
HTML
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="row">
<div id="column">
<button id="button">I am Button #1</button>
<button id="button">I am Button #3</button>
</div>
<div id="column">
<button id="button">I am Button #2</button>
<button id="button">I am Button #4</button>
</div>
</div>
<div id="hidden">
<p id="content"> So here I am #1</p>
</div>
<div id="hidden">
<p id="content"> So here I am #2</p>
</div>
<div id="hidden">
<p id="content"> So here I am #3</p>
</div>
<div id="hidden">
<p id="content"> So here I am #4</p>
</div>
CSS
#hidden {
background: #ccc;
margin-top: 2%;
overflow: hidden;
transition: height 200ms;
height: 0; /* <-- set this */
}
#button {
padding: 10px;
margin-top: 5px;
width:50%;
margin-left: 10%;
cursor: pointer;
}
#row {
display: flex;
}
#column {
flex: 50%;
}
JS
$(function() {
var b = $("#button");
var w = $("#hidden");
var l = $("#content");
b.click(function() {
if (w.hasClass('open')) {
w.removeClass('open');
w.height(0);
} else {
w.addClass('open');
w.height(l.outerHeight(true));
}
});
});
The biggest issue is that you're using IDs when you should be using classes. IDs must be unique to each element in a page. When you repeat an ID, JS will only target the first element using that ID. That's why only the first one is working.
The second issue is that, because of the way the script is written, it will only target a single element. What you need to do is get all the elements you want to target by something like their class name and then loop through them, applying the event listener to each one and its appropriate children.
EDIT: Here is an example from some code I wrote for a page with multiple accordions a few weeks ago in vanilla JS
//Below I establish a counting variable and find all the accordions on the page
const acc = document.getElementsByClassName( 'accordion' );
let i;
//Looping through each accordion
for ( i = 1; i <= acc.length; i++ ) {
//Identify target for the event listener. In this case, a heading for each accordion, which I've numbered e.g. "title-1"
const title = 'title-' + i;
const label = document.getElementById( title );
//Identify target content, in this case a list that has a unique ID e.g. "list-1"
const listNum = 'list-' + i;
const list = document.getElementById( listNum );
//Add event listener to heading that toggles the active classes
label.addEventListener( 'click', function() {
label.classList.toggle( 'accordion--active' );
});
}
Of course, there's more than one way to skin a cat, but this is a working example.
I have tracked the clicked event of each button and showed the corresponding hidden content with the use of data- attribute.
I have used vanilla JavaScipt instead of jQuery.
const buttons = document.querySelectorAll('.button');
const hiddens = document.querySelectorAll('.hidden');
buttons.forEach((btn) => {
btn.addEventListener('click', btnClicked)
function btnClicked(e) {
hiddens.forEach((hidden) => {
if(e.target.dataset.btn == hidden.dataset.content) {
hidden.classList.toggle('height')
} else {
hidden.classList.remove('height')
}
})
}
})
.hidden {
background: #ccc;
margin-top: 2%;
padding-left:2%;
overflow: hidden;
transition: height 200ms;
height: 0; /* <-- set this */
}
.hidden.height {
height: 50px;
}
.button {
padding: 10px;
color: white;
background-color: #2da6b5;
border: none;
margin-top: 5px;
width:90%;
margin-left: 5%;
cursor: pointer;
}
.button:hover {
filter: brightness(.9);
}
#row {
display: flex;
}
.column {
flex: 50%;
}
<div id="row">
<div class="column">
<button class="button" data-btn="one">I am Button #1</button>
<button class="button" data-btn="three">I am Button #3</button>
</div>
<div class="column">
<button class="button" data-btn="two">I am Button #2</button>
<button class="button" data-btn="four">I am Button #4</button>
</div>
</div>
<div class="hidden" data-content="one">
<p class="content"> So here I am #1</p>
</div>
<div class="hidden" data-content="two">
<p class="content"> So here I am #2</p>
</div>
<div class="hidden" data-content="three">
<p class="content"> So here I am #3</p>
</div>
<div class="hidden" data-content="four">
<p class="content"> So here I am #4</p>
</div>
Also, please do not use the same ID at multiple elements.

JavaScript Show invisible divs on click

I ran into a problem that when I click on the button, it just flips the icon but only makes the invisible fields visible on the second click. Are there any idea how to do it?
(Heres a gif to show my problem: https://ibb.co/cvz7pWC )
Also heres my code :
function moreSoc() {
var moresoc = document.getElementById("moresoc");
var btnText = document.getElementById("mbtn");
if (moresoc.style.display === "none" ) {
moresoc.style.display = "block";
mbtn.innerHTML = "More ▲";
} else {
moresoc.style.display = "none";
mbtn.innerHTML = "More ▼"
}
}
.morebutton {
border: none;
background: #fff;
color: #111;
font-size: 32px;
}
#moresoc {
display: none;
}
<div class="wrapper more">
<button class="morebutton" id="mbtn" onclick="moreSoc()">More ▲</button>
</div>
<section class="social-links" id="moresoc">
<div class="wrapper">
<h2>Others</h2>
<div class="social-link facebook">
<p>Facebook</p>
</div>
<div class="social-link instagram">
<p>Instagram</p>
</div>
<div class="social-link twitter">
<p>Twitter</p>
</div>
<div class="social-link youtube">
<p>Youtube</p>
</div>
</div>
</section>
This could be to do with you not being to read element.style.display as none the first time round. This is because it has not yet been set by JavaScript, but just by css. I suggest changing your if statement to check for not "block".
function moreSoc() {
var moresoc = document.getElementById("moresoc");
var btnText = document.getElementById("mbtn");
if (moresoc.style.display != "block" ) {
moresoc.style.display = "block";
mbtn.innerHTML = "More ▲";
} else {
moresoc.style.display = "none";
mbtn.innerHTML = "More ▼"
}
}
.morebutton {
border: none;
background: #fff;
color: #111;
font-size: 32px;
}
#moresoc {
display: none;
}
<div class="wrapper more">
<button class="morebutton" id="mbtn" onclick="moreSoc()">More ▼</button>
</div>
<section class="social-links" id="moresoc">
<div class="wrapper">
<h2>Others</h2>
<div class="social-link facebook">
<p>Facebook</p>
</div>
<div class="social-link instagram">
<p>Instagram</p>
</div>
<div class="social-link twitter">
<p>Twitter</p>
</div>
<div class="social-link youtube">
<p>Youtube</p>
</div>
</div>
</section>
ElementCSSInlineStyle.style only returns (or sets) inline styles on an element. On your first click there is no inline display property to read so your condition sets it to none. On the second click your condition finds none and sets it to block.
The answer to look for !block solves this immediate problem but it stills ties your styling to your js rather than keeping it in your CSS. This means that if the default display property of your div needs to change in your layout (inline-block, flex, etc) you would need to change it in your js as well as your CSS.
For this reason I would recommend not using inline styles at all but rather rather use Element.classList to manage applied styles from your CSS – in this case just the adding/removing of a .hidden class that sets display to none without having to know what the appropriate visible display default is.
Also, since you are querying the button element in your code anyway, it would be better to apply the click listener from your js as well rather than inline.
function moreSoc() {
const moresoc = document.getElementById("moresoc");
if (moresoc.classList.contains('hidden')) {
moresoc.classList.remove('hidden');
mbtn.innerHTML = "More ▲";
} else {
moresoc.classList.add('hidden');
mbtn.innerHTML = "More ▼"
}
}
const mbtn = document.getElementById("mbtn");
mbtn.addEventListener('click', moreSoc);
.morebutton {
border: none;
background: #fff;
color: #111;
font-size: 32px;
}
#moresoc {
}
.hidden {
display: none;
}
<div class="wrapper more">
<button class="morebutton" id="mbtn">More ▲</button>
</div>
<section class="social-links hidden" id="moresoc">
<div class="wrapper">
<h2>Others</h2>
<div class="social-link facebook">
<p>Facebook</p>
</div>
<div class="social-link instagram">
<p>Instagram</p>
</div>
<div class="social-link twitter">
<p>Twitter</p>
</div>
<div class="social-link youtube">
<p>Youtube</p>
</div>
</div>
</section>

Javascript tabs using data attributes rather than IDs to link button and tab

I'm wanting to create a variation of Javascript tabs using data attributes rather than IDs to link the tab and the content.
Here's how it should work:
Clicking a <button class="tab" data-tab-trigger="1"> adds a class of is-active and removes any is-active classes from all other button elements
The value of data-tab-trigger matches the value of data-tab-content on the corresponding <div class="tab-content" data-tab-content="1"> and should add a class of is-open to it
The is-active class highlights the active tab and the is-open class shows the related tab content
Here's the JS I'm currently working which isn't working as expected:
var tabTriggerBtns = document.querySelectorAll('.tabs li button');
tabTriggerBtns.forEach(function(tabTriggerBtn, index){
tabTriggerBtn.addEventListener('click', function(){
var tabTrigger = this;
var tabTriggerData = tabTrigger.getAttribute('data-tab-trigger');
var tabContent = document.querySelector('.tab-content');
var currentTabData = document.querySelector('.tab-content[data-tab-content="' + tabTriggerData + '"]').classList.add('is-open');
if(tabContent !== currentTabData) {
tabContent.classList.toggle('is-open');
}
if(tabTrigger.classList.contains('is-active')) {
tabTrigger.classList.remove('is-active');
}
else {
tabTriggerBtn.classList.remove('is-active');
tabTrigger.classList.add('is-active');
}
});
});
Here's a Codepen with my ongoing script: https://codepen.io/abbasarezoo/pen/752f24fc896e6f9fcce8b590b64b37bc
I'm having difficulty finding what's going wrong here. I'm relatively comfortable writing jQuery, but quite raw when it comes to vanilla JS so any help would be very much appreciated.
One of your main issue is in this line:
tabContent !== currentTabData
You may use dataset in order to access data attributes.
Moreover, you may simplify your code in few steps:
remove classess
add classess
The snippet:
var tabTriggerBtns = document.querySelectorAll('.tabs li button');
tabTriggerBtns.forEach(function(tabTriggerBtn, index){
tabTriggerBtn.addEventListener('click', function(){
var currentTabData = document.querySelector('.tab-content[data-tab-content="' + this.dataset.tabTrigger + '"]');
// remove classess
document.querySelector('.tab-content.is-open').classList.remove('is-open');
document.querySelector('.tabs li button.is-active').classList.remove('is-active');
// add classes
currentTabData.classList.add('is-open');
this.classList.add('is-active');
});
});
* {
margin: 0;
padding: 0;
}
body {
display: flex;
}
.tabs {
width: 25%;
border: 2px solid red;
}
button.is-active {
background-color: red;
}
.tab-content__outer {
width: 75%;
}
.tab-content {
display: none;
}
.tab-content.is-open {
display: block;
background-color: yellow;
}
<ul class="tabs">
<li>
<button class="tab is-active" data-tab-trigger="1">First</button>
</li>
<li>
<button class="tab" data-tab-trigger="2">Second</button>
</li>
<li>
<button class="tab" data-tab-trigger="3">Third</button>
</li>
</ul>
<div class="tab-content__outer">
<div class="tab-content is-open" data-tab-content="1">
First
</div>
<div class="tab-content" data-tab-content="2">
Second
</div>
<div class="tab-content" data-tab-content="3">
Third
</div>
</div>

Block elements are parsed outside their block parents

I am writing a simple little nameplate page and am using multiple paragraphs hidden and shown with javascript to get all the sections in one document.
Here is my html:
<header>
<h1><span>Scott Colby</span></h1>
<nav>
<div id="twitternav">Twitter</div>
<div id="tumblrnav">Tumblr</div>
<div id="flickrnav">Flickr</div>
<div id="facebooknav">Facebook</div>
<div id="linksnav">Links</div>
<div id="aboutnav" class="active">About Me</div>
</nav>
</header>
<div id="content">
<p id="twitter">
Placeholder text for Twitter
</p>
<p id="tumblr">
Placeholder text for Tumblr
</p>
<p id="flickr">
Placeholder text for Tumblr
</p>
<p id="facebook">
Placeholder text for Tumblr
</p>
<p id="links">
Placeholder text for Links
</p>
<p id="about" class="active">
<div id="portrait"><img src="img/portrait.jpg" width="188" height="221" alt="-----" /><br /><span class="credit">Image: © 2011 Jim Thomas</span></div>
<div>Placeholder text for About Me</div>
</p>
</div>
My CSS:
nav {
color: white;
margin: 0 5px -8px 0;
text-align: right;
z-index: 1;
}
nav div{
display: inline;
margin: 0 0 0 .9em;
padding: .25em .25em .25em .25em;
z-index: 1;
}
nav div:hover {
background: #F77D00;
}
nav div.active {
background: #FF9900;
}
#content p {
display: none;
font-size: 85%;
z-index: -1;
}
#content p.active {
display: block;
}
And my javascript:
function hideAll() {
document.getElementById('twitter').className = '';
document.getElementById('twitternav').className = '';
document.getElementById('tumblr').className = '';
document.getElementById('tumblrnav').className = '';
document.getElementById('flickr').className = '';
document.getElementById('flickrnav').className = '';
document.getElementById('facebook').className = '';
document.getElementById('facebooknav').className = '';
document.getElementById('links').className = '';
document.getElementById('linksnav').className = '';
document.getElementById('about').className = '';
document.getElementById('aboutnav').className = '';
}
function showTwitter() {
hideAll();
document.getElementById('twitter').className = 'active';
document.getElementById('twitternav').className = 'active';
}
function showTumblr() {
hideAll();
document.getElementById('tumblr').className = 'active';
document.getElementById('tumblrnav').className = 'active';
}
function showFlickr() {
hideAll();
document.getElementById('flickr').className = 'active';
document.getElementById('flickrnav').className = 'active';
}
function showFacebook() {
hideAll();
document.getElementById('facebook').className = 'active';
document.getElementById('facebooknav').className = 'active';
}
function showLinks() {
hideAll();
document.getElementById('links').className = 'active';
document.getElementById('linksnav').className = 'active';
}
function showAbout() {
hideAll();
document.getElementById('about').className = 'active';
document.getElementById('aboutnav').className = 'active';
}
Now, I know that's a lot of code to go through, but it's pretty simple stuff I think.
Here is my problem: even when the #about p is not active and has display:none (i.e. another section is active and visible), the image and the div with "Placeholder text for About" are both visible. When I investigated this in firebug, it shows something like this:
<p id="about"> </p>
<div id="portrait"><img .... /></div>
<div>Placeholder text for About</div>
Why do the two div's migrate outside their parent element? How can I make them disappear along with their parent?
The <p> element does not allow block level elements like <div> inside it. When the HTML parser sees the <div> tag, it assumes that the </p> tag has been omitted (it's optional) and that the p element is complete. Hence the DOM you see with the div elements as following siblings of the p element.
Tip: It's always a good idea to validate your HTML before posting a question on SO. Had you done so, the validator would have indicated the error to you.

Categories