How to close a dropdown menu when another one is opening - javascript

I can't figure out how to close one submenu when another one is open. I'm not sure if html is needed here, so I'm just attaching JS code here:
const burgerBtn = document.querySelector(".header__burger"),
menu = document.querySelector(".menu"),
body = document.querySelector(".body"),
filter = document.querySelector(".filter"),
blockFilter = document.querySelectorAll(".block-filter"),
dropdown = document.querySelectorAll(".block-filter__dropdown");
if (filter) {
blockFilter.forEach(item => {
item.addEventListener("click", event => {
item.querySelector(".block-filter__dropdown").classList.toggle("block-filter__dropdown_state_active");
item.querySelector(".block-filter__icon").classList.toggle("block-filter__icon_state_active");
if (event.target.classList.contains("block-filter__item")) {
item.querySelector(".block-filter__value").textContent = event.target.textContent;
}
})
})
}
<div class="filter hero__filter">
<form class="filter__form">
<div class="filter__block block-filter">
<div class="block-filter__button">
<div class="block-filter__header">
<span class="block-filter__type">Purpose</span>
<div class="block-filter__icon"></div>
</div>
<span class="block-filter__value">Buy</span>
</div>
<div class="block-filter__dropdown">
<span class="block-filter__item">Buy</span>
<span class="block-filter__item">Sell</span>
</div>
</div>

Sure, just remove the class from the active one first:
item.addEventListener("click", (event) => {
// get active, and if it exists, remove active
document.querySelector(".block-filter__dropdown_state_active")?.classList.remove("block-filter__dropdown_state_active");
item.querySelector(".block-filter__dropdown").classList.toggle(
"block-filter__dropdown_state_active"
);
item.querySelector(".block-filter__icon").classList.toggle(
"block-filter__icon_state_active"
);
if (event.target.classList.contains("block-filter__item")) {
item.querySelector(".block-filter__value").textContent =
event.target.textContent;
}
});
We use ?. here to prevent us from going further (and causing an error) if there is no active dropdown already.

What you need to do is look for a currently active item first and "de-activate" them. You should also check that the currently active item is not the clicked item as you already have logic defined for that.
I've expanded on your snippet to create a solution.
NOTE: It might be useful creating a separate function/s for handling to "activate" and "de-activate" code where you pass in a .block-filter element.
const burgerBtn = document.querySelector(".header__burger"),
menu = document.querySelector(".menu"),
body = document.querySelector(".body"),
filter = document.querySelector(".filter"),
blockFilter = document.querySelectorAll(".block-filter"),
dropdown = document.querySelectorAll(".block-filter__dropdown");
if (filter) {
blockFilter.forEach(item => {
item.addEventListener("click", event => {
const active_dropdown = document.querySelector(".block-filter__dropdown_state_active");
if(active_dropdown !== null){
// get parent until we find ".block-filter"
const active_item = active_dropdown.closest(".block-filter");
// check it's not the current item
if(active_item !== null && active_item !== item){
// apply same logic as below to remove active state
active_item.querySelector(".block-filter__dropdown").classList.remove("block-filter__dropdown_state_active");
active_item.querySelector(".block-filter__icon").classList.remove("block-filter__icon_state_active");
}
}
// your original logic
item.querySelector(".block-filter__dropdown").classList.toggle("block-filter__dropdown_state_active");
item.querySelector(".block-filter__icon").classList.toggle("block-filter__icon_state_active");
if (event.target.classList.contains("block-filter__item")) {
item.querySelector(".block-filter__value").textContent = event.target.textContent;
}
})
})
}
/* base styles */
* {
box-sizing: border-box;
}
html {
font-family: sans-serif;
background-color: #f3f3f3;
}
.filter.hero__filter {
width:600px;
margin:auto;
border: 2px solid #eee;
background-color: #fff;
}
.filter__form {
display:flex;
}
.filter__block {
flex: 1;
padding: 5px;
position: relative;
}
.block-filter__header {
font-weight:600;
font-size:12px;
color: #555;
}
.block-filter__dropdown {
display:none;
position:absolute;
top:100%;
left:0;
right:0;
background-color:#fff;
border: 1px solid #ccc;
box-shadow: 0 2px 4px rgb(0 0 0 / 10%);
border-radius:4px;
}
.block-filter__dropdown_state_active {
display: block;
}
.block-filter__item {
padding: 5px 10px;
display:block;
border-bottom: 1px solid #eee;
}
.block-filter__item:last-child {
border-bottom: none;
}
<div class="filter hero__filter">
<form class="filter__form">
<div class="filter__block block-filter">
<div class="block-filter__button">
<div class="block-filter__header">
<span class="block-filter__type">Purpose</span>
<div class="block-filter__icon"></div>
</div>
<span class="block-filter__value">Buy</span>
</div>
<div class="block-filter__dropdown">
<span class="block-filter__item">Buy</span>
<span class="block-filter__item">Sell</span>
</div>
</div>
<div class="filter__block block-filter">
<div class="block-filter__button">
<div class="block-filter__header">
<span class="block-filter__type">Second</span>
<div class="block-filter__icon"></div>
</div>
<span class="block-filter__value">Alpha</span>
</div>
<div class="block-filter__dropdown">
<span class="block-filter__item">Bravo</span>
<span class="block-filter__item">Charlie</span>
<span class="block-filter__item">Delta</span>
</div>
</div>
</form>
</div>

