How to remove duplicate instances of class - javascript

In javascript, I am trying to apply an 'active' class upon clicking a button. However, I would then like to remove the class from the button which previously had it. I'm not sure how to best go about this. I was considering something involving a second loop after the click, but that seems somewhat convoluted, and like there's probably a better way. Here's the code I have to add the class, but again, not sure how to best go about removing it from the button to which it was previously applied.
const giftcards = document.querySelectorAll('.giftcard');
for(let giftcard of giftcards){
giftcard.onclick = () => {
giftcard.classList.add('active');
}
}

If there is always at most one .giftcard with an active class you could query for that giftcard before setting active to the currently clicked one using document.querySelector('.giftcard.active') and utilize Optional chaining (?.) to remove the active class only if an element was found.
const giftcards = document.querySelectorAll('.giftcard');
for (let giftcard of giftcards) {
giftcard.onclick = () => {
document.querySelector('.giftcard.active')?.classList.remove('active');
giftcard.classList.add('active');
}
}
.active {
color: red;
}
<div class="giftcard">card 1</div>
<div class="giftcard">card 2</div>

<div>
<button class="giftcard">Button 1</button>
<button class="giftcard">Button 1</button>
<button class="giftcard">Button 1</button>
</div>
<script>
const giftcards = document.querySelectorAll(".giftcard");
giftcards.forEach((giftcard) => {
giftcard.addEventListener("click", () => {
giftcardClick(giftcard);
});
});
function giftcardClick(giftcard) {
giftcards.forEach((giftcard) => {
giftcard.classList.remove("active");
});
giftcard.classList.add("active");
}
</script>

Related

I want to disable the options for each question saperately after clicking the option for that question

setIsStarted(true)
setCurrentScore(0)
setResultCard(false)
setCurrentQuestion(0)
}
const checkOption = (isCorrect) =>{
setDisability(true)
if(isCorrect){
setCurrentScore(currentScore+1);
}
setCurrentQuestion(currentQuestion+1);
if(currentQuestion==questions.length-1){
setShowResult(true);
}
}
function showResultButton(showResult){
if (showResult) {
return <button className='show-result' onClick={showResultCard}>Show results</button>
}
}
const showResultCard = () =>{
setShowResult(false)
setResultCard(true);
setIsStarted(false)
}
function displayResultCard(resultCard){
if(resultCard){
return <div className='score-card'>You have answerd {currentScore}/5 correctly</div>
}
}
const updateDisability = () =>{
setDisability(false)
}
function DisplayQuesion(){
const quesList = questions.map((element) => {
return(
<div className='question-card'>
<h2>{element.text}</h2>
<ul>
{element.options.map((option) => {
return <button key={option.id} onClick={() => checkOption(option.isCorrect)} disabled={updateDisability}>{option.text}</button>
})}
</ul>
</div>
)
})
return (<div>{quesList}</div>)
}
In this code my all options are getting disabled even after clicking one option.When an option for a question is clicked, all other buttons for the question are disabled. when all questions are answered, the show results button gets shown.
This happens because you're not splitting down the list of questions in child components and so on.
You need to create three components to achieve this:
Questionary
Question
Answer
By doing so, you'll be able to define a separate state for each question/answer so they don't get mixed up.
Within your code, notice that if you click on an item it will update the state of the entire component which consequentially will update also all the other answers because they shared the same state.
You can check this link to have a better understanding:
https://www.pluralsight.com/guides/passing-state-of-parent-to-child-component-as-props

How can I create functionality that allows the user to filter multiple times in JavaScript?

