How to short this statements - javascript

How to short this code and which array method i can use here. I want to add class on every click on a single element and rest of elements remove class.
const activeLink = document.querySelectorAll(".nav-item");
activeLink[0].addEventListener("click", () => {
activeLink[0].classList.add("active");
activeLink[1].classList.remove("active");
activeLink[2].classList.remove("active");
activeLink[3].classList.remove("active");
});
activeLink[1].addEventListener("click", () => {
activeLink[1].classList.add("active");
activeLink[0].classList.remove("active");
activeLink[2].classList.remove("active");
activeLink[3].classList.remove("active");
});
activeLink[2].addEventListener("click", () => {
activeLink[2].classList.add("active");
activeLink[0].classList.remove("active");
activeLink[1].classList.remove("active");
activeLink[3].classList.remove("active");
});
activeLink[3].addEventListener("click", () => {
activeLink[3].classList.add("active");
activeLink[0].classList.remove("active");
activeLink[1].classList.remove("active");
activeLink[2].classList.remove("active");
});

Rather then looping through all items per click, you can just keep track of which item is currently active and reset this one.
https://codesandbox.io/s/compassionate-northcutt-50ucp?file=/src/index.js
let activeLink = null;
document.querySelectorAll(".nav-item").forEach(navItem => {
navItem.addEventListener("click", () => {
if (activeLink) {
activeLink.classList.remove("active");
}
navItem.classList.add("active");
activeLink = navItem;
});
});

Related

Toggling not working properly - Javascript

I have this code which sould toggle between elements, if user opens the element all other elements should close and that works, but if I click on the same element multiple times nothing happens and I cant figure out why. I need some help.
Here is my code:
const heads = [...document.querySelectorAll(".head")];
const dropdowns = [...document.querySelectorAll(".head-dropdown")];
const activate_filter = document.getElementById("filter-btn");
let isActive = false;
let isFilterActive = false;
let queue = [];
if (!isFilterActive) {
dropdowns.forEach((val, i) => {
val
.querySelector(".dropdown-search")
.setAttribute(
"name",
`${heads[i].querySelector("p").textContent}-search`
);
});
}
activate_filter.addEventListener("click", e => {
dropdowns.forEach((val, i) => {
val.classList.remove("activated");
val.dataset.isActivated = false;
});
if (!isFilterActive) {
heads.forEach((val, i) => {
const p = val.children[0];
p.innerHTML += `<i class='eos-icons'>arrow_drop_down</i>`;
val.addEventListener("click", handleEventAll);
val.style.cursor = "pointer";
});
isFilterActive = true;
} else {
heads.forEach(val => {
const p = val.children[0];
const i = p.querySelector("i");
if (i) {
i.remove();
}
val.removeEventListener("click", handleEventAll);
val.style.cursor = "default";
});
isFilterActive = false;
}
});
function handleEventAll(e) {
const test = e.target.querySelector(".head-dropdown");
test.classList.add("activated");
queue.unshift(test);
if (queue.length > 3) {
queue.pop();
}
dropdowns.forEach((val, i) => {
if (!val.dataset) {
val.dataset.isActivated = false;
}
let t = eval(queue[0].dataset.isActivated);
console.log(t);
if (t) {
console.log("in true", queue[0]);
queue[0].dataset.isActivated = true;
queue[0].classList.toggle("activated");
queue.pop();
} else {
console.log("in false", queue[0].dataset);
queue[0].dataset.isActivated = false;
console.log(queue);
queue[1].classList.remove("activated");
queue.pop();
}
});
}
window.addEventListener("click", e => {});
The elements are added to the queue and the queue sorts it out if previous element which was opened is properly closed and it opens a new element which is clicked. If the same element is clicked twice the queue closes the element but does not open it again until some other element is clicked.
Here is codepen for full experience: https://codepen.io/darioKolic/pen/eYdYzdy
I rewrote the handleEventAll function to make the required functionality without using a queue. check this
What I did is save the current state (is activated class exist or not) for clicked element, and then I remove all activated class from all element then add or remove the class activated depends on the old value.

How to use event delegation to execute a function based on clicked element's id?