Related

Counter Observe when I scroll

Problem
I created a counter using HTML, CSS and JS (such as satisfied customer numbers, branch numbers, etc.)
The counter is also animated but since it's down the page, I'd like to animate it only when it gets to that point on the page. How do I do with the js?
const counters = document.querySelectorAll('.value');
const speed = 400;
counters.forEach( counter => {
const animate = () => {
const value = +counter.getAttribute('akhi');
const data = +counter.innerText;
const time = value / speed;
if(data < value) {
counter.innerText = Math.ceil(data + time);
setTimeout(animate, 1);
}else{
counter.innerText = value;
}
}
animate();
});
.counter-box {
display: block;
background: #f6f6f6;
padding: 40px 20px 37px;
text-align: center
}
.counter-box p {
margin: 5px 0 0;
padding: 0;
color: #909090;
font-size: 18px;
font-weight: 500
}
.counter {
display: block;
font-size: 32px;
font-weight: 700;
color: #666;
line-height: 28px
}
.counter-box.colored {
background: #eab736;
}
.counter-box.colored p,
.counter-box.colored .counter {
color: #fff;
}
<div class="container">
<div class="row contatore">
<div class="col-md-4">
<div class="counter-box colored">
<span class="counter value" akhi="560">0</span>
<p>Countries visited</p>
</div>
</div>
<div class="col-md-4">
<div class="counter-box">
<span class="counter value" akhi="3275">0</span>
<p>Registered travellers</p>
</div>
</div>
<div class="col-md-4">
<div class="counter-box">
<span class="counter value" id="conta" akhi="289">0</span>
<p>Partners</p>
</div>
</div>
</div>
</div>
What I have tried
i had tried with
const target = document.querySelector('.counter');
observer.observe(target);
but it doesn't seem to work. Many thanks to whoever can help me.
I would recommend, as others have suggested, to use the Intersection Observer API to animate your elements once they appear in the viewport.
The idea is simple, we'll create an observer that will observe the counters to animate and we're going to configure it so that it calls the animate function once a counter is fully visible in the viewport.
You may learn more about the options that an IntersectionObserver can accept in order to customize its behavior. Meanwhile, here's a live demo that illustrates how to make the counters animate once they appear in the screen (the code below has some helpful comments):
const counters = document.querySelectorAll('.value'),
speed = 400,
/**
* create an IntersectionObserver with the specified callback that will be executed for each intersection change for every counter we have.
* You may customize the options (2nd argument) per you requirement
*/
observer = new IntersectionObserver(
entries => entries.forEach(entry => entry.isIntersecting && animate(entry.target)),
{
threshold: 1 // tells the browser that we only need to execute the callback only when an element (counter) is fully visible in the viewport
}
),
// the animate function now accepts a counter (HTML element)
animate = counter => {
const value = +counter.dataset.akhi,
data = +counter.innerText,
time = value / speed;
if (data < value) {
counter.innerText = Math.ceil(data + time);
setTimeout(() => animate(counter), 1);
} else {
counter.innerText = value;
}
};
// attach the counters to the observer
counters.forEach(c => observer.observe(c));
.counter-box {
display: block;
background: #f6f6f6;
padding: 40px 20px 37px;
text-align: center
}
.counter-box p {
margin: 5px 0 0;
padding: 0;
color: #909090;
font-size: 18px;
font-weight: 500
}
.counter {
display: block;
font-size: 32px;
font-weight: 700;
color: #666;
line-height: 28px
}
.counter-box.colored {
background: #eab736;
}
.counter-box.colored p,
.counter-box.colored .counter {
color: #fff;
}
<div class="container">
<div class="row contatore">
<div class="col-md-4">
<div class="counter-box colored">
<!-- it is recommended to use "data-*" attributes to cache data that we might use later. The "data-akhi" contains the number to animate -->
<span class="counter value" data-akhi="560">0</span>
<p>Countries visited</p>
</div>
</div>
<div class="col-md-4">
<div class="counter-box">
<span class="counter value" data-akhi="3275">0</span>
<p>Registered travellers</p>
</div>
</div>
<div class="col-md-4">
<div class="counter-box">
<span class="counter value" id="conta" data-akhi="289">0</span>
<p>Partners</p>
</div>
</div>
</div>
</div>
As others suggested, you should use Intersection Observer.
This is how I'd do:
Scrolldown the snippet in order to see the counter animating up once is on the screen.
const counters = document.querySelectorAll('.value');
const speed = 400;
const observer = new IntersectionObserver( items => {
if(items[0].isIntersecting) {
const target = items[0].target;
const animate = () => {
const value = + target.getAttribute('akhi');
const data = + target.innerText;
const time = value / speed;
if(data < value) {
target.innerText = Math.ceil(data + time);
setTimeout(animate, 1);
}else{
target.innerText = value;
}
}
animate();
observer.unobserve(target);
}
})
counters.forEach( counter => observer.observe(counter));
.counter-box {
display: block;
background: #f6f6f6;
padding: 40px 20px 37px;
text-align: center
}
.counter-box p {
margin: 5px 0 0;
padding: 0;
color: #909090;
font-size: 18px;
font-weight: 500
}
.counter {
display: block;
font-size: 32px;
font-weight: 700;
color: #666;
line-height: 28px
}
.counter-box.colored {
background: #eab736;
}
.counter-box.colored p,
.counter-box.colored .counter {
color: #fff;
}
<div style="height: 600px;">
</div>
<div class="container">
<div class="row contatore">
<div class="col-md-4">
<div class="counter-box colored">
<span class="counter value" akhi="560">0</span>
<p>Countries visited</p>
</div>
</div>
<div class="col-md-4">
<div class="counter-box">
<span class="counter value" akhi="3275">0</span>
<p>Registered travellers</p>
</div>
</div>
<div class="col-md-4">
<div class="counter-box">
<span class="counter value" id="conta" akhi="289">0</span>
<p>Partners</p>
</div>
</div>
</div>
</div>

