Making multilayer accordions with pure JS and using nextElementSibling - javascript

New to Javascript. I recently posted a question about creating multiple multilayer accordions. I got some great feedback, but someone mentioned that if my HTML was set up correctly, I could achieve the same goal by using nextElementSibling and thus have much cleaner JS.
I figured out how to do this using only queryselect. See the below example:
HTML:
<div class="mainAccordion">
<h2>dropdown one</h2>
<h3>dropdown two</h3>
<p>content content content content</p>
</div>
CSS:
.mainAccordion {
background-color:lightblue;
width:200px;
margin:auto;
padding:3%;
}
.mainAccordion :nth-child(1){
background-color: blue;
padding:3%;
cursor:pointer;
color:white;
}
.mainAccordion :nth-child(2){
background-color:yellow;
cursor:pointer;
max-height:0;
overflow:hidden;
}
.mainAccordion :nth-child(3){
font-weight:bold;
max-height:0;
overflow:hidden;
}
And the JS:
var mainAccordion = document.querySelector(".mainAccordion").addEventListener("click", function(e) {
if (e.target.nextElementSibling.style.maxHeight) {
e.target.nextElementSibling.style.maxHeight = null;
} else {
e.target.nextElementSibling.style.maxHeight = e.target.nextElementSibling.scrollHeight + "px";
}
});
This works as intended. However, when I introduce multiple multilayer accordions and switch to "querySelectorAll", it stops working. Also depending on the browser, I sometimes get an error message saying my "addEventListener" is not a function.
See below:
HTML:
<div class="mainAccordion">
<h2>dropdown one</h2>
<h3>dropdown two</h3>
<p>content content content content</p>
</div>
<div class="mainAccordion">
<h2>dropdown one</h2>
<h3>dropdown two</h3>
<p>content content content content</p>
</div>
CSS:
body {
display:flex;
width: 900px;
margin:auto;
}
.mainAccordion {
background-color:lightblue;
width:200px;
margin:auto;
padding:3%;
}
.mainAccordion :nth-child(1){
background-color: blue;
padding:3%;
cursor:pointer;
color:white;
}
.mainAccordion :nth-child(2){
background-color:yellow;
cursor:pointer;
max-height:0;
overflow:hidden;
}
.mainAccordion :nth-child(3){
font-weight:bold;
max-height:0;
overflow:hidden;
}
and JS:
var mainAccordion = document.querySelectorAll(".mainAccordion").addEventListener("click", function(e) {
if (e.target.nextElementSibling.style.maxHeight) {
e.target.nextElementSibling.style.maxHeight = null;
} else {
e.target.nextElementSibling.style.maxHeight = e.target.nextElementSibling.scrollHeight + "px";
}
});
I've tried changing "querySelectorAll(".mainAccordion") to getElementsByClassName("mainAccordion") but also doesn't work.
Is forEach somehow involved?
Note: I know you can also achieve the same goal by toggling a class that has the "max-height:0;overflow:hidden". However, this was how I was initially taught to do accordions.
This is for my own practice.
I appreciate the help.

Try this:
let accordionElements = document.querySelectorAll(".mainAccordion");
accordionElements.forEach(element => {
element.addEventListener("click", function(e) {
if (e.target.nextElementSibling.style.maxHeight) {
e.target.nextElementSibling.style.maxHeight = null;
} else {
e.target.nextElementSibling.style.maxHeight = e.target.nextElementSibling.scrollHeight + "px";
}
})
});
It's because with querySelector() an HTML Element is return. With querySelectorAll() it's a NodeList. In your sample code you try to attach an event to a node list which is not possible.
You need to loop inside and then attaching the event to each HTML Element inside.

i think that the problem is that the querySelector() returns the first Element within the document that matches the specified selector. then the event listener will be applied to the first element found.
the querySelectorAll() returns a list. you could use it with forEach like this
var mainAccordion = document.querySelectorAll(".mainAccordion");
console.log(mainAccordion);
mainAccordion.forEach(accordion => {
accordion.addEventListener("click", function(e) {
if (e.target.nextElementSibling.style.maxHeight) {
e.target.nextElementSibling.style.maxHeight = null;
} else {
e.target.nextElementSibling.style.maxHeight =
e.target.nextElementSibling.scrollHeight + "px";
}
});
});