I've worked with event delegation in the past but for some reason I'm having trouble setting up a single event listener that executes one of three functions depending on the ID of the element clicked.
Here's the code without event delegation:
eventListeners: function() {
document.getElementById("button-1").addEventListener('click',
function() {
shuffler.reset1();
shuffler.displayCards();
});
document.getElementById("button-2").addEventListener('click', function() {
shuffler.reset2();
shuffler.displayCards();
});
document.getElementById("button-3").addEventListener('click',
function() {
shuffler.reset3();
shuffler.displayCards();
});
I've tried using something along the lines of:
document.getElementsByClass("button").addEventListener('click', function
() {
if (event.target.id == "button-1") {
shuffler.reset1();
}
});
Attach the listener to the container that contains all buttons. Then, I'd use an object indexed by id, and check if the id of the element that was clicked exists in the object - if so, run the function:
const fns = {
'button-1': () => {
shuffler.reset1();
shuffler.displayCards();
},
// ...
}
document.querySelector('< something that contains all buttons >').addEventListener('click', ({ target }) => {
const { id } = target;
if (fns[id]) {
fns[id]();
}
});
Note that in this particular case, you can use just one function by checking the last number in the ID:
document.querySelector('< something that contains all buttons >').addEventListener('click', ({ target }) => {
const { id } = target;
if (id.startsWith('button-')) {
const buttonNum = id.match(/\d+/)[0];
shuffler['reset' + buttonNum]();
shuffler.displayCards();
}
});

Click a nav item, add a class, remove class from other nav items - vanilla JS

I'm able to add a class of is-active to a nav item when it's clicked. However, i'd like to remove the class and add it to another nav item when another is clicked.
Here's what I'm currently working with:
JS:
const links = document.querySelectorAll('a');
links.forEach(function(link, index){
link.addEventListener('click', function() {
if(this.classList.contains('is-active')) {
this.classList.remove('is-active');
} else {
this.classList.add('is-active');
}
});
});
Here's a Codepen example.
This attempt adds the class, but doesn't remove it when another link is clicked.
How would I remove the class? Thanks in advance.
You just loop through the links that aren't this:
const links = document.querySelectorAll('a');
links.forEach(function(link, index){
link.addEventListener('click', function() {
if(this.classList.contains('is-active')) {
this.classList.remove('is-active');
} else {
this.classList.add('is-active');
links.forEach(l => { // ***
if (l !== this) { // ***
l.classList.remove('is-active'); // ***
} // ***
});
}
});
});
(See below for the for-of version.)
Alternately, you can do a new query of just the is-active links:
document.querySelectorAll('a').forEach(function(link, index){
link.addEventListener('click', function() {
if(this.classList.contains('is-active')) {
this.classList.remove('is-active');
} else {
document.querySelectorAll('a.is-active').forEach(activeLink => { // ***
activeLink.classList.remove('is-active'); // ***
}); // ***
this.classList.add('is-active');
}
});
});
Or if you like, since there should be only one, querySelector:
document.querySelectorAll('a').forEach(function(link, index){
link.addEventListener('click', function() {
if(this.classList.contains('is-active')) {
this.classList.remove('is-active');
} else {
const activeLink = document.querySelector('a.is-active'); // **
if (activeLink) { // **
activeLink.classList.remove('is-active'); // **
} // **
this.classList.add('is-active');
}
});
});
Side note: The NodeList from querySelectorAll doesn't have forEach in some browsers (it was added relatively recently). See this answer for how to add it if it's missing, and (on ES2015+ platforms) how to ensure it's iterable as well (as it's also meant to be).
And if you can rely on iterability, here are for-of versions of those:
for-of version of the first example:
const links = document.querySelectorAll('a');
for (const link of links) {
link.addEventListener('click', function() {
if(this.classList.contains('is-active')) {
this.classList.remove('is-active');
} else {
this.classList.add('is-active');
for (const l of links) {
if (l !== this) {
l.classList.remove('is-active');
}
}
}
});
}
for-of version of the second example:
for (const link of document.querySelectorAll('a')) {
link.addEventListener('click', function() {
if(this.classList.contains('is-active')) {
this.classList.remove('is-active');
} else {
for (const activeLink of document.querySelectorAll('a.is-active')) {
activeLink.classList.remove('is-active');
}
this.classList.add('is-active');
}
});
}
And the third:
for (const link of document.querySelectorAll('a')) {
link.addEventListener('click', function() {
if(this.classList.contains('is-active')) {
this.classList.remove('is-active');
} else {
const activeLink = document.querySelector('a.is-active'); // **
if (activeLink) { // **
activeLink.classList.remove('is-active'); // **
} // **
this.classList.add('is-active');
}
});
}
A common requirement - you can do this:
get an iterable representation of the DOM Elements using spread syntax like [...document.querySelectorAll('a')]
use forEach to loop through the links
you can use the classList.toggle function instead of having the if-else conditions
See demo below:
// get an iterable representation using the spread syntax
const elements = [...document.querySelectorAll('a')];
elements.forEach(e => e.addEventListener('click', () => {
// remove active class from all links
elements.forEach(e => e.classList.remove('is-active'));
// add active to the clicked link
e.classList.toggle('is-active');
}));
.is-active {
color: red;
}
Link 1
Link 2
Link 3
this in the function refers to the elements that is clicked. You should hide all the elements every time using forEach in each click. And then show the desired one
const links = [...document.querySelectorAll('a')];
links.forEach(function(link, index){
link.addEventListener('click', function() {
let temp = this.classList.contains('is-active')
links.forEach(x => x.classList.remove('is-active'))
if(!temp) {
this.classList.add('is-active');
}
});
});

