removing a class when a search box is clicked - javascript

TLDR; The functionality I'm trying to create in this code example is upon clicking a box (collapsible has active class), and then you click on the search box, the active class is removed closing the collapsible. Currently, the remove class is not working. Any idea why?
In more detail:
I have a list of collapsible blocks get an active class when they are clicked. You can search a search box to filter out which block you want to be displayed, and thus click on it to display more content. I have realized that when you go back to search and didn't 'unclick' a box (to close the collapsible) the box is still active (which makes perfect sense).
I had an idea that upon search box focus, I would loop through all the collapsible and remove the active class itself (in turn, closing the collapsible).
I've gotten to the point where I can find which collapsible is active, but I cannot remove the active class.
Is this because the box was initially clicked to add the class and has to be physically clicked once again to remove it?
var coll = document.getElementsByClassName("collapsible");
var i;
for (i = 0; i < coll.length; i++) {
coll[i].addEventListener("click", function() {
this.classList.toggle("active");
var content = this.nextElementSibling;
if (content.style.maxHeight) {
content.style.maxHeight = null;
} else {
content.style.maxHeight = content.scrollHeight + "px";
}
});
}
function searchClick() {
var searchTable;
searchTable = document.getElementById("search-table");
faqButtons = searchTable.getElementsByTagName("button");
for (i = 0; i < faqButtons.length; i++) {
if (faqButtons[i].classList.contains("active")) {
console.log('THIS ONE: ', faqButtons[i]);
faqButtons[i].classList.remove("active");
}
}
}
.collapsible {
display: flex;
justify-content: space-between;
background-color: white;
color: black;
cursor: pointer;
padding: 18px;
width: 100%;
border: none;
text-align: left;
outline: none;
font-size: 20px;
}
.collapsible span {
padding-left: 5px;
padding-right: 5px;
}
.collapsible .active,
.collapsible:hover {
background-color: #333;
color: white;
}
.content {
padding: 0 18px;
max-height: 0;
overflow: hidden;
transition: max-height 0.2s ease-out;
}
.content p {
margin-top: 10px;
}
<div class="search-faq">
<input autocomplete="off" type="text" id="search-faq" placeholder="Search for FAQ" onfocus="searchClick()" />
</div>
<div id="search-table" class="superHidden">
<div id="wolfandgrizzly-FAQ-search">
<h1>A to B sales</h1>
<button class="collapsible"><span>What are our shipping policies?</span></button>
<div class="content">
<p>
They are crazy cool.
</p>
</div>
<button class="collapsible"><span>Are you making more products?</span></button>
<div class="content">
<p>
We'll sell you more very soon
</p>
</div>
</div>

I don't see where you are getting that the active class is not being removed. It doesn't appear that there is a problem with that. I think the problem is that you are showing the answers via maxHeight setting, but you aren't resetting it when you click the search box. I've updated it in this snippet, but essentially it boiled down to adding the following in your loop:
var content = faqButtons[i].nextElementSibling
content.style.maxHeight = null
var coll = document.getElementsByClassName("collapsible");
var i;
for (i = 0; i < coll.length; i++) {
coll[i].addEventListener("click", function() {
this.classList.toggle("active");
var content = this.nextElementSibling;
if (content.style.maxHeight) {
content.style.maxHeight = null;
} else {
content.style.maxHeight = content.scrollHeight + "px";
}
});
}
function searchClick() {
var searchTable;
searchTable = document.getElementById("search-table");
faqButtons = searchTable.getElementsByTagName("button");
for (i = 0; i < faqButtons.length; i++) {
if (faqButtons[i].classList.contains("active")) {
console.log('THIS ONE: ', faqButtons[i]);
faqButtons[i].classList.remove("active");
}
var content = faqButtons[i].nextElementSibling
content.style.maxHeight = null
}
}
.collapsible {
display: flex;
justify-content: space-between;
background-color: white;
color: black;
cursor: pointer;
padding: 18px;
width: 100%;
border: none;
text-align: left;
outline: none;
font-size: 20px;
}
.collapsible span {
padding-left: 5px;
padding-right: 5px;
}
.collapsible .active,
.collapsible:hover {
background-color: #333;
color: white;
}
.content {
padding: 0 18px;
max-height: 0;
overflow: hidden;
transition: max-height 0.2s ease-out;
}
.content p {
margin-top: 10px;
}
<div class="search-faq">
<input autocomplete="off" type="text" id="search-faq" placeholder="Search for FAQ" onfocus="searchClick()" />
</div>
<div id="search-table" class="superHidden">
<div id="wolfandgrizzly-FAQ-search">
<h1>A to B sales</h1>
<button class="collapsible"><span>What are our shipping policies?</span></button>
<div class="content">
<p>
They are crazy cool.
</p>
</div>
<button class="collapsible"><span>Are you making more products?</span></button>
<div class="content">
<p>
We'll sell you more very soon
</p>
</div>
</div>

