Home > Software design >  Removing 'checked' attribute does not change its display in the browser
Removing 'checked' attribute does not change its display in the browser

Time:10-11

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).

enter image description here

 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>

  • Related