How to get the child of a element with event.target - javascript

I'm trying to get the child elements of a div to toggle a class 'active'
JS
const dots = document.querySelectorAll('[data-dots]');
dots.forEach(dot => dot.addEventListener('click', handleClick));
function handleClick(e) {
e.target.getElementsByClassName('tb-drop').classList.toggle('active');
console.log(e.target.getElementsByClassName('tb-drop'))
}
HTML
<div class="dots" data-dots>
<i class="fas fa-ellipsis-v dots"></i>
<div class="tb-drop">
<i class="far fa-edit icon-grid"></i>
<i class="fas fa-link icon-grid"></i>
</div>
</div>
So i'm selecting all the divs with the data-dots attribute and then select the child of that div and add the class active. I tried with e.target.children but didnt work.
Thanks, I'm just trying to learn :)

In order to identify the first child, the easiest option is simply to use Element.querySelector() in place of Element.getElementsByClassName():
const dots = document.querySelectorAll('[data-dots]');
dots.forEach(dot => dot.addEventListener('click', handleClick));
function handleClick(e) {
// Element.querySelector() returns the first - if any -
// element matching the supplied CSS selector (element,
// elements):
e.target.querySelector('.tb-drop').classList.add('active');
}
The problem is, of course, that if no matching element is found by Element.querySelector() then it returns null; which is where your script will raise an error. So, with that in mind, it makes sense to check that the element exists before you try to modify it:
const dots = document.querySelectorAll('[data-dots]');
dots.forEach(dot => dot.addEventListener('click', handleClick));
function handleClick(e) {
let el = e.target.querySelector('.tb-drop');
if (el) {
el.classList.add('active');
}
}
It's also worth noting that EventTarget.addEventListener() passes the this element into the function, so rather than using:
e.target.querySelector(...)
it's entirely possible to simply write:
this.querySelector(...)
Unless, of course, handleClick() is rewritten as an Arrow function.
Demo:
const dots = document.querySelectorAll('[data-dots]');
dots.forEach(dot => dot.addEventListener('click', handleClick));
function handleClick(e) {
let el = e.target.querySelector('.tb-drop');
if (el) {
el.classList.add('active');
}
}
div {
display: block;
border: 2px solid #000;
padding: 0.5em;
}
i {
display: inline-block;
height: 1em;
}
::before {
content: attr(class);
}
.active {
color: limegreen;
}
<div class="dots" data-dots>
<i class="fas fa-ellipsis-v dots"></i>
<div class="tb-drop">
<i class="far fa-edit icon-grid"></i>
<i class="fas fa-link icon-grid"></i>
</div>
</div>
Or, if you wish to toggle the 'active' class you could, instead, use toggle() in place of add:
const dots = document.querySelectorAll('[data-dots]');
dots.forEach(dot => dot.addEventListener('click', handleClick));
function handleClick(e) {
let el = e.target.querySelector('.tb-drop');
if (el) {
el.classList.toggle('active');
}
}
div {
display: block;
border: 2px solid #000;
padding: 0.5em;
}
i {
display: inline-block;
height: 1em;
}
::before {
content: attr(class);
}
.active {
color: limegreen;
}
<div class="dots" data-dots>
<i class="fas fa-ellipsis-v dots"></i>
<div class="tb-drop">
<i class="far fa-edit icon-grid"></i>
<i class="fas fa-link icon-grid"></i>
</div>
</div>
References:
Element.querySelector

e.target already is the clicked child of the element that you installed the listener on. You probably want to use e.currentTarget or this instead.
Then you can go using .getElementsByClassName(), .querySelector[All]() or .children from there.

You can also try this code.
var dots = document.querySelectorAll('[data-dots]');
for (var i = 0; i < dots.length; i++) {
dots[i].addEventListener('click', function () {
handleClick(this);
}, false);
}
function handleClick(object) {
var container = object.getElementsByClassName('tb-drop')[0];
if (container != undefined) {
if (container.classList.contains('active')) {
container.classList.remove('active')
}else{
container.classList.add('active')
}
}
}

