Tzar
Tzar

Reputation: 4789

Disabling fieldsets does not work as expected

In my demo form, fieldsets are enabled sequentially on button selection. So checking a button in first fieldset enables the second, and so on.

If all checkboxes are unchecked in the first fieldset, the form should reset and then second to fourth fieldset should be disabled again. But this is not what happens. Second fieldset somehow remains enabled. What is the problem here?

document.addEventListener("DOMContentLoaded", _ => {
  const form = document.forms[0]

  // First, disable all fieldsets except the first

  function disableFieldsets() {
    const disabledFieldsets = form.querySelectorAll(
      "section:not(:first-of-type) fieldset"
    )

    for (let i = 0; i < disabledFieldsets.length; i++) {
      disabledFieldsets[i].disabled = true
    }
  }

  disableFieldsets()

  // Sequentially enable fieldsets on selection

  const sections = form.querySelectorAll("section")

  sections.forEach(section => {
    section.addEventListener("change", function() {
      let nextFieldset = this.nextElementSibling.querySelector("fieldset")

      if (nextFieldset) {
        nextFieldset.disabled = false
      }
    })
  })

  // Reset form and disable fieldsets after all inputs in the first fieldset are deselected

  const firstFieldsetButtons = form.querySelectorAll("[name=First]")
  let isChecked = false

  firstFieldsetButtons.forEach(firstFieldsetButton => {
    firstFieldsetButton.addEventListener("click", function(e) {
      if (this.checked) {
        isChecked = true
      } else {
        form.reset()
        disableFieldsets()
      }
    })
  })
})
<form method=post action=submit>    
  <section>
  <fieldset>
    <legend>First Fieldset</legend>
    <label><input type=checkbox name=First value=A>A</label>
    <label><input type=checkbox name=First value=B>B</label>
    <label><input type=checkbox name=First value=C>C</label>
  </fieldset>
  </section>
  
  <section>
  <fieldset>
    <legend>Second Fieldset</legend>
    <label><input type=radio name=Second value=1.1>1.1</label>
    <label><input type=radio name=Second value=1.2>1.2</label>
    <label><input type=radio name=Second value=1.3>1.3</label>
  </fieldset>
  </section>
  
  <section>
  <fieldset>
    <legend>Third Fieldset</legend>
    <label><input type=radio name=Third value=2.1>2.1</label>
    <label><input type=radio name=Third value=2.2>2.2</label>
    <label><input type=radio name=Third value=2.3>2.3</label>
  </fieldset>
  </section>
  
  <section>
  <fieldset>
    <legend>Fourth Fieldset</legend>
    <label><input type=radio name=Fourth value=3.1>3.1</label>
    <label><input type=radio name=Fourth value=3.2>3.2</label>
    <label><input type=radio name=Fourth value=3.3>3.3</label>
  </fieldset>
  </section>
  
  <input type=submit value=Submit>
</form>

Upvotes: 1

Views: 179

Answers (3)

KooiInc
KooiInc

Reputation: 122906

Just for fun, this refactoring prevents the problem @Nick Parsons described so well

const form = document.forms[0];
// one of the checkboxes checked?
const checkFirstFieldsetCheckboxes = () =>
  Array.from(document.querySelector("fieldset")
    .querySelectorAll("input[type=checkbox]"))
    .filter(v => v.checked).length > 0;
// disable all fieldsets except the first
const disableFieldsets = () => {
  form.reset();
  Array.from(form.querySelectorAll("fieldset")).slice(1)
    .forEach(fieldset => fieldset.disabled = true);
};
const enableNext = origin => {
  const nextFieldset = origin.closest("section")
    .nextElementSibling.querySelector("fieldset");
  if (nextFieldset) {
    nextFieldset.disabled = false;
  }
};
const fieldsetHandling = evt => {
  const origin = evt.target;
  return !origin.type 
    	? false 
      : origin.type === "radio" 
        	? enableNext(origin) 
          : !checkFirstFieldsetCheckboxes() 
            ? disableFieldsets() 
            : enableNext(origin);
};
// main
disableFieldsets();
// add handler (event delegation)
document.addEventListener("click", fieldsetHandling);
<form method=post action=submit>
  <section>
    <fieldset>
      <legend>First Fieldset</legend>
      <label><input type=checkbox name=First value=A>A</label>
      <label><input type=checkbox name=First value=B>B</label>
      <label><input type=checkbox name=First value=C>C</label>
    </fieldset>
  </section>

  <section>
    <fieldset>
      <legend>Second Fieldset</legend>
      <label><input type=radio name=Second value=1.1>1.1</label>
      <label><input type=radio name=Second value=1.2>1.2</label>
      <label><input type=radio name=Second value=1.3>1.3</label>
    </fieldset>
  </section>

  <section>
    <fieldset>
      <legend>Third Fieldset</legend>
      <label><input type=radio name=Third value=2.1>2.1</label>
      <label><input type=radio name=Third value=2.2>2.2</label>
      <label><input type=radio name=Third value=2.3>2.3</label>
    </fieldset>
  </section>

  <section>
    <fieldset>
      <legend>Fourth Fieldset</legend>
      <label><input type=radio name=Fourth value=3.1>3.1</label>
      <label><input type=radio name=Fourth value=3.2>3.2</label>
      <label><input type=radio name=Fourth value=3.3>3.3</label>
    </fieldset>
  </section>

  <input type=submit value=Submit>
</form>

Upvotes: 1

shrys
shrys

Reputation: 5940