Here's an approach that uses as little JavaScript as possible. If JavaScript is disabled, the only thing that will break is the search box. Sections will still open and close as expected when clicked.
The accordion stores its state in checkboxes, one in each section. Each section's title is a label element which toggles that section's checkbox when clicked. The sections are expanded and collapsed using CSS :checked selectors.
var sections = [].slice.call(document.querySelectorAll(".accordion li")),
searchAccordion = function() {
var value = document.getElementById("search").value.toLowerCase();
sections.map(function(section) {
var content = section.textContent.toLowerCase();
section.querySelector("input").checked = content.includes(value);
});
};
body {
font-family: sans-serif;
}
.accordion {
padding-left: 0;
margin: -1rem;
}
.accordion li {
list-style-type: none;
}
.accordion input {
display: none;
}
.accordion label {
background-color: #eee;
transition: background-color 100ms;
cursor: pointer;
font-size: 1.2rem;
padding: 1rem;
display: block;
}
.accordion label:hover {
background-color: #444;
color: white;
}
.accordion .content {
padding: 1rem;
display: none;
}
.accordion input:checked ~ .content {
display: block;
}
<input id="search" onKeyup="searchAccordion()" type="text" placeholder="Search for FAQ" autocomplete="off">
<h1>A to B sales</h1>
<ul class="accordion">
<li>
<input type="checkbox" id="section1">
<label for="section1">What are our shipping policies?</label>
<div class="content">They are crazy cool.</div>
</li>
<li>
<input type="checkbox" id="section2">
<label for="section2">Are you making more products?</label>
<div class="content">We'll sell you more very soon</div>
</li>
</ul>

Related

Animated Collapsibles

