Styles revert back after being changed with JS code - javascript

I am trying to set the styles of a div when a button is clicked. The changes occur but then they instantly revert back to the existing styling.
View Demo
Full Source Code
document.body.addEventListener("click", (e) => {
if (e.target.classList.contains("js-multi-btn")) {
gameBoard.setGameMode('multi');
displayController.revealBoard();
}
});
document.body.addEventListener("click", (e) => {
if (e.target.classList.contains("js-single-btn")) {
gameBoard.setGameMode('single');
displayController.revealBoard();
}
});
function revealBoard() {
var btns = document.querySelector('.js-gameplay-btns');
btns.style.display = 'none'
var board = document.getElementById('js-board')
board.style.backgroundColor = 'white';
}
Multi Player Mode
Single Player Mode

https://jsfiddle.net/mplungjan/unjsp6tm/
Change to
<a href="" data-gm="multi" class="js-btn single-btn">
<a href="" data-gm="single" class="js-btn multi-btn">
then
document.querySelector(".js-gameplay-btns").addEventListener("click", (e) => {
const tgt = e.target.closest("a");
if (tgt && tgt.classList.contains("js-btn")) {
e.preventDefault()
gameBoard.setGameMode(tgt.dataset.gm);
displayController.revealBoard();
}
})

Related

Close button popup doesn't work (JAVASCRIPT)

