Event target and rest operator with nodelist - javascript

I have four buttons, each of these is different than others.
What I want:
When I click on the button I want to add to this button a class named 'togglemath' and exactly in the same time I want to remove class from the three left buttons ( of course if the buttons has a classname 'togglemath')
I know that the code below is bad, but I put this here to understand what I mean.
const sumButton = document.getElementById('add');
const substractButton = document.getElementById('subtract');
const multipleButton = document.getElementById('multiple');
const divideButton = document.getElementById('divide');
const mathButtons = [sumButton, substractButton, multipleButton, divideButton];
const activeClass = () => {
mathButtons.forEach(el => {
el.addEventListener('click', e => {
[e.target, ...rest] = mathButtons;
e.target.classList.add('togglemath');
rest.classList.remove('togglemath');
});
});
};
activeClass();

Lets make for example 3 buttons:
<button class="toggle">1</button>
<button class="toggle">2</button>
<button class="toggle">3</button>
<button class="toggle">4</button>
As far as I understand you need to remove class togglemath from all the buttons except the one that you clicked on. Then we can do something like this:
const TOGGLE_CLASS_NAME = 'togglemath';
const $buttons = document.getElementsByClassName('toggle');
function toggleClass(event) {
for (const $button of $buttons) {
$button.classList.remove(TOGGLE_CLASS_NAME);
}
event.target.classList.add(TOGGLE_CLASS_NAME);
}
for (const $button of $buttons) {
$button.addEventListener('click', toggleClass);
}

First remove the class from all the elements when a button is clicked and then add the class to the clicked button:
const sumButton = document.getElementById('add');
const substractButton = document.getElementById('subtract');
const multipleButton = document.getElementById('multiple');
const divideButton = document.getElementById('divide');
const mathButtons = [sumButton, substractButton, multipleButton, divideButton];
mathButtons.forEach(button => button.addEventListener('click', activeClass))
function activeClass() {
mathButtons.forEach(button => button.classList.remove('togglemath'));
event.target.classList.add('togglemath');
}
.togglemath {
color: red;
}
<button id="add">add</button>
<button id="subtract">subtract</button>
<button id="multiple">multiple</button>
<button id="divide">divide</button>

You are not far from the actual solution. The problem seems to be with the understanding of the following line:
[e.target, ...rest] = mathButtons;
This doesn't search the e.target out of mathButtons and assigns the other elements to rest. But instead assigns e.target to the first element in mathButtons and rest to the second, third end fourth. This produces the follow-up problem that e.target.classList.add('togglemath') will always add the class to the first button.
rest.classList.remove('togglemath') has a somewhat other issue, namely that you try to access classList on an array. Instead you should access it for element in the array.
Without changing your code much you could be looking at something like this:
const sumButton = document.getElementById('add');
const substractButton = document.getElementById('subtract');
const multipleButton = document.getElementById('multiple');
const divideButton = document.getElementById('divide');
const mathButtons = [sumButton, substractButton, multipleButton, divideButton];
const activeClass = () => {
mathButtons.forEach(el => {
el.addEventListener('click', e => {
mathButtons.forEach(mathButton => {
mathButton.classList.remove('togglemath');
});
e.target.classList.add('togglemath');
});
});
};
activeClass();
This solution first removes all togglemath classes from all buttons. Then adds it (back) to the clicked target.
Strafing somewhat from what you've provided, you could opt to save the active button in a variable. This way you only have to remove the class from the current active button, add it to the event target, and replace the active variable with the clicked target.
const sumButton = document.getElementById('add');
const substractButton = document.getElementById('subtract');
const multipleButton = document.getElementById('multiple');
const divideButton = document.getElementById('divide');
const mathButtons = [sumButton, substractButton, multipleButton, divideButton];
const activeClass = () => {
let active;
mathButtons.forEach(button => {
button.addEventListener('click', event => {
if (event.target === active) return;
if (active) active.classList.remove('togglemath');
event.target.classList.add('togglemath');
active = event.target;
});
});
};
activeClass();
button {
border: 1px solid #33497b;
padding: 0.25em 1em;
background-color: white;
color: #33497b;
}
button.togglemath {
background-color: #33497b;
color: white;
}
<button id="add" type="button">+</button>
<button id="subtract" type="button">−</button>
<button id="multiple" type="button">×</button>
<button id="divide" type="button">÷</button>