Related

Hide/Show div and then hide all div

I am trying to make a "meet the staff" section that has hidden bios that display on click. Right now the div displays as it should, but only disappears when the original button is clicked again. I am needing some additional javascript to hide any opened divs when a different (or same) button is clicked. I don't know enough javascript to know what to try in order to make this happen. Thanks in advance!
HTML
<div id="lastname" class="toggle-div" style="display: none;">
<div><p>bio</p>
</div>
</div>
<button class="bio-button" onclick="myBiof()">Click for Bio</button>
Javascript
<script>
function myBiof() {
var y = document.getElementById("lastname");
if (y.style.display === "block") {
y.style.display = "none";
} else {
y.style.display = "block";
}
}
</script>
You will need to add some attributes to your HTML to keep track of which item is active, what item a button controls and which ones should be hidden from screen readers. aria-controls aria-expanded and aria-hidden do just that. Once a button is clicked... if it is currently open, just close it (remove active) and toggle the appropriate attributes. If it is not open, close all of them (remove active), open the one you clicked on (add active) and toggle the appropriate attributes. Here is a simple example:
const buttons = document.querySelectorAll("button");
const people = document.querySelectorAll(".person");
const handleClick = (event) => {
const clickedBtn = event.target;
if (clickedBtn.getAttribute("aria-expanded") === "true") {
let personId = clickedBtn.getAttribute("aria-controls");
let person = document.getElementById(personId);
person.classList.remove("active");
person.setAttribute("aria-hidden", "true");
clickedBtn.setAttribute("aria-expanded", "false");
} else if (clickedBtn.getAttribute("aria-expanded") === "false") {
people.forEach(person => {
person.classList.remove("active")
person.setAttribute("aria-hidden", "true");
});
buttons.forEach(button => button.setAttribute("aria-expanded", "false"));
let personId = clickedBtn.getAttribute("aria-controls");
let person = document.getElementById(personId);
person.classList.add("active");
person.setAttribute("aria-hidden", "false");
clickedBtn.setAttribute("aria-expanded", "true");
}
}
buttons.forEach(button => button.addEventListener("click", handleClick));
button {
display: block;
background: transparent;
border: none;
border-bottom: 1px solid #000;
width: 100%;
height: 2rem;
}
.person-container {
width: 400px;
margin: 0 auto;
}
.person {
display: none;
border-left: 1px solid #000;
border-right: 1px solid #000;
border-bottom: 1px solid #000;
padding: 1rem;
}
.person h2 {
margin-top: 0px;
}
.person p {
margin-bottom: 0px;
}
.active {
display: block;
}
<div class="person-container">
<button aria-controls="person-one" aria-expanded="false">Show Person One</button>
<div id="person-one" aria-hidden="true" class="person">
<h2>Name One</h2>
<p>Person One Bio</p>
</div>
<button aria-controls="person-two" aria-expanded="false">Show Person Two</button>
<div id="person-two" aria-hidden="true" class="person">
<h2>Name Two</h2>
<p>Person Two Bio</p>
</div>
<button aria-controls="person-three" aria-expanded="false">Show Person Three</button>
<div id="person-three" aria-hidden="true" class="person">
<h2>Name Three</h2>
<p>Person Three Bio</p>
</div>
</div>
/*
Function to add all the events to the buttons.
Checking if divs are hidden or not with [data-hidden] attribute.
This HMTML attributes can be named however you want but starting
with data-
Note that this code will only work if every button
is placed in the HTML after the bio div
*/
function addEventsAndListenToThem() {
const buttons = document.querySelectorAll('.bio-button')
buttons.forEach(btn => {
btn.onclick = (e) => {
const target = e.target.previousElementSibling
// If element is hided, show it changing
// attribute data-hidden value to false
target.getAttribute('data-hidden') === 'true' ?
target.setAttribute('data-hidden', 'false') :
target.setAttribute('data-hidden', 'true')
}
})
const hide_or_show_all = document.querySelector('.bio-button-all')
// Var to check wether .bio-button-all
// has been pressed or not
var showing = false
hide_or_show_all.onclick = () => {
// Get al divs with data-hidden property
const all_bios = document.querySelectorAll('div[data-hidden]')
showing === false ? (
() => {
// Show all divs
all_bios.forEach(bio => bio.setAttribute('data-hidden', 'false'))
showing = true
}
)() :
(
// Hide all divs
() => {
all_bios.forEach(bio => bio.setAttribute('data-hidden', 'true'))
showing = false
}
)()
}
}
addEventsAndListenToThem()
/*
Display none only to [data-hidden="true"] elements
*/
[data-hidden="true"] {
display: none;
}
.bio-button,
.bio-button-all {
display: block;
margin: 10px 0px;
}
<div id="lastname" class="toggle-div" data-hidden='true'>
<div>
<p>First bio</p>
</div>
</div>
<button class="bio-button">Click for first Bio</button>
<div id="lastname" class="toggle-div" data-hidden='true'>
<div>
<p>Second bio</p>
</div>
</div>
<button class="bio-button">Click for second Bio</button>
<div id="lastname" class="toggle-div" data-hidden='true'>
<div>
<p>Third bio</p>
</div>
</div>
<button class="bio-button">Click for third Bio</button>
<button class="bio-button-all">Show/Hide all</button>

