I have a button group where each button should light up when it's "checked". It half works which is to say it doesn't work (only one button should be active at any time).
let stanceBar = ["long", "short", "out", "none"]
.map(
(stanceElement) =>
`
<input
type="radio"
id="${stanceElement}"
autoComplete="off"
readOnly
/>
<label
for="${stanceElement}"
>
${stanceElement}
</label>
`
)
.join("")
<<This is inserted into a div>>
listeners:
document.getElementById(stance).addEventListener("click", (e) => {
let target = e.target as HTMLInputElement
s.stance = target.id
setState['s](s) // Run my setter script to kick off the for loop below
})
})
part of "setter" script
for (const stance of ["long", "short", "out", "none"]) {
const element = document.getElementById(stance)
// console.log(s.stance, "=", stance, s.stance === stance)
if (s.stance === stance) {
console.log("setting", stance, "button to", s.stance === stance)
element.setAttribute("checked", "")
} else {
console.log("setting", stance, "button to", s.stance === stance)
element.removeAttribute("checked")
}
}
When I click the buttons, I do get the button I click to activate, but the other buttons that were previously active don't deactivate. I have debugged line by line and the removeAttribute statement is apparently running. Just nothing happens. If I run the same statement from the browser console it works fine.
let stanceBar = ["long", "short", "out", "none"]
.map(
(stanceElement) =>
`
<input
type="radio"
id="${stanceElement}"
autoComplete="off"
readOnly
/>
<label
for="${stanceElement}"
>
${stanceElement}
</label>
`
)
.join("")
document.getElementById('stanceBar').innerHTML = stanceBar
;
let s = {
stance: 'none'
};
["long", "short", "out", "none"].forEach((stance) => {
document.getElementById(stance).addEventListener("click", (e) => {
let target = e.target
s.stance = target.id
setter() // setState["s"](s)
})
})
function setter() {
for (const stance of ["long", "short", "out", "none"]) {
const element = document.getElementById(stance)
if (s.stance === stance) {
element.setAttribute("checked", "")
} else {
element.removeAttribute("checked")
}
}
}
<div id='stanceBar'></div>
CodePudding user response:
Adding a name attribute when mapping the radio buttons name="stance"
it basically acts as container & everytime you change the radio element it is overwritten as it can only have one value at a time
let stanceBar = ["long", "short", "out", "none"]
.map(
(stanceElement) =>
`
<input
type="radio"
id="${stanceElement}"
autoComplete="off"
name="stance"
readOnly
/>
<label
for="${stanceElement}"
>
${stanceElement}
</label>
`
)
.join("")
document.getElementById('stanceBar').innerHTML = stanceBar
;
let s = {
stance: 'none'
};
["long", "short", "out", "none"].forEach((stance) => {
document.getElementById(stance).addEventListener("click", (e) => {
let target = e.target
s.stance = target.id
setter() // setState["s"](s)
})
})
function setter() {
for (const stance of ["long", "short", "out", "none"]) {
const element = document.getElementById(stance)
if (s.stance === stance) {
element.setAttribute("checked", "")
} else {
element.removeAttribute("checked")
}
}
}
<div id='stanceBar'></div>
CodePudding user response:
If you supply your radio buttons with a shared name
attribute (radio button group), the selection will only be applied to one at a time.
const defaultOptions = ['long', 'short', 'out', 'none'];
const renderStanceBar = (name, options = defaultOptions) =>
options.map(
(option) => `
<input
type="radio"
name="${name}"
id="${option}"
autoComplete="off"
/>
<label
for="${option}"
>
${option}
</label>
`).join('');
document.querySelectorAll('.stance-bar')
.forEach(stanceBar =>
stanceBar.innerHTML = renderStanceBar('stance'));
:root {
--background-color: #212529;
--primary-color: #0D6EFD;
--secondary-color: #FFF;
}
* { margin: 0; user-select: none; }
*, *::before, *::after { box-sizing: border-box; }
html, body { width: 100%; height: 100%; }
body { display: flex; justify-content: center; align-items: center; background: var(--background-color); font-family: Arial; }
.stance-bar {
display: flex;
border: thin solid var(--primary-color);
border-radius: 0.5rem;
color: var(--primary-color);
}
.stance-bar label {
display: flex;
padding: 0.667rem;
cursor: pointer;
position: relative;
}
.stance-bar label {
border-right: thin solid var(--primary-color);
}
.stance-bar label:first-of-type {
border-top-left-radius: 0.4rem;
border-bottom-left-radius: 0.4rem;
}
.stance-bar label:last-of-type {
border-right: none;
border-top-right-radius: 0.4rem;
border-bottom-right-radius: 0.4rem;
}
.stance-bar input[type="radio"]:checked label {
background: var(--primary-color);
color: var(--secondary-color);
}
.stance-bar input[type="radio"] {
opacity: 0;
position: absolute;
};
<div ></div>
CodePudding user response:
Your setter()
function is redundant and unnecessary. Browsers have built-in ability to ensure that only one radio in a group (defined by use of the name
attribute) is checked at a time.
The only time you would need to worry about checking them using the code is if you were starting off with none checked and wanted to check a specific one, but that doesn't seem to be the case here.
Even so, the way to check a radio that has already been rendered is to set its .checked
property, not to change its attributes. And you would only need to check one of them in that case, not iterate through all of them.
Simplified, working code:
let stanceBar = ["long", "short", "out", "none"]
.map(
(stanceElement) =>
`
<input
type="radio"
name="stance"
id="${stanceElement}"
autoComplete="off"
readOnly
/>
<label
for="${stanceElement}"
>
${stanceElement}
</label>
`
)
.join("")
document.getElementById('stanceBar').innerHTML = stanceBar;
let s = {
stance: 'none'
};
["long", "short", "out", "none"].forEach((stance) => {
document.getElementById(stance).addEventListener("click", (e) => {
let target = e.target
s.stance = target.id;
console.log(s.stance);
})
});
<div id='stanceBar'></div>