I found this nice future to collapse elements on w3schools: https://www.w3schools.com/howto/tryit.asp?filename=tryhow_js_collapsible_symbol
I would like to revert it, so that all elements are shown when page is loaded, and then use this future to toggle between visible/hidden..
I have this code:
<style>
.collapsible {
background-color: #777;
color: white;
cursor: pointer;
padding: 5px;
width: 100%;
border: none;
text-align: left;
outline: none;
font-size: 12px;
}
.collapsible:after {
content: '\002B';
color: white;
font-weight: bold;
float: right;
margin-left: 5px;
}
.collapsible.active:after {
content: "\2212";
}
.content {
padding: 0 20px;
max-height: 0;
overflow: hidden;
transition: max-height 0.2s ease-out;
background-color: #f1f1f1;
}
</style>
<script>
$(document).ready(function() {
var coll = document.getElementsByClassName("collapsible");
var i;
for (i = 0; i < coll.length; i++) {
coll[i].addEventListener("click", function() {
this.classList.toggle("active");
var content = this.nextElementSibling;
if (content.style.maxHeight){
content.style.maxHeight = null;
} else {
content.style.maxHeight = content.scrollHeight + "px";
}
});
}
});
</script>
Example here: https://jsfiddle.net/g50v4cL2/
So when page is loaded, i want to have it like this:
And then able to collapse.. can anyone help me with this?
Thank you.
There're many ways to do it, in this case, I tried to not make many changes and to stick to the way you have, so you can just add active class to your elements, and change your js code a bit to be like this:
var coll = document.getElementsByClassName("collapsible");
var i;
const adjustContentHeight = (content) => {
if (content.style.maxHeight){
content.style.maxHeight = null;
} else {
content.style.maxHeight = content.scrollHeight + "px";
}
}
for (i = 0; i < coll.length; i++) {
adjustContentHeight(coll[i].nextElementSibling);
coll[i].addEventListener("click", function() {
this.classList.toggle("active");
adjustContentHeight(this.nextElementSibling);
});
}
.collapsible {
background-color: #777;
color: white;
cursor: pointer;
padding: 5px;
width: 100%;
border: none;
text-align: left;
outline: none;
font-size: 12px;
}
.collapsible:after {
content: '\002B';
color: white;
font-weight: bold;
float: right;
margin-left: 5px;
}
.collapsible.active:after {
content: "\2212";
}
.content {
padding: 0 20px;
max-height: 0;
overflow: hidden;
transition: max-height 0.2s ease-out;
background-color: #f1f1f1;
}
<button class='collapsible active'>Test1</button>
<div class='content'>
<label><input type = 'checkbox' value = 'value1'/> value1</label>
<label><input type = 'checkbox' value = 'value2'/> value2</label>
<label><input type = 'checkbox' value = 'value3'/> value3</label>
<label><input type = 'checkbox' value = 'value4'/> value4</label>
</div>
<button class='collapsible active'>Test2</button>
<div class='content'>
<label><input type = 'checkbox' value = 'value5'/> value5</label>
<label><input type = 'checkbox' value = 'value6'/> value6</label>
<label><input type = 'checkbox' value = 'value7'/> value7</label>
</div>
If you don't want to see this transition in the beginning, consider removing the transition property from css and adding it in js after this code

Why is does the toggle function only work after double clicking? (Javascript, CSS)

Following this w3 guide to creating collapsible divs, but inside a React app. The cards I made only collapse after double clicking instead of single clicking. The only difference I can find is that I'm not using <script> and instead using onClick={function}.
Here's my code:
At top of Card component file:
function collapseElement() {
var coll = document.getElementsByClassName("collapsible");
var i;
for (i = 0; i < coll.length; i++) {
coll[i].addEventListener("click", function() {
console.log(i);
this.classList.toggle("active");
var content = this.nextElementSibling;
if (content.style.display === "block") {
content.style.display = "none";
} else {
content.style.display = "block";
}
});
}
}
In Card Component:
<div>
<button type="button" className="collapsible" onClick={collapseElement}>
<div className="collapsible-title">
{props.title}
</div>
</button>
<div className="content">
{props.children}
</div>
</div>
and the CSS:
.collapsible {
background-color: #777;
color: white;
cursor: pointer;
padding: 18px;
width: 100%;
border: none;
text-align: left;
outline: none;
font-size: 15px;
}
.active, .collapsible:hover {
background-color: #555;
}
.content {
padding: 0 18px;
max-height: 0;
overflow: hidden;
transition: max-height 0.2s ease-out;
background-color: #f1f1f1;
}
Thanks in advance!
You've set up the button's onClick to run collapseElement, which then goes through a list of elements with the "collapsible" class, and adds another click-event-listener to them to do the collapsing.
Either just run that function that adds a new click listener to every item, or just split out the actual function that toggles the content, and give that as the onClick tag.

Javascript : Move to next element in a DIV using key

