Displaying show/hide content with a button and an .active css class - javascript

I am trying to create a testimonial section on a wordpress site where there is an "expand" button to show the full testimonial quote. I want the text in the button to change to "collapse" after it is clicked. I also need to add a class to the div wraper so I can implement custom css styling when the button is active. I need this pasted three times. The problem is it fails after the first testimonial.
I have this working with the code below, with it duplicated three times (for three different testimonials) and it works on a basic html document. But when I implement it in a wordpress site by pasting the code, only the first testimonial totally works. The other two do show/hide my inner div element, but they won't insert the .active class or change the text of the button to "collapse"
Both of the second testimonials give a
"Uncaught TypeError: Cannot set property 'innerHTML' of null" in the console.
So for example, here are two out of three of my testimonials I want to show. I have to change the ID's on them to avoid the javascript conflict.
function showhide() {
var content = document.getElementById('hidden-content');
var wrap = document.getElementById('testimonial-wrap');
var btn = document.getElementById('button1');
if (content.style.display === 'none') {
content.style.display = 'block';
wrap.style.background = 'grey';
btn.innerHTML = 'COLLAPSE';
wrap.classList.add('active');
} else {
content.style.display = 'none';
wrap.style.background = 'white';
btn.innerHTML = 'EXPAND';
wrap.classList.remove('active');
}
}
function showhide2() {
var content2 = document.getElementById('hidden-content2');
var wrap2 = document.getElementById('testimonial-wrap2');
var btn2 = document.getElementById('button2');
if (content2.style.display === 'none') {
content2.style.display = 'block';
wrap2.style.background = 'grey';
btn2.innerHTML = 'COLLAPSE';
wrap2.classList.add('active');
} else {
content2.style.display = 'none';
wrap2.style.background = 'white';
btn2.innerHTML = 'EXPAND';
wrap2.classList.remove('active');
}
}
<div id="testimonial-wrap" style="background-color: white;">
<div id="testimonial">
above testimonial content
<div id="hidden-content" style="display: none;">
<p>"hidden content”</p>
</div>
<button id="button1" onclick="showhide()">EXPAND</button>
</div>
</div>
<div id="testimonial-wrap2" style="background-color: white;">
<div id="testimonial">
above testimonial content
<div id="hidden-content2" style="display: none;">
<p>"hidden content.”</p>
</div>
<button id="button2" onclick="showhide2()">EXPAND</button>
</div>
</div>

