Reputation: 2231
I am working on an API where the plan is that the users fill in a short checklist with multiple options before a POST request is made. For each section the users gets multiple options / buttons, but only 1 of them can be selected.
The selected button will get the class marked
(Which changes the background color to green) whilst the others remain white (unless one of them is clicked, in which case it turns green and the others become white)
I have tried two different Javascript functions, but so far none of them were able to get this behavior to work.
Attempt 1:
function Label(self, group) {
// mark button
let btns = document.getElementsByClassName(group);
for (el in btns) {
btns[el].classList.remove('marked')
}
self.classList.add('marked');
}
Attempt 2 (a more explicit check to see if self
is involved)
function Label(self, group) {
// mark button
let btns = document.getElementsByClassName(group);
for (el in btns) {
if (btns[el] !== self) {
btns[el].classList.remove('marked')
}
}
self.classList.add('marked');
}
My reasoning was to first remove the .marked
class from all elements, and then set it on the this
element. As the function is called with the onclick
command in the HTML it knows which of the elements it is.
<div class="group1">
<button onclick="Label(this, pt_toggle)" class="pt_toggle">Incorrect</button>
<button onclick="Label(this, pt_toggle)" class="pt_toggle">Partially correct</button>
<button onclick="Label(this, pt_toggle)" class="pt_toggle">Correct</button>
</div>
However, the functions did not behave as I hoped. It throws an Uncaught TypeError: Cannot read property 'remove' of undefined
error and the class is not set.
Does anyone know what I am doing wrong?
Upvotes: 0
Views: 953
Reputation: 12115
There is an element which performs this same kind of thing natively: the radio button. This makes it easier to code a more concise solution (well, except the CSS to make it look like more a button, but that's optional):
var buttons = Array.from(document.querySelectorAll(".group1 .button"));
buttons.forEach((el) => {
el.addEventListener("click", function (e) {
buttons.forEach((button) => button.classList.toggle("marked", button.querySelector("input").checked));
});
});
/* Hide the radio button */
.button input { display: none; }
/* make it green when selected */
.button.marked {
background: green
}
/* make the label look more like a button */
.button {
font: caption;
font-size: smaller;
padding: 2px 6px;
border: 1px solid #999;
border-radius: 1px;
background: white;
}
.button:hover {
border-color: ThreeDDarkShadow;
}
<div class="group1">
<label class="button"><input type="radio" name="pt_toggle">Incorrect</label>
<label class="button"><input type="radio" name="pt_toggle">Partially correct</label>
<label class="button"><input type="radio" name="pt_toggle">Correct</label>
</div>
Upvotes: 1
Reputation: 1532
You're running into a feature of the for..in
loop syntax. When you perform:
for (el in btns) {
if (btns[el] !== self) {
btns[el].classList.remove('marked');
}
}
every property in the btns
object will be iterated over, including the length
property which you won't be able to remove a class name from btns["length"]
.
If you switch to using a normal for
loop using btns.length
as the limit, you won't iterate over the non-button element properties in the object:
for (var el = 0; el < btns.length; el++) {
if (btns[el] !== self) {
btns[el].classList.remove('marked');
}
}
Upvotes: 1
Reputation: 552
Adding "marked" class on click to your button:
Your html:
<button onclick="Label(event)" class="pt_toggle">Incorrect</button>
Your js:
function Label(event) {
event.target.classList.add("marked");
}
Upvotes: 0