I have been working on a website that displays projects completed by students for their Senior Project. I wanted to be able to sort by project category to make it easier for the user. I added that functionality in and everything works well. I then decided since there are going to be multiple years worth of Senior projects on my website, I want to filter by year as well. However, I can't just add more buttons for years. For example, if I filtered by class of 2022, I wouldn't be able to filter by category within the class of 2022. More specifically, when I press 2022, it shows all of the students in that class. Then, if I press Creative Project for example, it goes back to showing every single Creative Project regardless of year instead of from the class of 2022. My initial thought process was to just make another function and div for the grad year buttons. However, that does not change anything. Here is my JavaScript code:
function filterProject(value){
let buttons = document.querySelectorAll(".button-value");
buttons.forEach((button) => {
if(value.toUpperCase() == button.innerText.toUpperCase()){
button.classList.add("active");
}else{
button.classList.remove("active");
}
});
let elements = document.querySelectorAll(".card");
elements.forEach((element) => {
if(value == "all"){
element.classList.remove("hide");
}
else{
//having a space messes it up, make it _
if(element.classList.contains(value.replace(" ", "_"))){
element.classList.remove("hide");
}
else{
element.classList.add("hide");
}
}
});
}
function filterProject2(value){
let buttons = document.querySelectorAll(".grad-button");
buttons.forEach((button) => {
if(value.toUpperCase() == button.innerText.toUpperCase()){
button.classList.add("active");
}else{
button.classList.remove("active");
}
});
let elements = document.querySelectorAll(".card");
elements.forEach((element) => {
if(value == "All Years"){
element.classList.remove("hide");
}
else{
//having a space messes it up, make it _
if(element.classList.contains(value.replace(" ", "_"))){
element.classList.remove("hide");
}
else{
element.classList.add("hide");
}
}
});
}
Here is the relevant HTML code as well:
<div id ="buttons">
<button class = "button-value" onclick="filterProject('all')">All</button>
<button class = "button-value" onclick="filterProject('Creative Project')">Creative Project</button>
<button class = "button-value" onclick="filterProject('Developing Voice')">Developing Voice</button>
<button class = "button-value" onclick="filterProject('Interdisciplinary Fusion')">Interdisciplinary Fusion</button>
<button class = "button-value" onclick="filterProject('Personal Writing')">Personal Writing</button>
<button class = "button-value" onclick="filterProject('Curriculum Designer')">Curriculum Designer</button>
<button class = "button-value" onclick="filterProject('Internship')">Internship</button>
</div>
<div id ="gradbuttons">
<button class = "grad-button" onclick="filterProject2('All Years')">All Years</button>
<button class = "grad-button" onclick="filterProject2('2021')">2021</button>
<button class = "grad-button" onclick="filterProject2('2022')">2022</button>
</div>
I know that I could just add another page that separates the projects by grad year and has different buttons for each year, but I want to use JavaScript and make the website cleaner. Any suggestions will help. Thanks!
I would use an object var filters = { "category": "", "year": "" }; as a state variable to store the current filter options. Since you do similar logic in both of your filter functions, I would combine them into a single function that accepts an additional parameter filterType which is either "category" or "year". Then in your filtering logic, you can update the current filter for the appropriate filter type and make sure every condition in your filters is met.
Instead of using a unique class to hide elements, you can use two different classes, for example category-hidden and year-hidden.
Inside category filter:
button.classList.add('category-hidden')
Inside year filter:
button.classList.add('year-hidden')
Then, in your css, you hide the elements that have both classes.
<style>
.category-hidden .year-hidden {
display: none;
}
</style>
EDIT
I've just realized that this approach won't work if you select only one filter. So, you'll need to add classes to the elements' container indicating which filters are currently active. For example, if you activate the year filter add the class year-filter-active.
<div id="container" class="year-filter-active">
...
elements
...
</div>
And in your css put the following rules:
<style>
div.year-filter-active .year-hidden, div.category-filter-active .category-hidden {
display: none;
}
</style>

How to single select button using jquery?