Is it possible to move to next element in my DIV wih E and return to previous element with A. I'm trying to make a menu and navigate within only with keys.
// Add active class to the current button (highlight it)
var header = document.getElementById("myDIV");
var btns = header.getElementsByClassName("btn1");
for (var i = 0; i < btns.length; i++) {
btns[i].addEventListener("click", function() {
var current = document.getElementsByClassName("active");
if (current.length > 0) {
current[0].className = current[0].className.replace(" active", "");
}
this.className += " active";
});
}
/* Mini menu CSS */
/* Style the buttons */
#myDIV{
margin-top:50px;
margin-left: 12px;
margin-right: px;
display:inline-flex;
}
#myDIV btn1{
top:3px;
}
#myDIV p{
top:4px;
/*margin-left: 10px;*/
letter-spacing: 1.8px;
}
.btn1 {
border: none;
outline: none;
padding: 3px 12px;
cursor: pointer;
font-size: 18px;
border-radius: 2rem;
transition: 0.2s;
position: relative;
text-align: center;
align-items: center;
justify-content: center;
overflow: visible;
color: #000;
font-family: Proxima Nova;
font-weight: bold;
}
.active {
border-color: #366eaf;
border-width: 2px;
border-style: solid;
}
<div id="myDIV" style="display: inline-flex; margin-left: 15px; margin-right: 5px;">
<p class="btn1 active">ALL</p>
<p class="btn1">MENU1</p>
<p class="btn1">MENU2</p>
<p class="btn1">MENU3</p>
<p class="btn1">MENU4</p>
<p class="btn1">MENU5</p>
</div>
Semantically, you should be using an unordered list (<ul>) since you are actually making a list of menu items.
Next, it really isn't a "move" that you want, but a change of which element has the active class applied to it.
Your original JavaScript (while working) is more than you needed to be doing. See my reworked version with comments.
Lastly, you had some errors and some redundancy in your CSS.
See the comments inline for details.
// Listen for key strokes on the document
document.addEventListener("keydown", function(event){
// Get the currently active element
let activeElement = document.querySelector(".active");
// Check for "e" and if there is a previous sibling
if(event.key == "a" && activeElement.previousElementSibling){
// Make the previous sibling (if any) element active
deselectAll();
activeElement.previousElementSibling.classList.add("active");
} else if(event.key == "e" && activeElement.nextElementSibling){
// Make the next sibling element (if any) active
deselectAll();
activeElement.nextElementSibling.classList.add("active");
}
});
// Just set up one event handler on the parent of all the menu items
// Any click within that parent will bubble up and be handled here
document.getElementById("menu").addEventListener("click", function(event) {
// event.target is the actual element that triggered the event
if(event.target.classList.contains("btn1")){
deselectAll();
event.target.classList.add("active"); // Add active to the clicked item
}
});
// Don't use .getElementsByClassName() - - it's outdated
let items = document.querySelectorAll(".btn1");
function deselectAll(){
// Loop over all the menu items
items.forEach(function(item){
item.classList.remove("active"); // Remove the active class if its there
});
}
/* Mini menu CSS */
/* Style the buttons */
#menu{
margin-top:50px;
display: inline-flex;
margin-left: 15px;
margin-right: 5px;
list-style-type:none;
}
.btn1 {
top:3px;
letter-spacing: 1.8px;
border: none;
outline: none;
padding: 3px 12px;
cursor: pointer;
font-size: 18px;
border-radius: 2rem;
transition: 0.2s;
position: relative;
text-align: center;
align-items: center;
justify-content: center;
overflow: visible;
color: #000;
font-family: Proxima Nova;
font-weight: bold;
/* Give non-active items an invisible 2px border
so that when they do become active the overall
size of the element doesn't shift around. */
border: 1px solid rgba(0,0,0,0);
}
.active {
border-color: #366eaf;
border-width: 2px;
border-style: solid;
}
<ul id="menu">
<li class="btn1 active">ALL</li>
<li class="btn1">MENU1</li>
<li class="btn1">MENU2</li>
<li class="btn1">MENU3</li>
<li class="btn1">MENU4</li>
<li class="btn1">MENU5</li>
</ul>

Javascript not opening/closing accordion