How to Update getElementsByClassName after doing something?

if (document.querySelector(".delete-filter") !== null) {
let dltbtn = document.getElementsByClassName("delete-filter");
let contbtn = document.getElementsByClassName("filter-solid");
for (let i = 0; i < dltbtn.length; i++) {
dltbtn[i].onclick = function() {
contbtn[i].remove();
}
}
}
.filter-solid {
display: inline-block;
border: 1px solid #faa938;
border-radius: 2vw;
font-size: 13px;
padding: 5px 8px;
color: #525666;
margin: 4px 0;
}
.filter-solid button {
border: none;
background: none;
color: #525666;
}
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css" integrity="sha512-SfTiTlX6kk+qitfevl/7LibUOeJWlt9rbyDn92a1DqWOw9vWG2MFoays0sgObmWazO5BQPiFucnnEAjpAB+/Sw==" crossorigin="anonymous" referrerpolicy="no-referrer">
<div>
<span class="filter-solid"><span> فقط کالاهای تخفیف دار </span> <button class="delete-filter"><i class="fa fa-times"></i></button></span>
<span class="filter-solid"><span> از <span>200,000</span> تا <span>1,200,000</span> تومان </span> <button class="delete-filter"><i class="fa fa-times"></i></button></span>
<span class="filter-solid"><span> رنگ آبی </span> <button class="delete-filter"><i class="fa fa-times"></i></button></span>
</div>
In above code I want to remove an element after click, but the problem is when I delete elements (from left) the count of dltbtn and contbtn is not updating. I mean when I have 4 elements there is [0, 1, 2, 3] array, so when I delete first element the array should be [0, 1, 2] in order, but it will not be updated. how should I fix this?
If you use .addEventListener() instead of .onclick, and DOM traversal to find the container of the clicked button, you don't need dltbtn, contbtn or an index.
this in the event handler assigned with .addEventListener() is the clicked element -> the button.
With .closest(".filter-solid") we travel the DOM up to the first element that matches the selector ".filter-solid" (in this case .parentNode) would do the same -> the container that should be removed.
document.querySelectorAll(".delete-filter")
.forEach(function(btn) {
btn.addEventListener("click", function() {
const container = this.closest(".filter-solid");
if (container) {
container.remove();
}
});
})
.filter-solid {
display: inline-block;
border: 1px solid #faa938;
padding: 5px 8px;
margin: 4px 0;
}
<div>
<span class="filter-solid"><span> فقط کالاهای تخفیف دار </span> <button class="delete-filter"><i class="fa fa-times">X</i></button></span>
<span class="filter-solid"><span> از <span>200,000</span> تا <span>1,200,000</span> تومان </span> <button class="delete-filter"><i class="fa fa-times">X</i></button></span>
<span class="filter-solid"><span> رنگ آبی </span> <button class="delete-filter"><i class="fa fa-times">X</i></button></span>
</div>
The main issue is that getElementsByClassName() returns a live collection. To quote the MDN page:
Warning: This is a live HTMLCollection. Changes in the DOM will reflect in the array as the changes occur. If an element selected by this array no longer qualifies for the selector, it will automatically be removed. Be aware of this for iteration purposes.
Meaning that contbtn[i].remove() will implicitly remove the current element from the array (because it is removed from the DOM), shifting all elements that come after it.
An easy change would be swapping from getElementsByClassName() to querySelectorAll() which does not not return a live collection.
let dltbtn = document.querySelectorAll(".delete-filter");
let contbtn = document.querySelectorAll(".filter-solid");
With this change elements in the resulting collection won't shift when contbtn[i].remove() is called.
Another good solution in this scenario would be to use event delegation. This means adding an event listener to the wrapping <div> which checks if the .delete-filter is clicked and removes the associated .filter-solid.
This also simplifies adding new elements, since you no longer need to add event listeners on those new elements.
const containers = document.querySelectorAll(".filter-solid-container");
for (const container of containers) {
container.addEventListener("click", function ({ target }) {
// only search within the container by addeding the container selector
const dlt = target.closest(".filter-solid-container, .delete-filter");
// return if not clicked on .delete-filter
// (`dlt` will be set to the container)
if (dlt == container) return;
// I assume that .filter-solid is always present around a .delete-filter
// find and remove it
dlt.closest(".filter-solid").remove();
});
}
// demo: adding a new filter without fumbling with event handlers
const addFilter = document.querySelector("#add-filter");
addFilter.addEventListener("click", function () {
containers[0].innerHTML += `
<span class="filter-solid">
<span>test</span>
<button class="delete-filter"><i class="fa fa-times"></i></button>
</span>
`;
});
.filter-solid {
display: inline-block;
border: 1px solid #faa938;
border-radius: 2vw;
font-size: 13px;
padding: 5px 8px;
color: #525666;
margin: 4px 0;
}
.filter-solid button {
border: none;
background: none;
color: #525666;
}
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css" integrity="sha512-SfTiTlX6kk+qitfevl/7LibUOeJWlt9rbyDn92a1DqWOw9vWG2MFoays0sgObmWazO5BQPiFucnnEAjpAB+/Sw==" crossorigin="anonymous" referrerpolicy="no-referrer">
<div class="filter-solid-container">
<span class="filter-solid"><span> فقط کالاهای تخفیف دار </span> <button class="delete-filter"><i class="fa fa-times"></i></button></span>
<span class="filter-solid"><span> از <span>200,000</span> تا <span>1,200,000</span> تومان </span> <button class="delete-filter"><i class="fa fa-times"></i></button></span>
<span class="filter-solid"><span> رنگ آبی </span> <button class="delete-filter"><i class="fa fa-times"></i></button></span>
</div>
<button id="add-filter" type="button">add filter</button>

