Jake Pike
Jake Pike

Reputation: 11

Validating Multiple Radio Button Groups in Vanilla JS

I am trying to validate multiple groups of radio buttons with pureJS. Basically my client has a group of around 50 questions, and each one has 4 radio buttons that can be used to pick 1 of 4 answers.

They do not want to use jQuery, but pureJS, I have gotten the following to work when there is just one question, but not when there is multiples, any help would be appreciated.

document.getElementById("submit_btn").addEventListener("click", function(event){
    var all_answered = true;
    var inputRadios = document.querySelectorAll("input[type=radio]")
    for(var i = 0; i < inputRadios.length; i++) {
        var name = inputRadios[i].getAttribute("name");
        if (document.getElementsByName(name)[i].checked) {
            return true;
            var all_answered = true;
        } else {
            var all_answered = false;
        }
    }
    if (!all_answered) {
        alert("Some questiones were not answered. Please check all questions and select an option.");
        event.preventDefault();
    }       
});

The questions are all laid out like this -

<div class="each-question">
<div class="unanswered-question">
    <div class="question-text">
        <div class="number">33</div>
        <div class="text">
                <p>Troubleshoot technology issues.</p>
        </div>
    </div>
    <div class="options" id="ans_285">
        <div class="radio-button">
            <input type="radio" value="3" id="ans33op1" name="ans_285">
            <label for="ans33op1" class="radio-label">Very Interested</label>
        </div>
        <div class="radio-button">
            <input type="radio" value="2" id="ans33op2" name="ans_285">
            <label for="ans33op2" class="radio-label">Interested</label>
        </div>
        <div class="radio-button">
            <input type="radio" value="1" id="ans33op3" name="ans_285" class="custom">
            <label for="ans33op3" class="radio-label"> Slightly Interested</label>
        </div>
        <div class="radio-button">
            <input type="radio" value="0" id="ans33op4" name="ans_285">
            <label for="ans33op4" class="radio-label"> Not Interested</label>
        </div>
    </div>
</div>

This is the original jQuery used by the client which now has to be in pureJS

    jQuery(document).ready(function () {
    jQuery("#question_list").submit(function () {
        var all_answered = true;
        jQuery("input:radio").each(function () {
            var name = jQuery(this).attr("name");
            if (jQuery("input:radio[name=" + name + "]:checked").length == 0) {
                all_answered = false;
            }
        });
        if (!all_answered) {
            alert("Some questiones were not answered. Please check all questions and select an option.");
            return false;
        }
    });
});

Upvotes: 1

Views: 1286

Answers (3)

Angel Politis
Angel Politis

Reputation: 11323

Everything inside your for clause makes absolutely no sense. Here's why:

  • Since you already have inputRadios, there is no point and getting their name and then using that to get the elements by name, because you already have them.

  • Since you use return true, the function exits and everything beyond that is disregarded.

  • Instead of updating the existent all_answered variable you create a new, local one that will be lost once the current iteration ends.

What you should do:

  1. Instead of getting all inputs, get all answers, the div.options elements that contain the inputs for each answer, and iterate over those.

  2. Then, use the id of the answer, because it's the same as the name of the inputs, to get the related inputs.

  3. Use some to ensure that there is a checked input among the group. Then, check whether there isn't and stop the loop. You've found an unanswered question.

Snippet:

document.getElementById("submit_btn").addEventListener("click", function(event) {
  var
    /* Create a flag set by default to true. */
    all_answered = true,

    /* Get all answers. */
    answers = document.querySelectorAll(".options[id ^= ans_]");

  /* Iterate over every answer. */
  for (var i = 0; i < answers.length; i++) {
    var
      /* Use the id of the answer to get its radiobuttons. */
      radios = document.querySelectorAll("[name = " + answers[i].id + "]"),

      /* Save whether there is a checked input for the answer. */
      hasChecked = [].some.call(radios, function(radio) {
        return radio.checked;
      });

    /* Check whether there is a checked input for the answer or not. */
    if (!hasChecked) {
      /* Set the all_answered flag to false and break the loop. */
      all_answered = false;
      break;
    }
  }

  /* Check whether not all answers have been answered. */
  if (!all_answered) {
    console.log("Some questions were not answered...");
  } else {
    console.log("All questions are answered!");
  }
});
.question { display: inline-block }
<div class="question">
  <div class="text">
    <p>Troubleshoot technology issues.</p>
  </div>
  <div class="options" id="ans_285">
    <div class="radio-button">
      <input type="radio" value="3" id="ans33op1" name="ans_285">
      <label for="ans33op1" class="radio-label">Very Interested</label>
    </div>
    <div class="radio-button">
      <input type="radio" value="2" id="ans33op2" name="ans_285">
      <label for="ans33op2" class="radio-label">Interested</label>
    </div>
    <div class="radio-button">
      <input type="radio" value="1" id="ans33op3" name="ans_285" class="custom">
      <label for="ans33op3" class="radio-label">Slightly Interested</label>
    </div>
    <div class="radio-button">
      <input type="radio" value="0" id="ans33op4" name="ans_285">
      <label for="ans33op4" class="radio-label">Not Interested</label>
    </div>
  </div>
</div>
<div class="question">
  <div class="text">
    <p>Troubleshoot technology issues.</p>
  </div>
  <div class="options" id="ans_286">
    <div class="radio-button">
      <input type="radio" value="3" id="ans34op1" name="ans_286">
      <label for="ans34op1" class="radio-label">Very Interested</label>
    </div>
    <div class="radio-button">
      <input type="radio" value="2" id="ans34op2" name="ans_286">
      <label for="ans34op2" class="radio-label">Interested</label>
    </div>
    <div class="radio-button">
      <input type="radio" value="1" id="ans34op3" name="ans_286" class="custom">
      <label for="ans34op3" class="radio-label">Slightly Interested</label>
    </div>
    <div class="radio-button">
      <input type="radio" value="0" id="ans34op4" name="ans_286">
      <label for="ans34op4" class="radio-label">Not Interested</label>
    </div>
  </div>
</div>
<div class="question">
  <div class="text">
    <p>Troubleshoot technology issues.</p>
  </div>
  <div class="options" id="ans_287">
    <div class="radio-button">
      <input type="radio" value="3" id="ans35op1" name="ans_287">
      <label for="ans35op1" class="radio-label">Very Interested</label>
    </div>
    <div class="radio-button">
      <input type="radio" value="2" id="ans35op2" name="ans_287">
      <label for="ans35op2" class="radio-label">Interested</label>
    </div>
    <div class="radio-button">
      <input type="radio" value="1" id="ans35op3" name="ans_287" class="custom">
      <label for="ans35op3" class="radio-label">Slightly Interested</label>
    </div>
    <div class="radio-button">
      <input type="radio" value="0" id="ans35op4" name="ans_287">
      <label for="ans35op4" class="radio-label">Not Interested</label>
    </div>
  </div>
</div>
<button id="submit_btn">Submit</button>

Upvotes: 1

Tigger
Tigger

Reputation: 9130

The following is a simplified version, but there should be enough code to get you heading in the right direction.

var answer = [];
function checkAnswerCount(e) {
    // for the answer ids
    var i = 0, max = answer.length;
    // for the radios
    var j = 0; rMax = 0;
    // And a few extras
    var tmp = null, answerCount = 0;
    for(;i<max;i++) {
        tmp = document.getElementsByName(answer[i]);
        rMax = tmp.length;
        for(j=0;j<rMax;j++) {
            if (tmp[j].checked) {
                answerCount++;
                break;
            }
        }
    }
    if (answerCount == answer.length) {
        console.log("All questions have an answer, submit the form");
    } else {
        console.log("You need to answer all the questions");
    }
}

window.onload = function() {
    // each answer block is surrounded by the "options" class, 
    // so we use that to collect the ids of the raido groups
    var a = document.querySelectorAll(".options");
    var i = 0, max = a.length;
    for(;i<max;i++) {
        answer.push(a[i].id);
    }
    // And we want to check if all the answers have been answered
    // when the user tries to submit...
    var s = document.getElementById("submitAnswers");
    if (s) {
        s.addEventListener("click",checkAnswerCount,false);
    }
}
<p>Question 1.</p>
<div class="options" id="ans_1">
    <label><input type="radio" name="ans_1" value="a1_1" /> Answer 1, op1</label>
    <label><input type="radio" name="ans_1" value="a1_2" /> Answer 1, op2</label>