How to deactivate button once clicked Javascript

I want to stop incrementing the number of individual likes if the photo was liked(clicked) once, and increment the total number of likes for each individual photo liked(clicked)
individual photo likes likesAfterAddition
global photo likes globalNumberOfLikes
For the moment it is increasing every time I click in both individual and global likes, I know it is not the right logic!
What logic can I use please?
//increment likes on click
function incrementLikesOnClick() {
const heartIcons = Array.from(document.getElementsByClassName('heartIcon')); // multiple heart icons
heartIcons.forEach((likeIcon, index) => likeIcon.addEventListener('click', () => {
const individualLikeBox = document.getElementsByClassName('under-photo-info');
const totalLikesDivBox = document.getElementById("likesBox");
likeIcon.classList.add('activeRed');
let likesAfterAddition = likesTable[index] + 1; // add 1 like to the individual current photo
likesTable.splice(index, 1, likesAfterAddition); // replace the old value from the Array with the new value
let sum = likesTable.reduce(function(a, b){return a + b;}); // return the sum of the array
let globalNumberOfLikes = sum; // the sum of the array
individualLikeBox[index].innerHTML = `<span'>${likesAfterAddition}</span>`
totalLikesDivBox.innerHTML = `<div class="Likes">${globalNumberOfLikes}<i class="fas fa-heart"></i></div>`
console.log(likesTable)
}))
}
instead of using for loop to set event listeners which is not efficient
you can use the feature of bubbling, so when any of dom element is clicked, the event will bubble up of its parent elements sequentially till it reaches the parent dom
//increment likes on click
function incrementLikesOnClick() {
document.addEventListener("DOMContentLoaded", function(event) {
// Your code to run since DOM is loaded and ready
document.addEventListener('click', () => {
let clicked = event.target;
//element with class heartIcon is clicked and it doesnt have activeRed class
if(clicked.classList.contains('heartIcon') && !clicked.classList.contains('activeRed')){
let productContainer = clicked.parentElement.parentElement; // till you reach the product container
const individualLikeBox = productContainer.getElementsByClassName('under-photo-info');
const totalLikesDivBox = productContainer.getElementById("likesBox");
clicked.classList.add('activeRed');
// ..whatever extra logic you want to add
}
});
});
}
If the like icon is a button (which I assume it is). U can just add a 'disabled' attribute to it as part of the event handler (for the 'click' eventListener).
'When present, it specifies that the button should be disabled.
A disabled button is unusable and un-clickable.' (source)
I would calculate the total likes based on the presence of an "active" class on each button.
const totalLikesEl = document.querySelector('#total-likes');
const updateTotalLikes = () => {
totalLikesEl.textContent = document.querySelectorAll('.like.active').length;
};
const toggleLike = (e) => {
const button = e.currentTarget.classList.toggle('active');
updateTotalLikes();
};
document.querySelectorAll('.like').forEach(likeBtn => {
likeBtn.addEventListener('click', toggleLike);
});
html, body {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
}
body {
display: flex;
flex-direction: column;
}
.cards {
display: flex;
flex-direction: row-wrap;
justify-content: center;
}
.card {
display: flex;
flex-direction: column;
padding: 0.25em;
margin: 0.5em;
border: thin solid grey;
}
.card-content {
background: grey;
width: 6em;
height: 6em;
}
.card-actions {
display: flex;
flex-direction: row;
justify-content: flex-end;
margin-top: 0.5em;
}
.like > .fa-heart {
color: grey;
}
.like.active > .fa-heart {
color: red;
}
.example-1 .card-content {
background: rgb(63,94,251);
background: radial-gradient(circle, rgba(63,94,251,1) 0%, rgba(252,70,168,1) 100%);
}
.example-2 .card-content {
background: rgb(251,63,94);
background: radial-gradient(circle, rgba(251, 63,94,1) 0%, rgba(168,252,70,1) 100%);
}
.example-3 .card-content {
background: rgb(94,63,251);
background: radial-gradient(circle, rgba(94,63,251,1) 0%, rgba(70,252,168,1) 100%);
}
.status {
text-align: center;
margin-top: 0.5em;
}
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.2/css/all.min.css" rel="stylesheet"/>
<div class="cards">
<div class="card example-1">
<div class="card-content"></div>
<div class="card-actions">
<button class="like">
Like <i class="fas fa-heart"></i>
</button>
</div>
</div>
<div class="card example-2">
<div class="card-content"></div>
<div class="card-actions">
<button class="like">
Like <i class="fas fa-heart"></i>
</button>
</div>
</div>
<div class="card example-3">
<div class="card-content"></div>
<div class="card-actions">
<button class="like">
Like <i class="fas fa-heart"></i>
</button>
</div>
</div>
</div>
<div class="status">
<strong>Total Likes:</strong>
<span id="total-likes">0</span>
</div>