How to toggle between two classes on li elements which have arrows? (just JavaScript and CSS) [duplicate]

This question already has answers here:
How do I toggle an element's class in pure JavaScript?
(13 answers)
Closed 2 years ago.
I want to toggle between up and down arrow classes, if li is not opened then arrow down should be visible, and if it's li open a list, then arrow should be up. I want this to works on each li element which have possibility to be opened.
let ul = document.getElementById("nav");
let liArray = ul.getElementsByTagName("li");
liArray.addEventListener('click', function() {
for (let i = 0; i < liArray.length; i++) {
liArray[i].classList.add('fa-chevron-up');
}
})
.fa-chevron-down {
display: block;
float: right;
font-size: 20px;
text-align: right;
}
.fa-chevron-up {
display: block;
float: right;
font-size: 20px;
text-align: right;
}
<ul id="nav">
<li>BEST BONUSES</li>
<li>ONLINE CASINOS<i class="fas fa-chevron-down" id="hide"></i><i id="show" class="fas fa-chevron-up"></i>
<ul>
<li>Casinos Listing</li>
<li>Casinos by Province<i class="fas fa-chevron-down"></i><i class="fas fa-chevron-up"></i>
<ul>
<li>Western Cape
<li>Gauteng
</ul>
</li>
<li>ZAR Online Casinos</li>
<li>Reviews</li>
<li>Software<i class="fas fa-chevron-down"></i></li>
<li>Land-Based Casino Resorts</li>
</ul>
</li>
<li>ZAR BANKING<i class="fas fa-chevron-down"></i><i class="fas fa-chevron-up"></i></li>
<li>GAMES<i class="fas fa-chevron-down"></i><i class="fas fa-chevron-up"></i></li>
<li>BEST BONUSES</li>
<li>NEWS</li>
</ul>
You can check if its class list contains the fa-chevron-up when yes then remove it and add the down class. The same vice versa if it contains fa-chevron-down
Also Note: you can not call eventlistener on a collection
You should loop through the elements and assign to each element an eventlistener.
let ul = document.getElementById("nav");
let liArray = [...ul.getElementsByTagName("li")];
liArray.forEach((x) => {
x.addEventListener('click', function() {
if (this.classList.contains("fa-chevron-up")) {
this.classList.remove("fa-chevron-up");
this.classList.add("fa-chevron-down")
} else if (this.classList.contains("fa-chevron-down")) {
this.classList.remove("fa-chevron-down");
this.classList.add("fa-chevron-up")
}
})
})
You're trying to add the event listeners to the entire array, as opposed to the individual elements, try something like
let ul = document.getElementById("nav");
let liArray = ul.getElementsByTagName("li");
liArray.forEach(item => item.addEventListener('click' ,function
(e){
e.target.classList.add('fa-chevron-up');
}))