</div>
<p>Question 2.</p>
<div class="options" id="ans_2">
    <label><input type="radio" name="ans_2" value="a2_1" /> Answer 2, op1</label>
    <label><input type="radio" name="ans_2" value="a2_2" /> Answer 2, op2</label>
</div>
<p>Question 3.</p>
<div class="options" id="ans_3">
    <label><input type="radio" name="ans_3" value="a3_1" /> Answer 3, op1</label>
    <label><input type="radio" name="ans_3" value="a3_2" /> Answer 3, op2</label>
</div>
<button id="submitAnswers">Submit / check</button>

Upvotes: 0

samanime
samanime

Reputation: 26615

Not sure if it's just an issue with the copy, but you have a return true in your for loop which will cause the entire function to simply return true if just one is answered. Removing that would help.

Ignoring that though, your solution is a bit unwieldy, as it'll loop through every single input on the page individually and will mark it false if not every radio button is unchecked.

Here is a different approach. Basically, get all of the radio buttons, then group them into arrays by question. Then, loop through each of those arrays and check that within each group, at least one is answered.

document.querySelector('form').addEventListener('submit', e => {
  // Get all radio buttons, convert to an array.
  const radios = Array.prototype.slice.call(document.querySelectorAll('input[type=radio]'));
  
  // Reduce to get an array of radio button sets
  const questions = Object.values(radios.reduce((result, el) => 
    Object.assign(result, { [el.name]: (result[el.name] || []).concat(el) }), {}));
  
  // Loop through each question, looking for any that aren't answered.
  const hasUnanswered = questions.some(question => !question.some(el => el.checked));
  
  if (hasUnanswered) {
    console.log('Some unanswered');
  } else {
    console.log('All set');
  }
  
  e.preventDefault(); // just for demo purposes... normally, just put this in the hasUnanswered part
});
<form action="#">
 <div>
  <label><input type="radio" name="a" /> A</label>
  <label><input type="radio" name="a" /> B</label>
  <label><input type="radio" name="a" /> C</label>
  <label><input type="radio" name="a" /> D</label>
 </div>
 <div>
  <label><input type="radio" name="b" /> A</label>
  <label><input type="radio" name="b" /> B</label>
  <label><input type="radio" name="b" /> C</label>
  <label><input type="radio" name="b" /> D</label>
 </div>
 <button type="submit">Submit</button>
</form>

First up, I get all of the radio buttons that have a type of radio (that way if there are others, I won't bother with them).

Then, I turn the NodeList returned by querySelectorAll() into an Array by using Array.prototype.slice.call() and giving it my NodeList.

After that, I use reduce() to group the questions together. I make it an array with the element's name as the key (since I know that's how they have to be grouped). After the reduce, since I don't really care about it being an object with the key, I use Object.values() just to get the arrays.

After that, I use some() over the set of questions. If that returns true, it'll mean I have at least one unanswered question.

Finally, inside that some(), I do another over the individual radio buttons of the question. For this, I want to return !some() because if there isn't at least one that is answered, then I should return true overall (that I have at least one question not answered).

The above is a bit verbose. This one is a bit more concise and is what I would likely use in my own code:

document.querySelector('form').addEventListener('submit', e => {
  if (Object.values(
    Array.prototype.reduce.call(
      document.querySelectorAll('input[type=radio]'),
      (result, el) => 
        Object.assign(result, { [el.name]: (result[el.name] || []).concat(el) }), 
      {}
    )
  ).some(q => !q.some(el => el.checked))) {
    e.preventDefault();
    console.log('Some questions not answered');
  }
});
<form action="#">
 <div>
  <label><input type="radio" name="a" /> A</label>
  <label><input type="radio" name="a" /> B</label>
  <label><input type="radio" name="a" /> C</label>
  <label><input type="radio" name="a" /> D</label>
 </div>
 <div>
  <label><input type="radio" name="b" /> A</label>
  <label><input type="radio" name="b" /> B</label>
  <label><input type="radio" name="b" /> C</label>
  <label><input type="radio" name="b" /> D</label>
 </div>
 <button type="submit">Submit</button>
</form>

Upvotes: 3

Related Questions