Related

Faded text not reappearing

I'm working on a random wiki viewer, and its been a slog, but i'm finally at the point where i think that at least the UI's functionality is done. The only problem is that after i fade some text on the "random" button, and replace it with an iframe which is then removed when the button is clicked again, the text doesn't seem to fade back in. Any ideas?
https://codepen.io/EpicTriffid/pen/WOYrzg
$(document).ready(function() {
//Random Button
var but1status = "closed"
var randFrame = ("#randframe")
$(".button1").on("click",function () {
var but = $(".button1");
var cross = $("#cross1");
but.animate({marginTop:"10%", width:"100%", height:"100vh"}, "fast");
$(".b1text").animate({opacity:0});
cross.delay(1000).fadeIn();
but1status = "open"
if (but1status == "open") {
setTimeout(randFrame,1000)
function randFrame (){
$(".button1").html("<iframe class='randframe' src='demo_iframe.htm' height='100%' width='100%' style='border:none'></iframe>");
$("#cross1").click(function() {
$('.button1').removeAttr('style');
$("#cross1").fadeOut('fast');
$('.randframe').remove();
$(".b1text").animate({opacity:"1"});
});
};
};
});
You are emptying the HTML of .button1 when you do:
$(".button1").html(....
In order to get it back, you need to add:
$(".button1").html('<div class="b1text">Random</div>');
after
$('.randframe').remove();
Your button is missing the text Random
When you call:
$(".button1").html(...
you are replacing the inside html of the object with the iframe.
When you remove .randframe you need then re-add the text for your button.
Instead of:
$('.randframe').remove()
you can call this which will accomplish both:
$('.button1').html('random');
Efficiency tip: You did a good job of saving references to your jquery variables but and cross, why not use them?
but.html(...
cross.click(function (){...
This line effectively replaces whatever you have in the button 1 div
$(".button1").html("<iframe class='randframe' src='demo_iframe.htm' height='100%' width='100%' style='border:none'></iframe>");
Your cross1.click function does not re-populate the button1 div, I would recommend
$("#cross1").click(function() {
$('.button1').removeAttr('style');
$('.button1').html('Random');
$("#cross1").fadeOut('fast');
$(".b1text").animate({opacity:"1"});
});
Here you go with the solution https://codepen.io/Shiladitya/pen/WOLNpw
$(document).ready(function() {
//Random Button
var but1status = "closed"
var randFrame = ("#randframe")
$(".button1").on("click",function () {
var but = $(".button1");
var cross = $("#cross1");
but.animate({marginTop:"10%", width:"100%", height:"100vh"}, "fast");
$(".b1text").fadeOut();
cross.delay(1000).fadeIn();
but1status = "open"
if (but1status == "open") {
setTimeout(randFrame,1000)
function randFrame (){
$(".button1").html("<iframe class='randframe' src='demo_iframe.htm' height='100%' width='100%' style='border:none'></iframe>");
$("#cross1").click(function() {
but.removeAttr('style');
cross.fadeOut('fast');
$('.randframe').remove();
but.html('<div class="b1text">Random</div>');
});
};
};
});
//Search Button
var but2 = "closed"
$(".button2").click(function () {
var but = $(".button2");
var btext = $(".b2text");
var cross = $("#cross2");
but.animate({marginTop:"10%", width:"100%", height:"100vh"}, "fast");
btext.fadeOut();
cross.delay(2000).fadeIn()
but2 = "open"
$("#cross2").click(function() {
$('.button2').removeAttr('style');
$('.b2text').fadeIn(1500);
$("#cross2").fadeOut('fast');
})
})
});
#spacer {
margin:0;
padding:0;
height:50px;
}
body {
min-height: 100%;
min-width: 1024px;
width:100%;
margin-top:4em;
padding:0;
background-color: teal;
}
h1 {
color:white;
font-family:"cabin";
text-align:center;
}
#cross1 {
position:relative;
font-size:3em;
color:white;
margin-top:6px;
float: left;
display:none;
}
#cross2 {
position:relative;
font-size:3em;
color:white;
margin-top:6px;
float: right;
display:none;
}
#randframe {
display:none;
}
.button1 {
position:absolute;
height:2.6em;
width:5em;
font-size:1.5em;
text-align:center;
color: white;
font-family:"cabin";
border:solid;
border-radius:25px;
padding:10px;
box-sizing:border-box;
transition: all 2s ease;
}
.button2 {
position:absolute;
right:0;
height:2.6em;
width:5em;
font-size:1.5em;
text-align:center;
color: white;
font-family:"cabin";
border:solid;
border-radius:25px;
padding:10px;
box-sizing:border-box;
transition: all 2s ease;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<head>
<link href="https://fonts.googleapis.com/css?family=Josefin+Slab" rel="stylesheet">
<link href="https://fonts.googleapis.com/css?family=Raleway" rel="stylesheet">
<link href="https://fonts.googleapis.com/css?family=Crimson+Text" rel="stylesheet">
<link href="https://fonts.googleapis.com/css?family=Cabin" rel="stylesheet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet">
</head>
<div class="container">
<div class="row">
<div class="col-xs-12">
<h1>Wiki Viewer</h1>
</div>
</div>
</div>
<div id="spacer"></div>
<div class="container">
<div class="row">
<div class="col-xs-12">
<div class="button1">
<div class="b1text">Random</div>
</div>
<div class="button2">
<div class="b2text">Search</div>
</div>
</div>
</div>
<div class="row">
<div class="col-xs-12">
<div class="text-center">
<i id="cross1" class="glyphicon glyphicon-remove"></i>
</div>
</div>
<div class="col-xs-12">
<div class="text-center">
<i id="cross2" class="glyphicon glyphicon-remove"></i>
</div>
</div>
You need to keep the content inside the ".button1" to reuse after the iframe is removed.
Try using callbacks. So change your #cross1 fadout to
$("#cross1").fadeOut('fast',function(){
$('.randframe').remove();
$(".b1text").animate({opacity:"1"});
});
Also, this may not be affecting your code, but you're missing some semi colons after some variable declarations.
Not all methods have callbacks in JQuery, but when available, they are useful because basically it means that your code is not fired until the other thing is completely done. This happens a lot with fading and opacity.

How to make one option active and disable the others

i'm creating a navbar. when i click one of the options , that option becomes active but when i click on the other option the previous option which was active remains there
SEE MY FIDDLE EXAMPLE : https://jsfiddle.net/bpmbu3th/
I just want to make the item active which is clicked
MY HTML
<p id="parent1">i'm the parent</p>
<p id="child1">i'm the first child</p>
<p id="parent2">i'm the 2nd parent</p>
<p id="child2">i'm the second children</p>
MY CSS
#parent1{
background-color:#000;
color:#fff;
}
#child1{
display:none;
background-color:red;
color:#fff;
}
#parent2{
background-color:#000;
color:#fff;
}
#child2{
display:none;
background-color:red;
color:#fff;
}
MY JQUERY
(function(){
var object = {
dropdown1:$('#child1'),
dropdown2:$('#child2'),
dropdown1parent: function(){
if(object.dropdown1.is(':hidden')){
object.dropdown1.fadeIn();
}
else{
object.dropdown1.fadeOut();
}
},
dropdown2parent: function(){
if(object.dropdown2.is(':hidden')){
object.dropdown2.fadeIn();
}
else
{
object.dropdown2.fadeOut();
}
}
};
$('#parent1').on('click', object.dropdown1parent);
$('#parent2').on('click', object.dropdown2parent);
})();
You should use common class to get rid of repetitive code.
Here's an example
$(function() {
$('.parent').on('click', function() {
var child = $(this).next('.child'); //Finds the next child
//fade out others then handle current child
$('.child').not(child).fadeOut(function() {
child.fadeToggle();
})
});
});
.parent {
background-color:#000;
color:#fff;
}
.child {
display:none;
background-color:red;
color:#fff;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<p class="parent">i'm the parent</p>
<p class="child">i'm the first child</p>
<p class="parent">i'm the 2nd parent</p>
<p class="child">i'm the second children</p>
You just have to add two lines of code in your javascript, here is the edited snippet -
dropdown1parent: function(){
if(object.dropdown1.is(':hidden')){
object.dropdown1.fadeIn();
object.dropdown2.fadeOut(); //this line
}
else{
object.dropdown1.fadeOut();
}
},
dropdown2parent: function(){
if(object.dropdown2.is(':hidden')){
object.dropdown1.fadeOut(); //this line
object.dropdown2.fadeIn();
}
else
{
object.dropdown2.fadeOut();
}
}
Is that you wanted?
You need to fade out the other one:
(function () {
var object = {
dropdown1: $('#child1'),
dropdown2: $('#child2'),
dropdown1parent: function () {
if (object.dropdown1.is(':hidden')) {
object.dropdown1.fadeIn();
object.dropdown2.fadeOut();
} else {
object.dropdown1.fadeOut();
object.dropdown2.fadeOut();
}
},
dropdown2parent: function () {
if (object.dropdown2.is(':hidden')) {
object.dropdown2.fadeIn();
object.dropdown1.fadeOut();
} else {
object.dropdown1.fadeOut();
object.dropdown2.fadeOut();
}
}
};
$('#parent1').on('click', object.dropdown1parent);
$('#parent2').on('click', object.dropdown2parent);
})();
https://jsfiddle.net/ghorg12110/bpmbu3th/1/
You should think about creating a function to manage that, it will become a nightmare if you use a lot of "dropdown".

Want to fetch the id of the different div elements on the click of p element using javascript

I have a page with p tags and div element, the div element is set with display:none in the starting so, I just want to display the different divs as shown below inside the body tag on the click of the p tag but i got stuck in fetching the different id of the divs. Please do help me out from this situation.Below is my code. Thanks
<script>
function toggle(id)// here is the function which gets the different ids of the div
{ var element = document.getElementById(id);
for(i=1; i<3; i++)
{
if(element[i].style.display == "none")
{
element[i].style.display = "block";
}
else
{
element[i].style.display = "none"
}
}
}
</script>
<body>
<p onclick="toggle('div1')">Sentence1</p>
<p onclick="toggle('div2')">Sentence2</p>
<div id="div1" name="Name 1" style="display:none; width:400px; height:300px; border:1px solid black; background-color:yellow;" id="div1">Barun Ghatak</div>
<div id="div2" style="display:none; width:400px; height:300px; border:1px solid black; background-color:black;" id="div2">Bhoopi</div>
</body>
You only have one of each div, so you don't need the loop. Just use
function toggle(id)// here is the function which gets the different ids of the div
{
var element = document.getElementById(id);
if(element.style.display == "none")
{
element.style.display = "block";
}
else
{
element.style.display = "none"
}
}
document.getElementById returns a single object and not an array.
If you want to get both the divs, I suggest using a class to get them.
If you wanted to only show one at a time, for example if you were building a tabs, then you could use this code to hide all the other divs first, then show only the one you want to toggle. Otherwise, if you're happy to toggle them, you can use the code posted by the others.
JS Fiddle demo: http://jsfiddle.net/ecs77e9a/
HTML
<p onclick="toggle('div1');">Sentence1</p>
<p onclick="toggle('div2');">Sentence2</p>
<div id="content">
<div id="div1" name="Name 1" style="display:none; width:400px; height:300px; border:1px solid black; background-color:yellow;" id="div1">Barun Ghatak</div>
<div id="div2" style="display:none; width:400px; height:300px; border:1px solid black; background-color:black;" id="div2">Bhoopi</div>
</div>
JS
function toggle(id)
{
//Hide all other divs first
var divs = document.getElementById('content').childNodes;
for ( var i = 0; i < divs.length; i++ ) {
if ( divs[i].nodeName == "DIV" ) {
var div = document.getElementById(divs[i].id);
div.style.display = "none";
}
}
//Show the one that's being requested
var element = document.getElementById(id);
element.style.display = "block";
}

How to exclude the container from this javascript code?

This question relates to the last i asked on this site - Using javascript to "link" from html background image?.
I received a good answer which worked, however the link for the background image is also applied to the container. How would I go about ensuring that only clicking on the background image (of the id body) and not the container links to whatever website?
I hope I have been clear enough. Many thanks in advance.
The html:
<html>
<head>
<link href = "style1.css" rel = "stylesheet" type = "text/css">
</head>
<div id = "header">
Header
</div>
<body>
<div id = "body">
<div class = "container">
</div>
</div>
</body>
</html>
<script>
document.getElementById('body').onclick = function() {
window.location = 'http://www.google.com/';
}
</script>
The CSS code:
#header{
width:100%;
height:50px;
background-color:black;
}
#body {
width:100%;
height:2000px;
background-image:url('uploads/1.jpg');
cursor:pointer;
}
.container{
width: 1000px;
margin-right: auto;
margin-left: auto;
height: 1000px;
background-color:white;
}
In your click handler, you can check the element that was clicked on, if it's not body (meaning it's a child), then don't do anything.
document.getElementById('body').onclick = function(e) {
// e.target is what you clicked on
// this is always what the event was bound to
if(e.target === this){
window.location = 'http://www.google.com/';
}
}
Try add this code in your script:
document.querySelector('#body .container').onclick = function() {return false; }
To have:
<script>
document.getElementById('body').onclick = function() {
window.location = 'http://www.google.com/';
}
document.querySelector('#body .container').onclick = function() {return false; }
</script>
You could also do a return false
document.getElementById('body').onclick = function(e)
{
if(!(e.target===this))
return false;
console.log("click");
//window.location = 'http://www.google.com/';
}