How to use scrollIntoView with overflow:hidden and without scrolling the page?

How to use scrollIntoView on container which has overflow:hidden and it shouldn't scroll the page?
Here is an example: at the bottom of the page text in container <div class="cnt'> which has fixed width and overflow hidden. I want to scroll items in this container without scrolling the page.
At the top of the page two buttons to scroll to first and last element. If i click on button it will scroll text in the container and scroll to that container at the bottom of the page.
I can't use scrollLeft because overflow is hidden. :(
Does anybody know how to solve it?
const cnt = document.querySelector('.cnt')
const spanElements = cnt.querySelectorAll('span');
const lastSpan = spanElements[spanElements.length - 1]
const firstSpan = spanElements[0]
lastSpan.scrollIntoView()
const buttons = Array.from(document.querySelectorAll('button'))
const [buttonToFirstEl, buttonToLastEl] = buttons;
buttonToFirstEl.onclick = function() {
firstSpan.scrollIntoView()
}
buttonToLastEl.onclick = function() {
lastSpan.scrollIntoView()
}
.cnt {
width: 90px;
overflow: hidden;
white-space: nowrap;
padding: 8px;
border: solid #ccc 1px;
}
.filler {
width: 100%;
height: 200px;
border: dashed 2px #ccc;
margin: 20px;
}
.root {
border: solid 1px;
}
<div class="root">
<button id="button">scroll to first element</button>
<button id="button">scroll to last element</button>
<div class="filler">
</div>
<div class="filler">
</div>
<div class="filler">
</div>
<div class="filler">
</div>
<div class="cnt">
<span>
first:tessst
</span>
<span>
2:dddd
</span>
<span>
3:cccddd
</span>
<span>
4:rreeee
</span>
<span>
last:dddrreddd
</span>
</div>
</div>
https://codepen.io/geeny273/pen/bGpxYqG
if you want scrolled element in .cnt class
you need to style every span in .cnt
.cnt span{
/* relative + block for make elements solid & listed*/
position : relative;
display : block;
color: red;
font-size : 16px;
}
and in .cnt parent you need to defined height for making scroll working
.cnt {
width: 90px;
/* for example 28px */
height : 28px;
overflow: hidden;
white-space: nowrap;
padding: 4px;
border: solid black 1px;
/* overflow y or x for making scroll*/
overflow-y : scroll;
}
i hope this steps help you
To get this correctly, (i.e with support for all writing-mode, direction and block-inline options) you'd basically have to rewrite scrollIntoView from scratch, while omitting the loop that goes up the tree of scrolling-boxes. This is not a trivial task.
If you don't need a bullet proof solution though, you can simply get the nearest scrolling-box and check by how much it should be scrolled to show the start of your element:
const cnt = document.querySelector('.cnt')
const spanElements = cnt.querySelectorAll('span');
const lastSpan = spanElements[spanElements.length - 1]
const firstSpan = spanElements[0]
const buttons = Array.from(document.querySelectorAll('button'))
const [buttonToFirstEl, buttonToLastEl] = buttons;
buttonToFirstEl.onclick = function() {
scrollIntoParentView( firstSpan );
}
buttonToLastEl.onclick = function() {
scrollIntoParentView( lastSpan );
}
function scrollIntoParentView( elem, options ) {
const directions = getSimpleScrollIntoViewDirections( elem, options );
const left = directions.inline_direction || 0;
const top = directions.block_direction || 0;
const new_options = {
left,
top,
behavior: (options && options.behavior) || "auto"
};
directions.scrolling_box.scrollBy( new_options );
}
function getSimpleScrollIntoViewDirections( elem, { block = "start", inline = "start" } = {} ) {
const element_bbox = elem.getBoundingClientRect();
const scrolling_box = getNearestScrollingBox( elem );
const scrolling_box_bbox = scrolling_box.getBoundingClientRect();
const block_direction = Math.round( element_bbox.top - scrolling_box_bbox.top);
const inline_direction = Math.round( element_bbox.left - scrolling_box_bbox.left);
return {
scrolling_box,
block_direction,
inline_direction
};
}
function getNearestScrollingBox( elem ) {
if( !elem.isConnected ) { return null; }
const elem_computed_styles = getComputedStyle( elem );
// not in specs, but that seems to be what browser implementations do
if( elem_computed_styles.getPropertyValue( 'position' ) === "fixed" ) {
return null;
}
const scrolling_box = elem.parentElement;
if( !scrolling_box ) {
return elem === document.scrollingElement ? null : document.scrollingElement;
}
const computed_styles = getComputedStyle( scrolling_box );
const scroll_x = computed_styles.getPropertyValue( "overflow-x");
const scroll_y = computed_styles.getPropertyValue( "overflow-y");
if(
(scroll_x === 'clip' && scroll_y === 'clip') ||
(scrolling_box.offsetHeight <= scrolling_box.scrollingHeight) ||
(scrolling_box.offsetWidth <= scrolling_box.scrollingWidth)
) {
return getNearestScrollingBox( scrolling_box );
}
return scrolling_box;
}
.cnt {
width: 90px;
overflow: hidden;
white-space: nowrap;
padding: 8px;
border: solid #ccc 1px;
}
.filler {
width: 100%;
height: 200px;
border: dashed 2px #ccc;
margin: 20px;
}
.root {
border: solid 1px;
}
<div class="root">
<button id="button">scroll to first element</button>
<button id="button">scroll to last element</button>
<div class="filler">
</div>
<div class="filler">
</div>
<div class="filler">
</div>
<div class="filler">
</div>
<div class="cnt">
<span>
first:tessst
</span>
<span>
2:dddd
</span>
<span>
3:cccddd
</span>
<span>
4:rreeee
</span>
<span>
last:dddrreddd
</span>
</div>
</div>
scrollLeft and scrollTop work well with overflow:hidden. I check scrollLeft property on first span element instead of its container
changing scrollLeft on parent element of span, solve this issue
firstSpan.parentElement.scrollLeft = 0;
const cnt = document.querySelector('.cnt')
const spanElements = cnt.querySelectorAll('span');
const lastSpan = spanElements[spanElements.length - 1]
const firstSpan = spanElements[0]
lastSpan.scrollIntoView()
const buttons = Array.from(document.querySelectorAll('button'))
const [buttonToFirstEl, buttonToLastEl] = buttons;
buttonToFirstEl.onclick = function() {
firstSpan.parentElement.scrollLeft = 0;
}
buttonToLastEl.onclick = function() {
const cnt = lastSpan.parentElement;
cnt.scrollLeft = cnt.scrollWidth;
}
.cnt {
width: 90px;
overflow: hidden;
white-space: nowrap;
padding: 8px;
border: solid #ccc 1px;
}
.filler {
width: 100%;
height: 200px;
border: dashed 2px #ccc;
margin: 20px;
}
.root {
border: solid 1px;
}
<div class="root">
<button id="button">scroll to first element</button>
<button id="button">scroll to last element</button>
<div class="filler">
</div>
<div class="filler">
</div>
<div class="filler">
</div>
<div class="filler">
</div>
<div class="cnt">
<span>
first:tessst
</span>
<span>
2:dddd
</span>
<span>
3:cccddd
</span>
<span>
4:rreeee
</span>
<span>
last:dddrreddd
</span>
</div>
</div>