In your sections' change event, instead of assigning nextFieldset as disabled you can assign !event.target.checked by adding event parameter to your function:

document.addEventListener("DOMContentLoaded", _ => {
  const form = document.forms[0]

  // First, disable all fieldsets except the first

  function disableFieldsets() {
    const disabledFieldsets = form.querySelectorAll(
      "section:not(:first-of-type) fieldset"
    )

    for (let i = 0; i < disabledFieldsets.length; i++) {
      disabledFieldsets[i].disabled = true
    }
  }

  disableFieldsets()

  // Sequentially enable fieldsets on selection

  const sections = form.querySelectorAll("section")

  sections.forEach(section => {
    section.addEventListener("change", function(event) {
      let nextFieldset = this.nextElementSibling.querySelector("fieldset")

      if (nextFieldset) {
        nextFieldset.disabled = !event.target.checked
      }
    })
  })

  // Reset form and disable fieldsets after all inputs in the first fieldset are deselected

  const firstFieldsetButtons = form.querySelectorAll("[name=First]")
  let isChecked = false

  firstFieldsetButtons.forEach(firstFieldsetButton => {
    firstFieldsetButton.addEventListener("click", function(e) {
      if (this.checked) {
        isChecked = true
      } else {
        form.reset()
        disableFieldsets()
      }
    })
  })
})
<form method=post action=submit>
  <section>
    <fieldset>
      <legend>First Fieldset</legend>
      <label><input type=checkbox name=First value=A>A</label>
      <label><input type=checkbox name=First value=B>B</label>
      <label><input type=checkbox name=First value=C>C</label>
    </fieldset>
  </section>

  <section>
    <fieldset>
      <legend>Second Fieldset</legend>
      <label><input type=radio name=Second value=1.1>1.1</label>
      <label><input type=radio name=Second value=1.2>1.2</label>
      <label><input type=radio name=Second value=1.3>1.3</label>
    </fieldset>
  </section>

  <section>
    <fieldset>
      <legend>Third Fieldset</legend>
      <label><input type=radio name=Third value=2.1>2.1</label>
      <label><input type=radio name=Third value=2.2>2.2</label>
      <label><input type=radio name=Third value=2.3>2.3</label>
    </fieldset>
  </section>

  <section>
    <fieldset>
      <legend>Fourth Fieldset</legend>
      <label><input type=radio name=Fourth value=3.1>3.1</label>
      <label><input type=radio name=Fourth value=3.2>3.2</label>
      <label><input type=radio name=Fourth value=3.3>3.3</label>
    </fieldset>
  </section>

  <input type=submit value=Submit>
</form>

Upvotes: 1

Nick Parsons
Nick Parsons

Reputation: 50684

You're enabling the second fieldset every time you're changing your first fieldset - even when you are unchecking a checkbox. Even though you are disabling your fieldsets after you "click" on a checkbox, your "change" event runs after this event, thus enabling the second fieldset. This could be fixed by using a flag to indicate whether or not the form has been reset:

document.addEventListener("DOMContentLoaded", _ => {
  const form = document.forms[0];

  // First, disable all fieldsets except the first

  function disableFieldsets() {
    const disabledFieldsets = form.querySelectorAll(
      "section:not(:first-of-type) fieldset"
    )

    for (let i = 0; i < disabledFieldsets.length; i++) {
      disabledFieldsets[i].disabled = true
    }
  }

  disableFieldsets()

  // Sequentially enable fieldsets on selection

  const sections = form.querySelectorAll("section")
  let reset = false;
  sections.forEach(section => {
    section.addEventListener("change", function() {
      let nextFieldset = this.nextElementSibling.querySelector("fieldset")
      if (nextFieldset && !reset) {
        nextFieldset.disabled = false;
      } else if(reset) {
        reset = false;
      }
    })
  })

  // Reset form and disable fieldsets after all inputs in the first fieldset are deselected

  const firstFieldsetButtons = form.querySelectorAll("[name=First]")
  let isChecked = false;

  firstFieldsetButtons.forEach(firstFieldsetButton => {
    firstFieldsetButton.addEventListener("click", function(e) {
      if (this.checked) {
        isChecked = true;
      } else {
        form.reset();
        disableFieldsets();
        reset = true;
      }
    })
  })
})
<form method=post action=submit>    
  <section>
  <fieldset>
    <legend>First Fieldset</legend>
    <label><input type=checkbox name=First value=A>A</label>
    <label><input type=checkbox name=First value=B>B</label>
    <label><input type=checkbox name=First value=C>C</label>
  </fieldset>
  </section>
  
  <section>
  <fieldset>
    <legend>Second Fieldset</legend>
    <label><input type=radio name=Second value=1.1>1.1</label>
    <label><input type=radio name=Second value=1.2>1.2</label>
    <label><input type=radio name=Second value=1.3>1.3</label>
  </fieldset>
  </section>
  
  <section>
  <fieldset>
    <legend>Third Fieldset</legend>
    <label><input type=radio name=Third value=2.1>2.1</label>
    <label><input type=radio name=Third value=2.2>2.2</label>
    <label><input type=radio name=Third value=2.3>2.3</label>
  </fieldset>
  </section>
  
  <section>
  <fieldset>
    <legend>Fourth Fieldset</legend>
    <label><input type=radio name=Fourth value=3.1>3.1</label>
    <label><input type=radio name=Fourth value=3.2>3.2</label>
    <label><input type=radio name=Fourth value=3.3>3.3</label>
  </fieldset>
  </section>
  
  <input type=submit value=Submit>
</form>

Upvotes: 1

Related Questions