I think this is what you're looking for. You can do it much easier with jQuery & a small amout of code.
I didn't use display: none as I want to add the transition to the action. (transition won't work with display: none)
$(document).ready(function() {
$(".toggle-button").on("click", function() {
$(this).closest(".testimonial-wrap").toggleClass("active");
});
});
.testimonial-wrap {
background-color: #C1C1C1;
padding: 5px;
margin-bottom: 10px;
}
.testimonial-wrap.active {
background-color: #0095FF
}
.hidden-content {
height: 0px;
visibility: hidden;
transition: all 0.5s ease-out;
}
.active .hidden-content {
height: 100px;
visibility: visible;
transition: all 0.5s ease-in;
background-color: rgba(0, 0, 0, 0.5);
}
button {
display: inline-block;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="testimonial-wrap">
<div id="testimonial">
<p>above testimonial content</p>
<div class="hidden-content">
<p>"hidden content”</p>
</div>
<button id="button1" class="toggle-button">EXPAND</button>
</div>
</div>
<div class="testimonial-wrap">
<div id="testimonial">
<p>above testimonial content</p>
<div class="hidden-content">
<p>"hidden content.”</p>
</div>
<button id="button2" class="toggle-button">EXPAND</button>
</div>
</div>

Related

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.

How to add a transition when styling display? [duplicate]

This question already has answers here:
Transitions on the CSS display property
(37 answers)
Closed 1 year ago.
I have a program, where I use JavaScript to make one div appear and another disappear when I click something:
<div id="first" class="active"> some content here </div>
<div id="second" class="inactive"> some content here </div>
<button onclick="change()"> button </button>
function change(){
document.getElementById("first").className="inactive";
document.getElementById("second").className="active"
}
.active{ display: block; }
.inactive{display: none; }
I'd like to make it so that one div fades in while the other fades out.
I've tried transition: ease 1s;, transition: display 1s and using a custom transition, however none of them have worked.
Try clicking the different buttons on this Carrd - the words fade in and out. I'm going for that effect here.
I'd prefer a solution using only HTML, CSS and/or JavaScript -- trying to keep this project simple.
Use opacity:
function change() {
document.getElementById("first").className = "inactive";
document.getElementById("second").className = "active"
}
.active{
opacity:1;
}
.inactive{
opacity:0;
}
div{
transition:opacity 1s;
}
<div id="first" class="active"> some content here </div>
<div id="second" class="inactive"> some content here </div>
<button onclick="change()"> button </button>
To prevent the hidden element from taking up space, use:
function change() {
const f1 = document.getElementById("first");
f1.style.opacity = "0";
setTimeout(() => {
f1.style.display = "none"
const f2 = document.getElementById("second");
f2.style.display = "block";
setTimeout(() => {
f2.style.opacity = "1";
}, 50);
}, 1000);
}
.active {
display: block;
opacity: 1;
}
.inactive {
display: none;
opacity: 0;
}
div {
transition: opacity 1s;
}
<div id="first" class="active"> some content here </div>
<div id="second" class="inactive"> some content here </div>
<button onclick="change()"> button </button>

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>

How can I select the child of a sibling element?

My accordion keeps filling up the space of my div bigger and smaller based on it opening and closing. I want to wrap a div around it and set it to the height of the accordion expanded to prevent this behavior. Trouble is my js is dependent on selecting this.nextSibling and that breaks everything.
var acc = document.getElementsByClassName("review-button");
var i;
for (i = 0; i < acc.length; i++) {
acc[i].onclick = function() {
this.classList.toggle("active");
var panel = this.nextElementSibling;
if (panel.style.maxHeight){
panel.style.maxHeight = null;
} else {
panel.style.maxHeight = panel.scrollHeight + 'px';
}
}
}
It must be using "this" within scope because there are multiple accordions.
My original thought was to do something like...
this.nextElementSibling.children[0];
but that didn't work.
how do I wrap the current content in a div with a set height, while still keeping the accordion functionality?
<!--accordion 1-->
<button class="review-button" data-target="#demo{{ gameIndex }}">
<span class="review-button-chevron fa fa-angle-down"></span>
Our Reviews
</button>
<!-- Slide out -->
<div class="quote-machine-container">
<div id="demo{{ gameIndex }}" class=" quote-machine"></div>
</div>
<!--accordion 2-->
<button class="review-button" data-target="#demo{{ gameIndex }}">
<span class="review-button-chevron fa fa-angle-down"></span>
Our Reviews
</button>
<!-- Slide out -->
<div class="quote-machine-container">
<div id="demo{{ gameIndex }}" class=" quote-machine"></div>
</div>
Placed each <header> and <div> in it's own <section>
Wrapped everything in <main> and added an eventListener() to it. Any clicks to it will be delegated to the header that was clicked (e.target.)
The event handler (acc()) will:
determine what is e.target (clicked node) by comparing it to the e.currentTarget (the node being captured at the moment.)
Once established, e.target will lose or gain the .active class.
In order to eliminate the limiting property of nextElementSibling, we replace that functionality with a simple CSS ruleset: .active + .x-panel which is basically the same as nextElementSibling.
SNIPPET
var main = document.getElementById("x-main");
main.addEventListener('click', acc, false);
function acc(e) {
if (e.target !== e.currentTarget) {
var tgt = e.target;
tgt.classList.toggle("active");
}
}
.x-section {
height: 100px;
}
.x-header {
cursor: pointer;
border: 3px outset grey;
height: 30px;
}
.x-panel {
border: 3px inset black;
max-height: 0;
opacity: 0;
transition: opacity .2s ease-in;
}
.active + .x-panel {
opacity: 1;
max-height: 300px;
overflow-y: hidden;
transition: max-height 1s ease-in .1s, opacity 1s;
}
<main id='x-main'>
<section class='x-section'>
<header class='x-header'>HEADER</header>
<div class='x-panel'>
CONTENT CONTENT CONTENT CONTENT CONTENT CONTENT CONTENT
</div>
</section>
<section class='x-section'>
<header class='x-header'>HEADER</header>
<div class='x-panel'>
CONTENT CONTENT CONTENT CONTENT CONTENT CONTENT
</div>
</section>
<section class='x-section'>
<header class='x-header'>HEADER</header>
<div class='x-panel'>
CONTENT CONTENT CONTENT CONTENT CONTENT CONTENT CONTENT CONTENT
</div>
</section>
</main>

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