Remove class before adding new one to li

I m building a menu bar which has current class which is highlighted when user clicks.
how do i make it so when someone clicks on li it removes from previous li and put it on new li which is clicked on ??
here is my code
li.forEach(li => {
li.addEventListener('click', () => {
li.classList.add("selected");
li.style.background = '';
li.style.paddingTop = '';
});
});
You could do something like this
li.forEach(li => {
li.addEventListener('click', () => {
removeClass();
li.classList.add("selected");
li.style.background = '';
li.style.paddingTop = '';
});
});
function removeClass () {
li.forEach(li => {
li.classList.remove("selected");
})
}
how about this
items.forEach(li => {
li.addEventListener('click', () => {
items.forEach(li => li.classList.remove("selected"));
li.classList.add("selected");
li.style.background = '';
li.style.paddingTop = '';
});
});
One simple approach is the following:
li.forEach(li => {
li.addEventListener('click', () => {
// find the parent-node of the current <li> element:
li.parentNode
// use Element.querySelector() to find the current
// <li> element with the 'selected' class:
.querySelector('li.selected')
// use classList.remove() to remove that class-name:
.classList.remove('selected');
// as before:
li.classList.add("selected");
// why is the following necessary? It *seems* to be
// undoing something that shouldn't be necessary:
li.style.background = '';
li.style.paddingTop = '';
});
});
References:
Element.classList.
Element.querySelector().

Set on click to `<li>` without iterating

I'm creating a dynamic <ul>, by creating a dynamic <li> tags list based on a template.
the <li> template looks something like that:
<script type="text/html" id="itemTemplate">
<li id="{{id}}">
<div class="name" title="{{name}}">{{name}}</div>
<div class="saveAs"></div>
<div class="copy"></div>
</li>
</script>
My goal is to make the saveAs and the copy div clickable and execute a function with the id as a parameter.
Iv'e managed to do that by this function:
function myView() {
self.itemTemplate = null;
self.myArrayOfObjects = null;
self.initItemsUl = () => {
self.itemsUl = self.mainContainer.find('.itemsUl');
self.myArrayOfObjects.forEach(self.initItemLi);
};
self.initItemLi = (item) => {
var viewObj = {
id: item.Id,
name: item.Name
};
var itemLi = $(Mustache.render(self.itemTemplate, viewObj));
self.mainContainer.append(itemLi[0]);
self.setupItemOnClick(itemLi, item.Id);
};
self.setupItemOnClick = (itemLi, id) => {
itemLi.find('.saveAs').on('click', null, () => {
//do_something(id)
});
itemLi.find('.copy').on('click', null, () => {
//do_something(id)
});
};
return {
init: (myArrayOfObjects) => {
self.myArrayOfObjects = myArrayOfObjects;
self.itemTemplate = $('#itemTemplate').html();
Mustache.parse(self.itemTemplate);
self.initItemsUl();
}
};
}
Pay attention that the function setupItemOnClick is being called every time i'm rendering the li template, my question is how to make this function to be called only once?
Use event delegation on the ul, rather than handlers on the individual .saveAs and .copy elements:
$("selector-for-your-ul")
.on("click", ".saveAs", e => {
// Handle click here, `e.currentTarget` is the `.saveAs` that was clicked
})
.on("click", ".copy", e => {
// Handle click here, `e.currentTarget` is the `.copy` that was clicked
});
Re your comment:
...how do i get the id of the parent li out of the e object?
By using $(e.currentTarget).closest("li").attr("id").

Categories

Resources