Why is this code using .parentNode not working?

Here is the method I’m trying to make. Basically what it’s supposed to do is, when an <input> with the type of button is clicked, it makes the next <div> (in this case hard-coded) go from display: none to display: block. However it’s not working.
matchDivs() {
let showInputs = document.querySelectorAll('input')
const inputs = Array.from(showInputs)
inputs.map(input => {
if (input.parentNode.getAttribute('class') === 'first-employee') {
document.querySelector('.second-employee').setAttribute('style', 'display: block')
}
return input
})
}
When you use if (node.getAttribute('class') === 'first-employee') this is return:
class='first-employee' // true
class='employee first-employee' // false
You must use:
if (node.classList.contains("first-employee")):
class='first-employee' // true
class='employee first-employee' // true
If I have understand your question properly that on button click you want to show/hide DIV tag next to it, which is inside common parent div for both, button and 'second-employee'.
I think below will be helpful.
// For single Div show/hide on button click
let btnMatchDiv = document.querySelector('#btnMatchDivs');
btnMatchDiv.addEventListener('click', matchDivs, true);
function matchDivs() {
let showInputs = document.querySelectorAll('input')
const inputs = Array.from(showInputs)
inputs.map(input => {
// Method 1
// ===========================
/*if (input.parentNode.getAttribute('class') === 'first-employee') {
document.querySelector('.second-employee').setAttribute('style', 'display: block')
}*/
// Method 2 : you can use new classList method
// ===========================
if (input.parentNode.classList.contains('first-employee')) {
input.nextElementSibling.classList.toggle('hidden');
}
//return input
})
}
body {
font-family: Arial;
}
.first-employee {
display: block;
padding: 1em;
border: 1px solid green;
}
.second-employee {
padding: 1em;
border: 1px solid red;
}
#btnMatchDivs {
padding: 1em 0.5em;
border: 1px solid #777;
}
.hidden {
display: none
}
<div class="first-employee">
<h2>
First Employee
</h2>
<input type="button" id="btnMatchDivs" value="Toggle Second Employee" />
<div class="second-employee hidden">
Second Employee
</div>
</div>
Please let me know if you need further help on this.
Thanks,
Jignesh Raval
Also if you want to toggle multiple items then you can try below code.
// For multiple Div show/hide on button click
// ===================================
let showInputs = document.querySelectorAll('input')
const btnInputs = Array.from(showInputs)
// Bind click event to each button input
btnInputs.map(input => {
input.addEventListener('click', matchDivs, false);
})
function matchDivs(event) {
let buttonEle = event.currentTarget;
if (buttonEle.parentNode.classList.contains('first-employee')) {
buttonEle.nextElementSibling.classList.toggle('hidden');
}
}
body {
font-family: Arial;
}
.first-employee {
display: block;
padding: 1em;
border: 1px solid green;
margin-bottom: 1em;
}
.second-employee {
margin: 1em 0;
padding: 1em;
border: 1px solid red;
}
#btnMatchDivs {
padding: 1em 0.5em;
border: 1px solid #777;
}
.hidden {
display: none
}
<div class="first-employee">
<h2>
First Employee 1
</h2>
<input type="button" id="btnMatchDivs" value="Match Divs" />
<div class="second-employee hidden">
Second Employee 1
</div>
</div>
<div class="first-employee">
<h2>
First Employee 2
</h2>
<input type="button" id="btnMatchDivs" value="Match Divs" />
<div class="second-employee hidden">
Second Employee 2
</div>
</div>
<div class="first-employee">
<h2>
First Employee 3
</h2>
<input type="button" id="btnMatchDivs" value="Match Divs" />
<div class="second-employee hidden">
Second Employee 3
</div>
</div>
I hope this will be helpful.
Thanks,
Jignesh Raval