How can I manipulate CSS and read text value and save it into variable when clicking on the button. when I click on the button using jquery how can I add a CSS to the button such as class="btn-n-active".
How to make sure that only one button is selected while switching on different buttons and that button should have active CSS
I was trying to just read the single value from the options, just a single selection.
<p>
<button onclick="myFunction()" class="btn-n">DOG</button>
<button onclick="myFunction()" class="btn-n">CAT</button>
<button onclick="myFunction()" class="btn-n">LION</button>
<button onclick="myFunction()" class="btn-n">PIG</button>
</p>
const myFunction = () => {
}
It's like a quiz system where I just want to read a single value. I am not able to apply the logic
There are a bunch of options to do this. Below you will see 3 of them.
The idea is to select all buttons and remove the active class and then add it to the button you clicked on.
My jquery is a bit rusty but I suggest you use just javaScript for such a simple request
const myFunction = (event) => {
const clickedElem = event.target
const allBtns = document.querySelectorAll('.btn-n')
allBtns.forEach(btn => btn.classList.remove("btn-n-active"))
clickedElem.classList.add("btn-n-active")
}
// option 2 without adding function in html
/* const allBtns = document.querySelectorAll('.btn-n')
allBtns.forEach(btn => btn.onclick = () => {
allBtns.forEach(btn => btn.classList.remove("btn-n-active"))
btn.classList.add("btn-n-active")
}) */
//option3 simple jQuery
/* const allBtns = $('.btn-n');
allBtns.click(function() {
$(this).addClass("btn-n-active")
allBtns.not(this).removeClass("btn-n-active")
}) */
const submit = () => {
const selectedText = document.querySelector(".btn-n-active") ? document.querySelector(".btn-n-active").innerText : 'Please select one'
console.log(selectedText)
}
.btn-n-active {
background: red
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<p>
<button onclick="myFunction(event)" class="btn-n">DOG</button>
<button onclick="myFunction(event)" class="btn-n">CAT</button>
<button onclick="myFunction(event)" class="btn-n">LION</button>
<button onclick="myFunction(event)" class="btn-n">PIG</button>
</p>
<button onclick="submit()">Click to see selected text</button>
You can find the selected answer button having the active class using the hasClass('btn-n-active')
$("button").click(function(){
$("button").removeClass("btn-n-active"); // Remove other active class
$(this).addClass("btn-n-active"); // Add active class to the clicked button
});
const myFunction = () => {
var selectedButtonText = $("button").hasClass("btn-n-active").html();
console.log("Selected answer: " + selectedButtonText);
}

Create a list of settings with Next and Previous buttons in the DOM procedurally

I wanted to create a list of settings that a user can change in the HTML procedurally through javascript.
Much like this: Quality: - 0 +
My approach to this was making an Option class with a value property and prev() and next() methods that change the value within its range. I'm extending this class so it can be a Range, Bool, etc. This is working fine.
My issue is that I can't seem to be able to incorporate this into the HTML. My current solution works only for the last option created, the others don't trigger the onclick event functions, and even if they did I don't feel like this is the right approach to it. How can I make this work in an elegant way?
I have tried the solution shown in this question but it prevents me from accessing the class instance with this.
class UIManager {
constructor (wrapperID, settings) {
this.wrapper = document.getElementById(wrapperID)
this.settings = settings
}
updateUI () {
this.wrapper.innerHTML = ``
for (let id = 0; id < this.settings.options.length; ++id) {
let option = this.settings.options[id]
this.wrapper.innerHTML += `
<li>
<div class="label">
${option.name}
</div>
<div class="option">
<input id="prev${id}" class="open" type="button" value="<">
${option.value}
<input id="next${id}" class="close" type="button" value=">">
</div>
</li>
`
let prevButton = document.getElementById(`prev${id}`)
let nextButton = document.getElementById(`next${id}`)
prevButton.onclick = _ => {
this.settings.options[id].prev()
this.updateUI()
}
nextButton.onclick = _ => {
this.settings.options[id].next()
this.updateUI()
}
}
}
}
The answer to the question you linked as a potential solution is usable so long as you bind the class instance member this to the anonymous function using Function.protoype.bind.
The code would look something like this (using the previously linked answer as the starting point):
for ( var i = 0; i < itemLists.length; i++ ) (function(i){
itemLists[i].onclick = function() {
// do something using `this`
}
}).bind(this)(i);
You mentioned in the comments that this didn't work, but that it was related to overwriting innerHTML and not due to the binding.
Hope this gives a small part of the larger picture.
Try closure to store the values required for functions created from inside the loop.
Something like this :
prevButton.onclick = (function(settings, updateUI) {
return function() {
settings.options[id].prev();
updateUI();
};
})(this.settings, this.updateUI);
nextButton.onclick = (function(settings, updateUI) {
return function() {
settings.options[id].next();
updateUI();
}
})(this.settings, this.updateUI);

Click event on div with img and text not firing when clicking img

I wrote toggle script in ES6/vanilla JS. The intended functionality is super simple, you click on the toggle div and it adds an active class to another div that matches the toggle div's data-toggle property. In my toggle div, I need there to be both text and an image. It works great when you click on the text within the div, but when you click on the image within the div, the toggle is not firing. Is there something specific I need to do to include all of the children within the div?
For some reason, I can't even get this working via this code snippet editor, but it is working in my project.
const setActive = (toggles, panels, id) => {
let activePanel = panels.filter(panel => panel.getAttribute('data-toggle') == id)
let activeToggle = toggles.filter(toggle => toggle.getAttribute('data-toggle') == id)
activePanel.forEach(panel => panel.classList.add('active'))
activeToggle.forEach(toggle => toggle.classList.add('active'))
}
const removeActive = (nodes) => {
nodes.forEach(node => node.classList.remove('active'))
}
const handler = (event) => {
event.preventDefault()
let id = event.target.getAttribute('data-toggle')
let panels = Array(...document.querySelectorAll('.js-toggle-panel'))
let toggles = Array(...document.querySelectorAll('.js-toggle'))
removeActive(panels)
removeActive(toggles)
setActive(toggles, panels, id)
}
let toggles = Array(...document.querySelectorAll('.js-toggle'))
toggles.forEach(toggle => toggle.addEventListener('click', handler))
.toggle-panel {
display: none;
}
.toggle-panel .active {
display: block;
}
<div class="js-toggle toggle" data-toggle="toggle-1">
<img src="https://via.placeholder.com/50"> First toggle
</div>
<div class="js-toggle toggle" data-toggle="toggle-2">
Second toggle
</div>
<div class="js-toggle-panel toggle-panel" data-toggle="toggle-1">
<h1>Toggle 1</h1>
</div>
<div class="js-toggle-panel toggle-panel" data-toggle="toggle-2">
<h1>Second toggle!</h1>
</div>
I changed two things that I believe will resolve your issue:
I changed the selector .toggle-panel .active to .toggle-panel.active-- without that, even in the cases where the JS was working as you intended nothing was actually be made visible.
I moved your code from using event.target to event.currentTarget -- the former always points to the clicked element, whereas the latter refers to the element on which the listener has been placed.
See the snippet below.
const setActive = (toggles, panels, id) => {
let activePanel = panels.filter(panel => panel.getAttribute('data-toggle') == id)
let activeToggle = toggles.filter(toggle => toggle.getAttribute('data-toggle') == id)
activePanel.forEach(panel => panel.classList.add('active'))
activeToggle.forEach(toggle => toggle.classList.add('active'))
}
const removeActive = (nodes) => {
nodes.forEach(node => node.classList.remove('active'))
}
const handler = (event) => {
event.preventDefault()
let id = event.currentTarget.getAttribute('data-toggle')
let panels = Array(...document.querySelectorAll('.js-toggle-panel'))
let toggles = Array(...document.querySelectorAll('.js-toggle'))
removeActive(panels)
removeActive(toggles)
setActive(toggles, panels, id)
}
let toggles = Array(...document.querySelectorAll('.js-toggle'))
toggles.forEach(toggle => toggle.addEventListener('click', handler))
.toggle-panel {
display: none;
}
.toggle-panel.active {
display: block;
}
<div class="js-toggle toggle" data-toggle="toggle-1">
<img src="https://via.placeholder.com/50"> First toggle
</div>
<div class="js-toggle toggle" data-toggle="toggle-2">
Second toggle
</div>
<div class="js-toggle-panel toggle-panel" data-toggle="toggle-1">
<h1>Toggle 1</h1>
</div>
<div class="js-toggle-panel toggle-panel" data-toggle="toggle-2">
<h1>Second toggle!</h1>
</div>
Instead of event.target you should use event.currentTarget in your handler function to return node to which event listener is attached. event.target is returning <img> node, not <div> with data-toggle in your case.

Categories