i'm trying to create a custom pupop in javascript, this is my first time with this.
I have a problem with the close button, the "x" target correctly the div to close, but doesn't remove the "active" class at click.
https://demomadeingenesi.it/demo-cedolino/
HTML CODE
<div class="spot spot-2">
<div class="pin"></div>
<div class="contenuto-spot flex flex-col gap-3">
<img class="chiudi-popup" src="img/chiudi.svg" />
[---CONTENT---]
</div>
</div>
</div>
JAVASCRIPT CODE
const tooltips = function () {
const spots = document.querySelectorAll(".spot");
spots.forEach((spot) => {
const contenuto = spot.querySelector(".contenuto-spot");
const pin = spot.querySelector(".pin");
spot.addEventListener("click", () => {
let curActive = document.querySelector(".spot.active");
let contActive = document.querySelector(".contenuto-spot.show");
const chiudiPopup = document.querySelector(".chiudi-popup");
spot.classList.add("active");
contenuto.classList.add("show");
if (curActive && curActive !== spot) {
curActive.classList.toggle("active");
contActive.classList.toggle("show");
}
chiudiPopup.addEventListener("click", () => {
spot.classList.remove("active");
contenuto.classList.remove("show");
});
});
});
const chiudiPopup = document.querySelector(".chiudi-popup");
chiudiPopup.addEventListener("click", () => {
spot.classList.remove("active");
contenuto.classList.remove("show");
});
What the code above does is adding an click listener, but it's inside another click listener, so all it's doing is adding an click listener on the first .chiudi-popup that removes .active and .show from the last spot element.
It's hard to see if this is correct, because you haven't given us enough to reproduce the problem, but I moved the code above outside the spot.addEventListener("click", () => { and instead of searching the whole document with const chiudiPopup = document.querySelector(".chiudi-popup"); the code nows only targets the .chuidi-popup element within the spot: const chiudiPopup = spot.querySelector(".chiudi-popup");
const tooltips = function() {
const spots = document.querySelectorAll(".spot");
spots.forEach((spot) => {
const contenuto = spot.querySelector(".contenuto-spot");
const pin = spot.querySelector(".pin");
spot.addEventListener("click", () => {
let curActive = document.querySelector(".spot.active");
let contActive = document.querySelector(".contenuto-spot.show");
spot.classList.add("active");
contenuto.classList.add("show");
if (curActive && curActive !== spot) {
curActive.classList.toggle("active");
contActive.classList.toggle("show");
}
});
// MOVED FROM THE CLICK LISTENER
const chiudiPopup = spot.querySelector(".chiudi-popup");
chiudiPopup.addEventListener("click", () => {
spot.classList.remove("active");
contenuto.classList.remove("show");
});
});
EDIT: I missed that you have the img.chiudi-popup inside your container, which will trigger both event listeners. I would honestly just simplify the code and always hide the container when clicking on it again. You can still have the img.chiudi-popup (close image) to make it easier for the users to understand that they can click on it.
const tooltips = function() {
const spots = document.querySelectorAll(".spot");
spots.forEach((spot) => {
const contenuto = spot.querySelector(".contenuto-spot");
const pin = spot.querySelector(".pin");
spot.addEventListener("click", () => {
let curActive = document.querySelector(".spot.active");
let contActive = document.querySelector(".contenuto-spot.show");
if (curActive !== spot) {
spot.classList.add("active");
contenuto.classList.add("show");
}
if (curActive) {
curActive.classList.remove("active");
contActive.classList.remove("show");
}
});

check if class exists on that page javascript

I have an index page and a dashboard, on the index I'm using typewriter and particlesjs on the index only, and on the dashboard I have a sidebar.
If I have all the code as-is, I get errors as the page is still looking for typewriter and particlesjs on all pages.
So I have attempted to wrap each section around an if so the plan is if that class or id exists on that page it will only render that JS. So I've created the following code.
edited code below based on groovy_guy's answer
document.querySelector('nav .toggle').addEventListener('click', e => {
document.querySelector('nav .hidden').classList.toggle('show')
});
let checkTypewriter = document.getElementById('typewriter');
if (checkTypewriter.length > 0) {
new Typewriter('#typewriter', {
strings: ['Website Developer', 'Freelancer' , 'System Admin'],
autoStart: true,
loop: true
});
}
let checkParticlesjs = document.getElementsByClassName('particlesjs');
if (checkParticlesjs.length > 0) {
let particles = Particles.init({
selector: '.particlesjs',
color: ['#48F2E3', '#48F2E3', '#48F2E3'],
connectParticles: true,
maxParticles: 200
});
}
let checkSidebar = document.getElementsByClassName('sidebar');
if (checkSidebar.length > 0) {
user_wants_collapse = false;
// Fetch all the details element.
const details = document.querySelectorAll("details");
// Add the onclick listeners.
details.forEach((targetDetail) => {
targetDetail.addEventListener("click", () => {
// Close all the details that are not targetDetail.
details.forEach((detail) => {
if (detail !== targetDetail) {
detail.removeAttribute("open");
};
});
});
});
document.querySelector('section').addEventListener('click', (ev) => {
// close any open details elements that this click is outside of
let target = ev.target;
let detailsClickedWithin = null;
while (target && target.tagName != 'DETAILS') {
target = target.parentNode;
};
if (target && target.tagName == 'DETAILS') {
detailsClickedWithin = target;
};
Array.from(document.getElementsByTagName('details')).filter(
(details) => details.open && details != detailsClickedWithin
).forEach(details => details.open = false);
// if the sidebar collapse is true and is re-expanded by clicking a menu item then clicking on the body should re-close it
if (user_wants_collapse == true && (document.querySelectorAll('.sidebar details'))) {
document.querySelector('body').classList.add('is--expand');
};
});
// when the sidebar menu is clicked this sets the user_wants_collapse var to true or false and toggles is--expand class on body
document.querySelector('.sidebar .menu-link').addEventListener('click', () => {
document.querySelector('body').classList.toggle('is--expand');
user_wants_collapse = !user_wants_collapse
document.querySelector('.sidebar').classList.toggle('is--expand');
// show all text
document.querySelectorAll('.sidebar .title').forEach((el) => {
el.classList.toggle('hidden');
});
// changing sidebar menu items and menu collapse icons
const icon = document.querySelector('.menu-link-arrows span');
if (icon.classList.contains('fa-angle-double-left')) {
icon.classList.remove('fa-angle-double-left');
icon.classList.add('fa-angle-double-right');
} else {
icon.classList.remove('fa-angle-double-right');
icon.classList.add('fa-angle-double-left');
}
});
// making sure the sidebar menu items re-expands the sidebar on click
let x = document.querySelectorAll('.sidebar details');
let i;
for (i = 1; i < x.length; i++) {
x[i].addEventListener('click', () => {
// changing sidebar menu items and menu collapse icons
// change menu items and menu collapse icons
const icon = document.querySelector('.sidebar-drop-parent-arrow span');
if (icon.classList.contains('fa-chevron-down')) {
icon.classList.remove('fa-chevron-down');
icon.classList.add('fa-chevron-up');
} else {
icon.classList.remove('fa-chevron-up');
icon.classList.add('fa-chevron-down');
}
if (document.querySelector('body').classList.contains('is--expand')) {
document.querySelector('body').classList.remove('is--expand');
};
});
};
};
when loading the JS I'm not getting any console errors but I'm not seeing any result of the JS.
Why don't you use querySelector()? I think that's more uniform across your codebase. Besides, I see that you only care about one element and not a list of elements, so this method is ideal since it gets the first element that encounters.
const checkTypewriter = document.querySelector('#typewriter')
if (checkTypewriter) {
// Element with an ID 'typewriter' exist in the DOM.
}
const checkParticlesjs = document.querySelector('.particlesjs')
if (checkParticlesjs) {
// Element with a class named "particlesjs" exist in the DOM.
}
Also, make sure to check if an element exist before attaching an event listener:
const toggleNav = document.querySelector('nav .toggle')
if (toggleNav) {
toggleNav.addEventListener('click', (e) => {
document.querySelector('nav .hidden').classList.toggle('show')
});
}
For Javascript:
var checkTypewriter = document.getElementsByClassName('typewriter');
if (checkTypewriter.length > 0) {
// Here write your code
}
var checkParticlesjs = document.getElementsByClassName('particlesjs');
if (checkParticlesjs.length > 0) {
// Here write your specific code
}
For JQuery:
if ($("#typewriter")[0]){
// Here write your code
}
if ($(".particlesjs")[0]){
// Here write your specific code
}
This is how you can check if your classes exist,

Avoid numbers incrementing multiple times when calling a function multiple times

I have written a quantity selector function to display on a page. The page can open some modals, which need to have another quantity selector within each.
I am calling the function within the main page, and also within the modal (to enable the functionality once the modal is displayed.)
When I adjust the quantity in the modal, close the modal, and adjust the quantity on the main page, the quantity increments/decrements double (or 3 times if I was to call the function 3 times.)
Is there a way to "reset" each of these event listeners/functions, to only adjust for their respective elements?
I've looked into "removeEventListener" but haven't had any joy in implementing this within my code.
Example of my work so far here (you can see what I mean if you click the buttons.)
https://codepen.io/777333/pen/zYoKYRN
const quantitySelector = () => {
const qtyGroups = document.querySelectorAll('.qty-group');
if(qtyGroups) {
qtyGroups.forEach((qtyGroup) => {
const qtyDecrease = qtyGroup.querySelector('[data-quantity-decrease]');
const qtyIncrease = qtyGroup.querySelector('[data-quantity-increase]');
const qtyInput = qtyGroup.querySelector('[data-quantity-input]');
const disableEnableDecrease = () => {
if(qtyInput.value == 1) {
qtyDecrease.disabled = true;
} else {
qtyDecrease.disabled = false;
}
};
qtyDecrease.addEventListener('click', (event) => {
event.preventDefault();
if(qtyInput.value > 1) {
qtyInput.value--;
}
disableEnableDecrease();
});
qtyIncrease.addEventListener('click', (event) => {
event.preventDefault();
qtyInput.value++;
disableEnableDecrease();
});
qtyInput.addEventListener('keyup', () => {
disableEnableDecrease();
});
});
}
};
quantitySelector(); // called within main page
quantitySelector(); // called within modal
The issue at hand is that each time you're calling the function, a new event handler is added on top of the previous ones. The best way to avoid this is through Event Delegation where you add a global event handler only once.
// A global event handler
document.addEventListener(
"click",
function (event) {
// Find the qty-group if clicked on it
const qtyGroup = event.target.closest(".qty-group");
// Stop if the click was elsewhere
if (qtyGroup) {
// Get your elements
const qtyDecrease = qtyGroup.querySelector("[data-quantity-decrease]");
const qtyIncrease = qtyGroup.querySelector("[data-quantity-increase]");
const qtyInput = qtyGroup.querySelector("[data-quantity-input]");
const disableEnableDecrease = () => {
if (qtyInput.value == 1) {
qtyDecrease.disabled = true;
} else {
qtyDecrease.disabled = false;
}
};
// Match your elements against what was clicked on.
if (event.target == qtyDecrease) {
event.preventDefault();
if (qtyInput.value > 1) {
qtyInput.value--;
}
disableEnableDecrease();
}
if (event.target == qtyIncrease) {
event.preventDefault();
qtyInput.value++;
disableEnableDecrease();
}
}
},
false
);
Instead of listening to individual elements, you can capture all the clicks on the document, and then finding those that click on elements of interest. You can make a second event handler for the keyup event.
You can save the value of qtyInput on mousedown event and then in the increment you add or subtract one from the saved value instead of the current value of the input.
const quantitySelector = () => {
const qtyGroups = document.querySelectorAll('.qty-group');
if(qtyGroups) {
qtyGroups.forEach((qtyGroup) => {
const qtyDecrease = qtyGroup.querySelector('[data-quantity-decrease]');
const qtyIncrease = qtyGroup.querySelector('[data-quantity-increase]');
const qtyInput = qtyGroup.querySelector('[data-quantity-input]');
const disableEnableDecrease = () => {
if(qtyInput.value == 1) {
qtyDecrease.disabled = true;
} else {
qtyDecrease.disabled = false;
}
};
let savedValue = null;
const saveState = (evebt) => savedValue = Number(qtyInput.value);
qtyDecrease.addEventListener('mousedown', saveState)
qtyIncrease.addEventListener('mousedown', saveState)
qtyDecrease.addEventListener('click', (event) => {
event.preventDefault();
event.stopPropagation();
if(qtyInput.value > 1) {
qtyInput.value = savedValue - 1;
}
disableEnableDecrease();
});
qtyIncrease.addEventListener('click', (event) => {
event.preventDefault();
event.stopPropagation();
qtyInput.value = savedValue + 1;
disableEnableDecrease();
});
qtyInput.addEventListener('keyup', () => {
disableEnableDecrease();
event.stopPropagation();
});
});
}
};
quantitySelector();
quantitySelector();
There is a method called removeEventListener (MDN) but I suggest you to reshape your code such that you do not add event listener if they are already present.
Put all of your addEventListener just when you create your elements, or in a "document ready" callback if they are instantiated by HTML code. Then, when you open your modal, just update your values.
UPDATING YOUR CODE
// hide/show modal function
function toggleModal() {
let modal = document.getElementById('modal');
modal.style.display = modal.style.display == 'none' ? 'block' : 'none';
}
// your document ready function
function onReady() {
const qtyGroups = document.querySelectorAll('.qty-group');
if(qtyGroups) {
qtyGroups.forEach((qtyGroup) => {
const qtyDecrease = qtyGroup.querySelector('[data-quantity-decrease]');
const qtyIncrease = qtyGroup.querySelector('[data-quantity-increase]');
const qtyInput = qtyGroup.querySelector('[data-quantity-input]');
const disableEnableDecrease = () => {
if(qtyInput.value == 1) {
qtyDecrease.disabled = true;
} else {
qtyDecrease.disabled = false;
}
};
qtyDecrease.addEventListener('click', (event) => {
event.preventDefault();
if(qtyInput.value > 1) {
qtyInput.value--;
}
disableEnableDecrease();
});
qtyIncrease.addEventListener('click', (event) => {
event.preventDefault();
qtyInput.value++;
disableEnableDecrease();
});
qtyInput.addEventListener('keyup', () => {
disableEnableDecrease();
});
});
}
// attach hide/show modal handler
const toggle = document.getElementById('modal_toggle');
toggle.addEventListener('click', toggleModal);
}
onReady();
<div class="qty-group">
<button data-quantity-decrease disabled>-</button>
<input data-quantity-input value="1">
<button data-quantity-increase>+</button>
</div>
<div class="qty-group" id="modal" style="display: none;">
<button data-quantity-decrease disabled>-</button>
<input data-quantity-input value="1">
<button data-quantity-increase>+</button>
</div>
<button id="modal_toggle">Toggle Modal</button>
REFACTORING
It is better in such cases to reason as Components. Components ensure code encapsulation, maintainability, reusage, single responsability and many other usefull principles:
// hide/show modal function
function toggleModal() {
// get the modal
let modal = document.getElementById('modal');
// hide the modal
modal.style.display = modal.style.display == 'none' ? 'block' : 'none';
// reset the input of the modal
modalInputReference.reset();
}
function createQuantityInput(target, initialQuantity=1, min=1, max=10, step=1) {
let quantity = 0;
// assign and check if should be disable, also bind to input value
let assign = (q) => {
quantity = Math.max(Math.min(q, max), min);
decrease.disabled = quantity <= min;
increase.disabled = quantity >= max;
input.value = quantity;
};
// CREATION
// This part is not mandatory, you can also get the elements from
// the target (document.querySelector('button.decrease') or similar)
// and then attach the listener.
// Creation is better: ensure encapsulation and single responsability
// create decrease button
let decrease = document.createElement('button');
decrease.addEventListener('click', () => { assign(quantity - step); });
decrease.innerText = '-';
// create increase button
let increase = document.createElement('button');
increase.addEventListener('click', () => { assign(quantity + step); });
increase.innerText = '+'
// create input field
let input = document.createElement('input');
input.value = quantity
input.addEventListener('change', () => { assign(parseFloat(input.value)); });
// resetting the quantity
assign(initialQuantity);
// appending the new component to its parent
target.appendChild(decrease);
target.appendChild(input);
target.appendChild(increase);
// return a reference to manipulate this component
return {
get quantity() { return quantity; },
set quantity(q) { assign(q); },
assign,
reset: () => assign(initialQuantity)
};
}
// this will be your modal reference
let modalInputReference;
function onReady() {
// inject all qty-group with a "quantityInput" component
document.querySelectorAll('.qty-group').forEach(elem => {
let input = createQuantityInput(elem);
if (elem.id == 'modal') {
// if it is the modal I save it for later use
// this is just an hack for now,
// a full code should split this part into a "modal" component maybe
modalInputReference = input;
}
});
// emualte the modal
let toggle = document.getElementById('modal_toggle')
toggle.addEventListener('click', toggleModal)
}
// this function should be wrapped by a
// $(document).ready(onReady) or any other
// function that ensure that all the DOM is successfully loaded
// and the code is not executed before the browser has generated
// all the elements present in the HTML
onReady();
<div class="qty-group"></div>
<div class="qty-group" id="modal" style="display: none;"></div>
<button id="modal_toggle">Toggle Modal</button>
It is shorter (without comments) and also more maintenable. Don't trust who says it is overengineered, it is just kind of time to learn to reason this way, then is much easier and faster. It is just a time investment to waste less time in the future. Try figure out why React or Angular(JS) have climbed the charts of the best frameworks so fast.

how to fetch a new image from api after one click vanilla.js

I'm trying to figure out a better way to fetch a new image when clicking my button.
here is my code:
function getPizzaHtml(pizza) {
return `
<div class="header">Random Pizzas 🍕</div>
<button id="show-pizza-btn">Click Me</button>
<div class="pizza-image">
<img class="pizza-images" style="display: none" src="${pizza.image}"/>
</div>
`
}
getPizzaImg().then(pizza => {
console.log(pizza)
document.body.innerHTML = getPizzaHtml(pizza);
let pizzaBtn = document.getElementById('show-pizza-btn');
pizzaBtn.addEventListener('click', (e) => {
let pizzaImg = document.querySelector('.pizza-images');
if(pizzaImg.style.display === 'none') {
pizzaImg.style.display = 'inline'
} else {
window.location.reload();
}
})
})
with the if-statement, I can fetch a new image since it reloads the page and so reloads the fetch. But, in order for it to appear, I have to click it twice.
I appreciate all advice and help!
You can change pizzaImg.src to change the image. You can change your event listener to something like this:
pizzaBtn.addEventListener('click', (e) => {
let pizzaImg = document.querySelector('.pizza-images');
if(pizzaImg.style.display === 'none') {
pizzaImg.style.display = 'inline'
} else {
getPizzaImg().then(newPizza => pizzaImg.src = newPizza.image)
}
})

How to break animation and run it again?

I have a page with some anchors and links which lead on this anchors. I can click on the link and the anchor's background-color will become some color. With help of animation I make this backgound-color dissapear in 10 sec - first I make background-color white than I remove class and styles from element to reuse it.
But when I click on the link and go to the anchor which animation haven't finished, the color is not the same as the color on start of animation, it continue becoming more transpanent.
I want to click the same link again (for the anchor which hasn't finished dissapearing) and animation on this anchor have to stop and run again with full color of background. How can I do it?
The example of code:
$("a").each(function () {
$(this).click(function () {
const anchorName = this.href.slice(this.href.indexOf('#'));
goToAnchor(decodeURIComponent(anchorName));
});
});
const goToAnchor = (anchorId) => {
const anchor = document.getElementById(anchorId.replace('#', ''));
const nextElem = $(anchor).parent().text() !== '' ? $(anchor).parent() : $(anchor).parent().next();
$(nextElem).addClass('focus-on-anchor');
$(nextElem).clearQueue();
(function (elem) {
$(elem).animate({
backgroundColor: 'rgb(255,255,255)'
}, 10000, function () {
$(this).removeAttr('class style');
});
}(nextElem));
}
https://jsfiddle.net/fiorsaoirse/j247atLc/13/
All I had to do is to add checking on having 'style' in element before running the animation:
$("a").each(function () {
$(this).click(function () {
const anchorName = this.href.slice(this.href.indexOf('#'));
goToAnchor(decodeURIComponent(anchorName));
});
});
const goToAnchor = (anchorId) => {
const anchor = document.getElementById(anchorId.replace('#', ''));
const nextElem = $(anchor).parent().text() !== '' ? $(anchor).parent() : $(anchor).parent().next();
$(nextElem).addClass('focus-on-anchor');
$(nextElem).stop().clearQueue();
if ($(nextElem).is('[style]')) {
// In case when animation hasn't stopped yet
$(nextElem).removeAttr('style');
}
makeElemDisappear($(nextElem));
}
const makeElemDisappear = (elem) => {
$(elem).animate({
backgroundColor: 'rgb(255,255,255)'
}, 10000, function () {
$(this).removeAttr('class style');
});
}

Categories

Resources