closing dropdown by clicking outside

I want to be able to close my dropdown menu not only by clicking the x, but by clicking outside of it aswell. My js code doesnt seem to work. The Javascript is copied from a template i had left somewhere but im actually not able to fix it in order for it to work.
window.onclick = function closeMenu() {
if(document.getElementById("dropdown-content").style.left != "-300px") {
var dropdown = document.getElementById("dropdown-content");
var i;
for(i = 0; i < dropdown.length; i++) {
var openDropdown = dropdown[i];
if(openDropdown.style.left != ('-300px')) {
openDropdown.style.left = ('-300px');
}
}
}
}
.dropdown-content {
position: fixed;
background-color: rgb(255, 255, 255);
width: 300px;
height: 100%;
/*box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2);*/
z-index: 600;
transition: 0.3s;
}
#dropdown-content {
left: -300px;
z-index: 600;
}
<div class="dropdown-container">
<div class="dropdown-content" id="dropdown-content">
<div class="menubutton" onclick="menu(this)">
<div class="bar1"></div>
<div class="bar2"></div>
<div class="bar3"></div>
</div>
<div class="menulist">
Angebot des Tages
Alle Angebote
Technik
Hardware
Mode
Automobil
</div>
</div>
</div>
const x = document.querySelector('.x');
const ul = document.querySelector('ul');
x.addEventListener('click', () => {
ul.classList.toggle('show');
});
document.addEventListener('click', ({
target
}) => {
if (target.matches('.x') === false) {
ul.classList.remove('show');
}
});
ul {
display: none;
}
ul.show {
display: block;
}
<div class="x">X</div>
<ul>
<li>Test</li>
<li>Test</li>
<li>Test</li>
<li>Test</li>
</ul>
Here, we track the X and just use toggle(), for any other click we ensure it is not X and then just remove() our show class.
If you are using just vanilla javascript, you can add a eventListener to the document object that listens to click event and checking some condition of the dropdown to check if it's opened, if it is, then closes it, if it's not, do nothing.
Something like:
document.addEventListener('click', () => {
const dropdown = ... // grab dropdown element
if (isOpened(dropdown)) closeDropdown()
})
EDIT: Also you should check if the click happened outside your dropdown since it will also be triggered if it is clicked. For that you can use the Node API.
Leaving it as:
document.addEventListener('click', (e) => {
const dropdown = ... // grab dropdown element
if (dropdown.contains(e.target)) return; // if the clicked element is inside the dropdown or it's the dropdown itself, do nothing
if (isOpened(dropdown)) closeDropdown()
})