I've tried a few different ways but can not tell why my.JS isn't opening and closing my accordion. Please check my code. Your help would be appreciated.
var acc = document.getElementsByClassName("accordion");
var i;
for (i = 0; i < acc.length; i++) {
acc[i].addEventListener("click", function() {
this.classList.toggle("active");
var panel = this.nextElementSibling;
if (panel.style.display === "block") {
panel.style.display = "none";
} else {
panel.style.display = "block";
}
});
}
/*this javascript seems to be fine and matches what some answers have said, and i've reset the page cache but i cannot seem to get it to work */
body {
text-align: center;
background-color: #ffffff;
}
h1 {
font: 40px courier new;
text-align: center;
}
a {
font: 25px arial;
text-align:center;
}
.accordion {
background-color: #eee;
color: #444;
cursor: pointer;
padding: 18px;
width: 60%;
text-align: center;
border: none;
outline: none;
transition: 0.4s;
}
.active, .accordion:hover {
background-color: #ccc;
}
.panel {
padding: 0 18px;
background-color: white;
overflow: hidden;
font: 16px Source Sans Pro;
width:60%;
margin:0 auto;
display:none;
height:200px auto
}
p{
text-align:center;
}
<div class="wrap1">
<button class="accordion">About Us Page Map</button>
<div id="pan1" class="panel">
<p>
Personel section<br />
Acheivments<br />
History<br />
</p>
</div>
</div>
I've added the css, i do have a class called accordion but it didn't show in the original question sorry!
The problem is here:
var acc = document.getElementsByClassName("accordion");
You don't have "accordion" class in your HTML file, that's why it is not able to recognize it. You have to take a button, assign it a class named accordion and then it will work properly.
var acc = document.getElementsByClassName("accordion");
var i;
for (i = 0; i < acc.length; i++) {
acc[i].addEventListener("click", function() {
this.classList.toggle("active");
var panel = this.nextElementSibling;
if (panel.style.display === "block") {
panel.style.display = "none";
} else {
panel.style.display = "block";
}
});
}
.accordion {
background-color: #eee;
color: #444;
cursor: pointer;
padding: 18px;
width: 100%;
border: none;
text-align: left;
outline: none;
font-size: 15px;
transition: 0.4s;
}
.active,
.accordion:hover {
background-color: #ccc;
}
.panel {
padding: 0 18px;
display: none;
background-color: white;
overflow: hidden;
}
<button class="accordion">Personal</button>
<div class="panel">
<p>Personal here</p>
</div>
<button class="accordion">Achievements</button>
<div class="panel">
<p>Achievements here</p>
</div>
<button class="accordion">History</button>
<div class="panel">
<p>History here</p>
</div>
Source: HTML and CSS Accordion - w3schools
Hope this helps.
I would suggest refactoring some of your code to make it more readable such as
HTML
Item 1
<div id="item1" class="accordion-content">This is my accordion Item 1 content</div>
Item 2
<div id="item2" class="accordion-content">This is my accordion Item 2 content</div>
JS
document.querySelectorAll(".accordion").forEach(function(node) {
node.addEventListener("click", function(e) {
this.nextElementSibling.toggleClass("active');
});
});
CSS
.accordion-content:not(.active) {
display: none;
}
Besides the refactor, I'm unable to see an element with the className accordion. It seems you are missing the HTML containing the actual accordion.

Accordion stoped working after i pasted it in my

i have site running on wordpress template.
i've pasted in the whole accordion code from W3School (here it is https://www.w3schools.com/howto/howto_js_accordion.asp).
my version unexpectedly doesn't work.
i've included css lines in the main.css file:
....
/* styles for accordion*/
.accordion {
background-color: #eee;
color: #444;
cursor: pointer;
padding: 18px;
width: 100%;
border: none;
text-align: left;
outline: none;
font-size: 15px;
transition: 0.4s;
}
.active, .accordion:hover {
background-color: #ccc;
}
.panel {
padding: 0 18px;
background-color: white;
max-height: 0;
overflow: hidden;
transition: max-height 0.2s ease-out;
}
/*end styles for accordion*/
i've added the html code on the page through the admin console:
<h2>Accordion</h2>
<button class="accordion">Section 1</button>
<div class="panel"> <p>Lorem ipsum</p> </div>
<button class="accordion">Section 2</button>
<div class="panel"> <p>Lorem ipsum</p> </div>
<button class="accordion">Section 3</button>
<div class="panel"> <p>Lorem ipsum</p> </div>
next i've created scripts.js file and added it into the template with wp_enqueue_script in functions.php. so i can see it loads when the page code is shown.
content of the scripts.js file:
$(document).ready(function() {
var acc = document.getElementsByClassName("accordion");
var i;
for (i = 0; i < acc.length; i++) {
acc[i].addEventListener("click", function() {
this.classList.toggle("active");
var panel = this.nextElementSibling;
if (panel.style.display === "block") {
panel.style.display = "none";
} else {
panel.style.display = "block";
}
});
}
});
then nothing happens on click on the accordion: no js errors in the console, no content of the accordion shows up. how could i make it work?
upd 1
changed class names to unique ones
js
var acc = document.getElementsByClassName("nikaccordion");
var i;
for (i = 0; i < acc.length; i++) {
acc[i].addEventListener("click", function() {
this.classList.toggle("nikactive");
var nikpanel = this.nextElementSibling;
if (nikpanel.style.display === "block") {
nikpanel.style.display = "none";
} else {
nikpanel.style.display = "block";
}
});
}
css:
/* styles for accordion*/
.nikaccordion {
background-color: #eee;
color: #444;
cursor: pointer;
padding: 18px;
width: 100%;
border: none;
text-align: left;
outline: none;
font-size: 15px;
transition: 0.4s;
}
.nikactive, .nikaccordion:hover {
background-color: #ccc;
}
.nikpanel {
padding: 0 18px;
display: none;
background-color: white;
overflow: hidden;
}
/*end styles for accordion*/
html:
<h2>Accordion</h2>
<button class="nikaccordion">Section 1</button>
<div class="nikpanel">
<p>Lorem ipsum</p>
</div>
<button class="nikaccordion">Section 2</button>
<div class="nikpanel">
<p>Lorem ipsum </p>
</div>
<button class="nikaccordion">Section 3</button>
<div class="nikpanel">
<p>Lorem ipsum </p>
</div>
upd 2
when im changing the order of the .js files that my file goes at the end of a list then i get an error
Uncaught TypeError: Cannot read property 'style' of null
at HTMLButtonElement.<anonymous> (scripts.js?ver=5.2.2:8)
so i suppose the
var nikpanel = this.nextElementSibling;
doesn't work
Your mistake is that you've mixed the basic version (non-animated) with the animated one.
For the basic version remove the min-height:0; and replace it with display:none.
This is because your max-height is 0 and your javascript code is changing the display value from 'none' to 'block' and vice-versa on click. Therefore, the element becomes a block element, however, it's height is limited by max-height:0.
Your CSS should be:
.accordion {
background-color: #eee;
color: #444;
cursor: pointer;
padding: 18px;
width: 100%;
border: none;
text-align: left;
outline: none;
font-size: 15px;
transition: 0.4s;
}
.active, .accordion:hover {
background-color: #ccc;
}
.panel {
padding: 0 18px;
background-color: white;
display: none;
overflow: hidden;
}
Or you could just change your JS to change the max-height value instead of the display value.
var acc = document.getElementsByClassName("accordion");
var i;
for (i = 0; i < acc.length; i++) {
acc[i].addEventListener("click", function() {
this.classList.toggle("active");
var panel = this.nextElementSibling;
if (panel.style.maxHeight){
panel.style.maxHeight = null;
} else {
panel.style.maxHeight = panel.scrollHeight + "px";
}
});
}
In your CSS replace
.panel {
padding: 0 18px;
background-color: white;
max-height: 0;
overflow: hidden;
transition: max-height 0.2s ease-out;
}
with
.panel {
padding: 0 18px;
display: none;
background-color: white;
overflow: hidden;
}
I've just tested your code and just that replacement get it working
i found out that WordPress adds p-tags to my html code thus the js script treated the phantom p-tags as target tags and tried to apply actions to them.
i disabled this addings by:
remove_filter( 'the_content', 'wpautop' );
remove_filter( 'the_excerpt', 'wpautop' );
in my functions.php

Categories

Resources