Can't delete div from to-do list

I'm creating a to-do list app, and in the app there is a div that wraps an input box (for the to-do item and so the user can edit the to-do) and a icon from font-awesome. When the user clicks on the icon, I want the entire div (which contains the to-do and the delete icon) to be deleted. But when tried to do that, it didn't work. Can someone help me?
Here's the JS Code
$(document).ready(() => {
$(".input input").on("keypress", check_todo);
$(".fa-trash").on("click", ".todo_container", delete_todo);
})
// delete todo
let delete_todo = (e) => {
e.target.remove();
}
// add todo
let add_todo = () => {
let todo = $(".input input").val();
$(".output").append(`
<input type="text" placeholder="Edit To-do" value="${todo}"><i class="fa fa-trash fa-lg" aria-hidden="true"></i>
`);
$(".input input").val("");
}
// check todo
let check_todo = (e) => {
if (e.keyCode == 13) {
if ($(".input input").val() == "") {
no_todo();
} else {
add_todo();
}
}
}
// no todo
let no_todo = () => {
alert("Please add a new todo");
}
See the html and a demo
You should binding to .out-put container.
$(".output").on("click",".fa-trash" , delete_todo);
http://codepen.io/Vrety/pen/WoWmaE
http://codepen.io/anon/pen/eBoXZe
In your event listener, you need to swap ".todo_container" and ".fa-trash".
$(".todo_container").on("click",".fa-trash" , delete_todo);
This statement means, when a click event occurs and bubbles up to .todo_container, check if the clicked element is .fa-trash, if so call the function.
Then change your delete function
let delete_todo = (e) => {
$(e.currentTarget).closest('.todo_container').remove()
}
This code means from the clicked icon, travel up the dom to find .todo_container, then remove it.
Good job with using the delegation in JQuery but while using it
$(".todo_container").on("click",".fa-trash" , delete_todo);
The base element $(".todo_container") needs to be static, You are deleting this also in the delete_todo() function.
Try using $(".output") instead and see if it works.
here is a working code
$(document).ready(function() {
$(".input input").on("keypress", check_todo);
//$(".fa-trash").on("click", ".todo_container", delete_todo);
$(".todo_container .fa-trash").on("click", delete_todo);
})
// delete todo
let delete_todo = function(e) {
//e.target.remove();
$(e.target).parent().remove();
}
// add todo
let add_todo = function() {
let todo = $(".input input").val();
//to do container element, the delete icon will added later
var toDoContainer = $(`
<div class="todo_container">
<input type="text" placeholder="Edit To-do" value="${todo}"></div>
`);
//create delete icon and set event listener
var elem = $('<i class="fa fa-trash fa-lg" aria-hidden="true"></i>').on("click", delete_todo).appendTo(toDoContainer);
$(".output").append(toDoContainer);
$(".input input").val("");
}
// check todo
let check_todo = (e) => {
if (e.keyCode == 13) {
if ($(".input input").val() == "") {
no_todo();
} else {
add_todo();
}
}
}
// no todo
let no_todo = () => {
alert("Please add a new todo");
}
#font-face {
font-family: Open Sans;
src: url("assets/fonts/OpenSans-Regular");
font-weight: 400
}
#font-face {
font-family: Open Sans;
src: url("assets/fonts/OpenSans-Semibold");
font-weight: 600
}
* {
margin: 0;
padding: 0;
transition: all 200ms ease-in-out;
}
*::selection {
background-color: #ffffaa;
}
.container {
width: 60%;
margin: 20px auto;
}
.header {
padding: 10px;
}
.header input {
padding: 10px;
width: 60%;
border: none;
outline: none;
font: 400 1.8em Open Sans;
}
.to-do {
padding: 10px;
text-align: center;
}
.input input {
padding: 10px;
width: 40%;
border: none;
outline: none;
font: 600 1em Open Sans;
border-bottom: 3px solid #333;
}
.output {
margin: 10px;
}
.output input {
padding: 20px;
border: none;
outline: none;
font: 600 1em Open Sans;
width: 50%;
cursor: pointer;
}
.output input:hover {
background-color: #eee;
}
.fa-trash {
padding: 20px;
cursor: pointer;
}
.fa-trash:hover {
background-color: #333;
color: #fff;
}
<head>
<title>To-do List</title>
<!-- FONTS -->
<link href="https://fonts.googleapis.com/css?family=Open+Sans:400,500" rel="stylesheet">
</head>
<body>
<div class="container">
<header class="header">
<input type="text" name="edit_name" placeholder="Edit Name">
</header>
<section class="to-do">
<div class="input">
<input type="text" name="add_todo" placeholder="Click To Add A New To-do">
</div>
<div class="output">
<div class="todo_container">
<input type="text" placeholder="Edit To-do" value="Todo #1"><i class="fa fa-trash fa-lg" aria-hidden="true"></i>
</div>
<div class="todo_container">
<input type="text" placeholder="Edit To-do" value="Todo #2"><i class="fa fa-trash fa-lg" aria-hidden="true"></i>
</div>
</div>
</section>
</div>
<!-- JQUERY -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.0/jquery.min.js"></script>
<script src="https://use.fontawesome.com/5840114410.js"></script>
</body>

Categories