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().
Related
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 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;
});
});
I'm getting back to learning JavaScript and wondering why this isn't working. No errors are showing in the console.
I made it work with dry coding it but now I'm trying to not use dry and it's not working.
let simonLi = document.querySelector('#simon');
let simonPic = document.querySelector('#simon-pic');
let bruceLi = document.querySelector('#bruce');
let brucePic = document.querySelector('#bruce-pic');
let benLi = document.querySelector('#ben');
let benPic = document.querySelector('#ben-pic');
let pictureChange = pic => {
if (pic.className === "hide") {
pic.classList.remove("hide");
} else {
pic.classList.add("hide");
}
};
simonLi.addEventListener('click', pictureChange(simonPic));
bruceLi.addEventListener('click', pictureChange(brucePic));
benLi.addEventListener('click', pictureChange(benPic));
No error messages and it's suppose to hide and show the image whenever the li is click.
You are attaching the result of the function pictureChange (which happens to be undefined)to the click event, instead of attaching a function.
You can try replacing
let pictureChange = pic => {
if (pic.classList.contains("hide")) {
pic.classList.remove("hide");
} else {
pic.classList.add("hide");
}
};
With
let pictureChange = pic => {
return function() {
if (pic.className === "hide") {
pic.classList.remove("hide");
} else {
pic.classList.add("hide");
}
}
};
While your at it, consider using pic.classList.toggle() instead.
This might work better:
let simonLi = document.querySelector('#simon');
let simonPic = document.querySelector('#simon-pic');
let bruceLi = document.querySelector('#bruce');
let brucePic = document.querySelector('#bruce-pic');
let benLi = document.querySelector('#ben');
let benPic = document.querySelector('#ben-pic');
function pictureChange(pic) {
pic.classList.toggle('hide')
}
simonLi.addEventListener('click', function(e) {
pictureChange(simonPic)
});
bruceLi.addEventListener('click', function(e) {
pictureChange(brucePic)
});
benLi.addEventListener('click', function(e) {
pictureChange(benPic)
});
.hide {
color: red;
}
<div id="simon"><span id="simon-pic">Simon</span></div>
<br />
<div id="bruce"><span id="bruce-pic">Bruce</span></div>
<br />
<div id="ben"><span id="ben-pic">Ben</span></div>
This shortens your code sigificantly:
for (const li of document.querySelectorAll('li')) {
li.addEventListener('click', (event) => {
if (event.target.matches('li[id]')) {
document.getElementById(`${event.target.id}-pic`).classList.toggle('hide');
}
})
}
This assumes all your li work the way you've shown. Otherwise, give all those li a common CSS class, e.g. class="li-with-pic" and adjust the querySelector accordingly:
for (const li of document.querySelectorAll('.li-with-pic')) {
li.addEventListener('click', (event) => {
if (event.target.matches('.li-with-pic')) {
document.getElementById(`${event.target.id}-pic`).classList.toggle('hide');
}
})
}
Question
How do I create a custom javascript dot function that does the same thing as the following code in jQuery:
$(document).on("click touchstart", ".someClass", function() {
// code goes here
});
I'd like to declare it like this:
$(".someClass").onClick(function () {
// code goes here
});
It needs to:
work with dynamically created elements (hence jQuery .on() instead of .click())
work with computer clicks as well as touch taps
ideally, but not mandatorily, it would be written in pure javascript (no jQuery in the prototype itself, but can still be accessed through jQuery)
What I've Tried
Object.prototype.onClick = function() {
$(document).on('click touchstart', this, arguments);
};
But I receive the following error in chrome console:
Uncaught TypeError: ((n.event.special[g.origType] || {}).handle || g.handler).apply is not a function
I think I'm declaring it incorrectly, and perhaps that this is also not the correct way to access events in javascript.
You are looking for Event Delegation this allows you too dynamically add elements and still have them respond to click.
const getDiv = (name) => {
if (Array.isArray(name) === false) name = [name];
let div2 = document.createElement('div');
name.forEach(n => div2.classList.add(n));
div2.innerText = `${name}`;
return div2;
};
const makeDivs = () => {
const div = document.querySelector('#main');
const {
dataset: {
count: x
}
} = div;
for (let i = 0; i < x; i++) {
div.appendChild(getDiv('div2'));
div.appendChild(getDiv('div3'));
div.appendChild(getDiv(['div2', 'div3']));
}
};
document.addEventListener('click', ({
target
}) => {
if (target.matches('.div2')) console.log('You clicked a DIV2');
if (target.matches('.div3')) console.log('You clicked a DIV3 !!')
if (target.matches('button')) makeDivs();
});
div {
border: 1px solid red;
margin: 5px;
}
<div id='main' data-count="10">
</div>
<button>Click Me!!!</button>
Wrapping this up into a custom function.
NOTE: I changed selector param to be an array, this allows you to pass complex selectors e.g. div.myClass li:hover
const getDiv = (name) => {
if (Array.isArray(name) === false) name = [name];
let div2 = document.createElement('div');
name.forEach(n => div2.classList.add(n));
div2.innerText = `${name}`;
return div2;
};
const makeDivs = () => {
const div = document.querySelector('#main');
const {
dataset: {
count: x
}
} = div;
for (let i = 0; i < x; i++) {
div.appendChild(getDiv('div2'));
div.appendChild(getDiv('div3'));
div.appendChild(getDiv(['div2', 'div3']));
}
};
document.on = (eventType, selector, callback) => {
const events = eventType.split(' ');
const selectors = (Array.isArray(selector)) ? selector : [selector];
events.forEach(event => { document.addEventListener(event, (e) => {
if(selectors.some(s => e.target.matches(s))) callback(e);
});
});
};
// Convenience method.
document.onClick = (selector, callback) => document.on('click', selector, callback);
// Simple
document.on('click', 'button', () => makeDivs());
document.on('click', '.div2', ({target}) => console.log('You clicked a DIV2'));
// Multi Event
document.on('click touchstart', '.div3', () => console.log('You clicked a DIV3'));
// Multi selectors
document.on('click', ['.div2', '.div3'], ({target}) => console.log('You clicked. !!'));
document.onClick('div', ({target}) => console.log('A Click Event!!'));
div {
border: 1px solid red;
margin: 5px;
}
<div id='main' data-count="10">
</div>
<button>Click Me!!!</button>
So you want a .onClick jQuery method
jQuery.fn.extend({
onClick: function(sel, cb) {
return this.on('touchstart click', sel, function(evt) {
evt.preventDefault();
if (cb && typeof cb === 'function') cb.apply(this, arguments);
})
}
});
$(document).onClick('.someClass', function(ev) {
console.log(ev.type)
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<p class="someClass">CLICK ME</p>
Or this simpler variant:
$.fn.onClick = function(sel, cb) {
return this.on('touchstart click', sel, cb);
};
$(document).onClick('.someClass', function(ev) {
ev.preventDefault();
console.log(ev.type)
});
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');
}
});
});