Elements "jerk" back to right when I change their order

I am trying to create an infinite scroll of elements, and haven't found a plugin that was sufficiently lightweight/capable enough, so I'm trying to create my own.
So far it is working swimmingly, except for a slight jerkiness in the animation when I remove the first element and append it to the end of the parent. The example I've tossed up also has an issue where the elements lose their padding, for some reason, but that is not occurring in my actual code...
Fiddle:
http://jsfiddle.net/WtFWy/
Using the sample markup:
<section id="photos" class="bg-transparent-black">
<div class="red"></div>
<div class="blue"></div>
<div class="green"></div>
</section>​
#photos{
position:absolute;
bottom:1em;
width:100px;
height:225px;
padding:3px;
overflow:hidden;
white-space:nowrap;
}
#photos div{
height:100%;
width:50px;
padding:3px;
display:inline-block;
position:relative;
border:1px solid black;
}
.red{background:red;}
.blue{background:blue;}
.green{background:green;}
​
And JavaScript:
scrollImages = function(){
var photoArea = $('#photos');
var children = photoArea.children();
var firstChild = $(children[0])
var firstOffset=firstChild.offset();
if(document.elementLeft === false){
document.elementLeft = firstOffset.left;
}
if(document.elementWidth === false){
document.elementWidth=firstChild.outerWidth(true);
}
if(firstOffset.left < 0 && (Math.abs(firstOffset.left) > Math.abs(document.elementWidth))){
photoArea.append(firstChild);
firstChild.css('margin-left', 0 + 'px')
children = photoArea.children();
firstChild = $(children[0])
firstOffset=firstChild.offset();
document.elementLeft = firstOffset.left;
document.elementWidth=firstChild.outerWidth(true);
}else{
}
document.elementLeft -= 1;
firstChild.css('margin-left', document.elementLeft + 'px');
t = setTimeout(scrollImages, 100);
}
document.elementLeft = false;
document.elementWidth = false;
var t = setTimeout(scrollImages, 500);
$('#photos').mouseover(function(){clearTimeout(t)});
$('#photos').mouseout(function(){scrollImages()})
});
​
If you remove the padding: 3px from #photos the animation works properly.

Categories