Related

I am unable to apply color styles for html elements generated via createElement in javascript

Issue is: after hitting the button on the HTML page, the html <h5> tag text changes on the page but the <h5> tag text color wont change to blue (expected behavior as CSS style doesn't reload after clicking the button).
What could be a possible workaround for solving this issue?
const btn = document.querySelector(".test");
btn.addEventListener("click", () => {
a1 = document.createElement('h5');
a1.className = "first";
a1.textContent = 'Blue updated.';
document.getElementById('position').innerHTML = a1.innerText;
//newtext = document.createTextNode('abc');
});
.test {
color: blue;
}
.first {
color: blue;
}
<h5 id="position">Text Color to be replaced to blue after hitting Blue button(but not happening)</h5>
<button class="test">Change to blue</button>
Above, after the button is clicked and the action listener is triggered, the HTML <h5> tag elements code are created with a1 = document.createElement('h5'); a1.className = "first"
The new text is displayed but the color didn't change (to blue).
You're inserting only the textContent instead of appending the entire new H5 element
const btn = document.querySelector(".test");
const pos = document.querySelector('#position');
btn.addEventListener("click", () => {
const h5 = document.createElement('h5');
h5.className = "first";
h5.textContent = 'Blue updated.';
pos.innerHTML = ""; // Empty
pos.append(h5); // Append!
});
.test, .first { color: blue; }
<h5 id="position">Text Color to be replaced to blue after hitting Blue button(but not happening)</h5>
<button class="test">Change to blue</button>
PS: You can also create some nifty reusable functions to handle the DOM querying and creation of elements, using a friendly syntax:
// DOM utility functions:
const el = (sel, par) => (par || document).querySelector(sel);
const elNew = (tag, prop) => Object.assign(document.createElement(tag), prop);
// Example:
const elPos = el('#position');
el(".test").addEventListener("click", () => {
const elH5 = elNew('H5', {
textContent: "Blue updated",
className: "first",
});
elPos.innerHTML = ""; // Empty
elPos.append(elH5); // Append!
});
.test, .first { color: blue; }
<h5 id="position">Text Color to be replaced to blue after hitting Blue button(but not happening)</h5>
<button class="test">Change to blue</button>

Button Functionality in JS

I created A simple Website for basic add/remove friend.
But the button is only working in first box and not working in other two boxes.
Button is completly working in first box but second and button button is not responding
Edit - All the buttons only changing the first box text
var arr = [{
name: "Raju",
image: "./1.jpg",
status: "Stranger"
},
{
name: "Shyam",
image: "./2.jpg",
status: "Stranger"
},
{
name: "Babu Bhaiya",
image: "./3.jpg",
status: "Stranger"
},
];
var cluster = "";
arr.forEach(function(val) {
cluster = cluster + `
<div id="card">
<img src="${val.image}" alt="">
<h1>${val.name}</h1>
<p>${val.status}</p>
<button>Send Request</button>
</div>
`;
});
//Botton Functionality
document.getElementById("cluster").innerHTML = cluster;
const btn = document.querySelectorAll("button");
var fact = 0;
btn.forEach((btn) => {
btn.addEventListener("click", function() {
if (fact == 0) {
var timer = setTimeout(() => {
document.querySelector("p").textContent = "Friend";
document.querySelector("p").style.color = "rgb(66, 178, 113";
document.querySelector("button").textContent = "Cancel";
}, 2000);
fact = 1;
document.querySelector("p").textContent = "Request Pending";
document.querySelector("button").textContent = "Send Request";
} else {
clearTimeout(timer);
fact = 0;
document.querySelector("p").textContent = "Strenger";
document.querySelector("p").style.color = "rgb(221, 66, 66)";
document.querySelector("button").textContent = "Send Request";
fact = 0;
}
});
});
<div class="container">
<div id="cluster" class="box"></div>
</div>
Instead of attaching listeners to all of the buttons you can use event delegation and attach one listener to a containing element that can listen out for events from its children as they "bubble up" the DOM.
In this example when a button is clicked we destructure out its previousElementSibling (the paragraph element), and its textContent. And then based on the textContent perform the operations.
(I've also used a switch statement instead of flags which makes the code a little cleaner, and map to produce the HTML rather than forEach.)
const arr=[{name:"Raju",image:"./1.jpg",status:"Stranger"},{name:"Shyam",image:"./2.jpg",status:"Friend"},{name:"Babu Bhaiya",image:"./3.jpg",status:"Request Pending"}];
// `map` over the array and produce an array of
// strings which is `joined` up at the end of the iteration
const html = arr.map(obj => {
return `
<div id="card">
<img src="${obj.image}" alt="">
<h3>${obj.name}</h3>
<p>${obj.status}</p>
<button type="button">Send Request</button>
</div>
`;
}).join('');
// Cache the container/cluster elements
const cluster = document.querySelector('#cluster');
const container = document.querySelector('.container');
// Add the event listener to the container
container.addEventListener('click', handleClick);
// Add the HTML to the cluster element
cluster.innerHTML = html;
// When the listener catches an event...
function handleClick(e) {
// ...first check that it's a button...
if (e.target.matches('button')) {
// ...destructure the previous element sibling
// element (relabeling it as `para`), and the textContent
// from the clicked element
const {
previousElementSibling: para,
textContent
} = e.target;
// Depending on the text content value
// choose the path to take
switch (textContent) {
case 'Send Request': {
para.textContent = 'Request Pending';
e.target.disabled = true;
setTimeout(() => {
para.textContent = 'Friend';
para.className = 'friend';
e.target.textContent = 'Cancel';
e.target.disabled = false;
}, 2000);
break;
}
case 'Cancel': {
para.textContent = 'Strenger';
para.className = 'strenger';
e.target.textContent = 'Send Request'
break;
}
}
}
}
.friend { color: rgb(66, 178, 113); }
.strenger { color: rgb(221, 66, 66); }
<div class="container">
<div id="cluster" class="box"></div>
</div>
Additional documentation
Destructuring assignment
map
matches
You are using document.querySelector() which will only select the first matched item.
According to MDN
The Document method querySelector() returns the first Element within the document that matches the specified selector, or group of selectors.
Instead what you need is querySelectorAll
const buttons = userList.querySelectorAll("button");
buttons.forEach((btn) => {
btn.addEventListener("click", function() {
//...
}
});
Edit:
All buttons changing the first text is the same issue as querySelector. It can be fixed by scoping it.
const buttons = userList.querySelectorAll("button");
buttons.forEach((btn) => {
btn.addEventListener("click", function() {
btn.querySelector(":scope > p").textContent = "Friend";
// ...
}
});

Switch paragraphs using inner HTML in JS

I am trying to switch two paragraphs after clicking the button but I am stuck. Is there any way how to do this using inner HTML without using IF or boolean? I tried this code but it doesn't work. Thanks
let elmsButton = document.querySelectorAll("button")[0];
let elmsParagraf1 = document.querySelectorAll(".prvni")[0];
let elmsParagraf2 = document.querySelectorAll(".druhy")[0];
elmsButton.addEventListener("click", () => {
elmsParagraf1.innerHTML = "<div class='druhy'></div>"
elmsParagraf2.innerHTML = "<div class='prvni'></div>"
});
Assign each DOM.innerHTML of paragraph to a variable then swap them like below example:
let elmsButton = document.querySelectorAll("button")[0];
let elmsParagraf1 = document.querySelectorAll(".prvni")[0];
let elmsParagraf2 = document.querySelectorAll(".druhy")[0];
elmsButton.addEventListener("click", () => {
const p1 = elmsParagraf1.innerHTML;
const p2 = elmsParagraf2.innerHTML
elmsParagraf1.innerHTML = p2;
elmsParagraf2.innerHTML = p1
});
<button>Click</button>
<div class='prvni'>Paragraph 1</div>
<div class='druhy'>Paragraph 2</div>
Why don't you use querySelector in place of using querySelectorAll and choose the first element?
By the way, I advise to delete and re-add the elements from the parent rather than using innerHTML. The use of innerHTML would not preserve listeners and have worse performances:
let elmsButton = document.querySelector("button");
let elmsParagraf1 = document.querySelector(".prvni");
let elmsParagraf2 = document.querySelector(".druhy");
elmsButton.addEventListener("click", () => {
swapElements(elmsParagraf1, elmsParagraf2);
});
function swapElements(elem1, elem2) {
// Check if siblings
if (elem1.parentElement !== elem2.parentElement) {
console.error('No sibling elements!');
return;
}
elem1.replaceWith(elem2.cloneNode(true));
elem2.replaceWith(elem1.cloneNode(true));
}
Example:
let elmsButton = document.querySelector("button");
elmsButton.addEventListener("click", () => {
let elmsParagraf1 = document.querySelector(".prvni");
let elmsParagraf2 = document.querySelector(".druhy");
swapElements(elmsParagraf1, elmsParagraf2);
});
function swapElements(elem1, elem2) {
// Check if siblings
if (elem1.parentElement !== elem2.parentElement) {
console.error('No sibling elements!');
return;
}
elem1.replaceWith(elem2.cloneNode(true));
elem2.replaceWith(elem1.cloneNode(true));
}
<button>Click me</button>
<div class="prvni">I am the first div</div>
<div class="druhy">I am the second div</div>
You can switch those two divs by creating a temporary variable that holds Paragraf1 and then change Paragraf1 to Paragraf2 and vice versa, but with the variable.
let temp = elmsParagraf1.innerHTML;
elmsParagraf1.innerHTML = elmsParagraf2.innerHTML
elmsParagraf2.innerHTML = temp

How Dynamically Created Button Target Individual Element

I am under the impression if we target an element by element name of class name it will be applicable to all elements or elements with that class name. But in the following code when I clicked a button it will only change the innerHTML of the nearest span element even though I have add event listener by selecting just button element. Please clarify. Thanks.
const myArray = [0, 1, 2];
let myContainer = document.querySelector('.container');
const newArray = myArray.map((item) => {
let newArticle = document.createElement('article');
const myHTML = `<article>
<span></span>
<button>Click ${item}</button>
</article>
`;
newArticle.innerHTML = myHTML;
let myBtn = newArticle.querySelector('button');
myBtn.addEventListener('click', (e) => {
newArticle.querySelector('span').innerHTML = 'clicked';
})
return newArticle;
});
newArray.forEach((item) => {
myContainer.appendChild(item);
});
<div class="container">
<section>
</section>
</div>
let myBtn = newArticle.querySelector('button');
returns the FIRST button in newArticle
let myBtn = document.querySelectorAll('button');
returns ALL buttons in document
You can tell us what you expect by clicking on each one button?

How to add concatenate button value into an api

So I'm making a holiday finder "app" and I want to have buttons where when someone clicks the name of their country it inputs the country's value into the api string.
What I did was loop over all the buttons and save the target value to a variable which I then concatenated into the api. The thing is when I look in the console at the api fetched, where the country code is supposed to be it says "undefined".
I'm a bit confused on why so if you find the solution please explain it.
let countrySelect = document.getElementById('country-select');
let holidayName = document.getElementById('holiday-name');
let holidayDesc = document.getElementById('holiday-desc');
let holidayDate = document.getElementById('holiday-date');
let holidayType = document.getElementById('holiday-type');
let info = document.querySelector('.cell');
let buttonValue;
// get button values
const button = document.querySelectorAll("button").forEach(
button => button.addEventListener('click', function(e) {
buttonValue = e.target.value;
console.log(buttonValue)
})
);
// api url
const api = `https://calendarific.com/api/v2/holidays?&api_key=<api key>&country=${buttonValue}&year=2020`;
// When the button is clicked fetch results
countrySelect.addEventListener('click', function() {
fetch(api)
.then(res => res.json())
.then(data => {
var apiResponse = data.response;
console.log(apiResponse);
}, networkError => {
alert(networkError)
})
})
You need to define / redefine your api variable within the countrySelect event listener.
At the moment it is being defined before any buttons are clicked, so buttonValue is undefined. So even if your buttonValue changes in response to buttons being clicked, the api variable stays how it was, ie. with country=undefined.
let countrySelect = document.getElementById('country-select');
let buttonValue;
const button = document.querySelectorAll("button").forEach(
button => button.addEventListener('click', function(e) {
buttonValue = e.target.value;
console.log(buttonValue);
})
);
// When the button is clicked fetch results
countrySelect.addEventListener('click', function() {
const api = `https://calendarific.com/api/v2/holidays?&api_key=<api key>&country=${buttonValue}&year=2020`;
console.log(api);
});
#country-select {
border: 1px solid green;
color: green;
display: inline-block;
cursor: pointer;
}
<button value='uk'>
UK
</button>
<button value ='us'>
US
</button>
<div id='country-select'>
Select Country
</div>

Categories