Avoid event.target refer to the child instead of parent

I'm trying to get data attributes of a group of links and buttons, creating a event listener as follow:
// For all major browsers, except IE 8 and earlier
if (document.addEventListener) {
document.addEventListener("click", executeOnClick);
} else if (document.attachEvent) {
// For IE 8 and earlier versions
document.attachEvent("onclick", executeOnClick);
}
This event listener, executes the next function:
function executeOnClick(e){
//////////// Only elements which has "specialClass"
if (hasClass(e.target, 'specialClass')) {
if(e.target !== undefined){
console.info(e.target.getAttribute('data-info'));
e.preventDefault();
}
}
}
But doesn't work when the link or button has other tags inside them. Example:
<a data-info="Lorem ipsum 3!" href="#" class="specialClass">
<div>Link with div inside: <br> "event.target" is "div", not "a"</div>
</a>
I don't know how to get it work when the elements has and has no children. Somebody can help me?
Codepen of my problem: http://codepen.io/tomloprod/pen/gwaVXE
NOTE: I have omitted the definition of the hasClass method because this isn't the problem. Anyway, you can see it on the codepen.
You could use a function which will recursively check the parentNode for the presence of a data-info attribute.
Here is an example.
//////////// This function works well.
function findParentWithData(elem) {
try {
if(elem.getAttribute('data-info'))
return elem;
} catch(e) {
console.log('This was an anchor without data-info attribute.')
return e
}
while(!elem.getAttribute('data-info')) {
return findParentWithData(elem.parentNode);
}
}
function hasClass(event, className) {
if (event.classList) {
return event.classList.contains(className);
}
return new RegExp('(^| )' + className + '( |$)', 'gi').test(event.className);
}
function executeOnClick(e) {
// if click came from body don't do anything
if (e.target === document.body) return;
var result = document.getElementById("result");
result.innerHTML = "";
//////////// Only elements that has "specialClass"
// find parent with data-info
var elem = findParentWithData(e.target)
if (elem instanceof Element && hasClass(elem, 'specialClass')) {
if(elem !== undefined){
result.innerHTML = "Information: " + elem.getAttribute('data-info');
e.preventDefault();
}
}
}
// For all major browsers, except IE 8 and earlier
if (document.addEventListener) {
document.addEventListener("click", executeOnClick);
} else if (document.attachEvent) {
// For IE 8 and earlier versions
document.attachEvent("onclick", executeOnClick);
}
.btn {
opacity:0.8;
border:0;
-webkit-border-radius: 28;
-moz-border-radius: 28;
border-radius: 28px;
font-family: Arial;
color: #ffffff;
font-size: 37px;
padding: 10px 20px 10px 20px;
text-decoration: none;
outline:0;
margin: 0em 0 1em 0;
display: -webkit-inline-box;
}
.btn:hover {
cursor:pointer;
opacity:1;
text-decoration: none;
}
.btn.red{
background:#e74c3c;
}
.btn.green{
background:#2ecc71;
}
<div id="result"></div>
<a data-info="Lorem ipsum!" href="#" class="btn green specialClass">Link: Working well</a>
<button data-info="Lorem ipsum 2!" class="btn green specialClass">Button: Working well too</button>
<a data-info="Lorem ipsum 3!" href="#" class="btn red specialClass">
<div>Link with div inside: <br> Doesn't work</div>
</a>
<a data-info="Lorem ipsum 4!" href="#" class="btn red specialClass">
<ul>
<li>
Link with ul inside:
</li>
<li>
Doesn't work
</li>
</ul>
</a>
Foo

Categories