简体   繁体   中英

Changing Drop Down Boxes To Radio Buttons For Quiz

Code newbie here!

I've managed to put together a form which tallies up a user's score based on their selections. And it works!

Here's my code:

 function finalScore(round) { var correct = 0; var selectValue; var questions = document.getElementsByClassName("question"); var numOfQuestions = questions.length; for (var i = 0; i < questions.length; i++) { //begin for loop //get the value of the select element selectValue = questions[i].options[questions[i].selectedIndex].value; //if the value equals right if (selectValue === "one") { //begin if then //increment the correct variable correct = correct + 1; } else if (selectValue === "two") { correct = correct + 2; } else if (selectValue === "three") { correct = correct + 3; } else if (selectValue === "four") { correct = correct + 4; } } //end for loop alert(correct); if (round === false) { //get the percentage of correct answers(not rounded) document.getElementById("scoreDisplay").innerHTML = correct; } else { //display the rounded value document.getElementById("scoreDisplay").innerHTML = correct; } //end if then else } //end function
 How Old Are You? <select class="question" id="1howold"> <option value="three">19 - 26</option> <option value="four">27 - 36</option> <option value="two">37 - 40</option> <option value="one">41+</option> </select><br /><br /> What's Your Relationship Status? <select class="question" id="2relationshipstatus"> <option value="four">Single and excited to see what's out there.</option> <option value="three">Recently single and emotionally destroyed.</option> <option value="two">Got some casual things on the go, not looking for anything too serious.</option> <option value="one">In a committed relationship and only taking this quiz for the lols.</option> </select><br /><br /> Where Is Your Location? <select class="question" id="3location"> <option value="four">Location Independent</option> <option value="three">London/The UK</option> <option value="two">Europe</option> <option value="one">Elsewhere</option> </select><br /><br /> <button type="button" onclick="finalScore(true)">Submit</button> <div id="scoreDisplay">score goes here</div>

See the JSfiddle here: https://jsfiddle.net/tm3o5g0k/

However, I've realised I need to switch these to radio buttons due to the length of some of the options. I've tried so many methods and I keep making a mess? Surely there's an easy way to do this that I'm overlookng?

Thanks: :)

Based on the code – and problem description – in your question, and comments to the question, I've developed the following solution. The assumptions I've made are:

  1. you want to convert the <option> elements to <input type="radio" /> elements, and
  2. you want to determine a total-score based on the value assigned to each of the <input> elements.

With that in mind I've adjusted the HTML to better fit the identified assumptions and their requirements:

  1. the value of each option element is the numeric equivalent of the score, in order that they can be more easily used in calculations, therefore value="four" has become value="1" ,
  2. the textNode prior to each of the <select> elements has been converted to a <legend> in order that each 'group' of <input> elements can be visually recognised as a group,
  3. I've removed the number – which seems largely irrelevant – from the id of each <select> , while under HTML 5 it's valid to have an id starting with a numeral it's historically problematic to select such an id via CSS as the number is required to be explicitly escaped,
  4. I've also assigned a name property to the <select> elements, this is because the name is required to correctly pass values to the server in the event that the results should be stored in a database, and also for <input type="radio"> elements to act as a mutually-exclusive group (so only one <input> can be selected at a time from the related elements) a name property must be assigned to identify that relationship. As such it made sense to add the name to the <select> prior to manipulation rather than relying on an auto-generated name , which would, or could, lead to server-side storage problems.

With all that said, one approach to solving your problems is below:

 // declaring the functions as constants, using 'const'; // variables declared with const cannot be reassigned, // though if the variable is an Object or Array the // properties, or contents, of that Object or Array can // be changed/updated later: const selectToRadio = (selectSelector) => { // within the function body we retrieve all relevant elements // using document.querySelectorAll(), passing the CSS selector // supplied to the function; then that iterable static // NodeList is converted to an Array using the spread syntax: const selects = [...document.querySelectorAll(selectSelector)], // we create basic elements: input = document.createElement('input'), label = document.createElement('label'), span = document.createElement('span'); // set the type of the created <input> element: input.type = 'radio'; // here we take the Array of Nodes and iterate over that // Array using Array.prototype.forEach() with an Arrow // function: selects.forEach( // 'sel' is a reference to the current <select> element // from the Array of <select> elements over which we're // iterating: (sel) => { // here we assign the property-value of the 'name' property // of the <select> element as the 'groupName' of the <inpu> // elements we're creating: let groupName = sel.name, // here we retrieve all descendant <option> elements // from the current <select> element, using // Element.querySelectorAll() and, again, creating // an Array - with the spread operator - of the // resulting iterable NodeList: options = [...sel.querySelectorAll('option')], // here we create a documentFragment() so all the // created elements can be appended at the same time: fragment = document.createDocumentFragment(); // here we again use Array.prototype.forEach() to iterate over // the Array of <option> elements: options.forEach( // 'opt' (these variables can be named whatever you like, so // so long as it's a valid variable-name within the naming // rules of JavaScript) is a reference to the current <option> // of the Array of <option> elements over which we're // iterating: (opt) => { // here we create clones of the various elements we // created earlier (rather than creating the same // type of element multiple times in each iteration // of the loop): let inputClone = input.cloneNode(), labelClone = label.cloneNode(), spanClone = span.cloneNode(); // here we set the 'name' and 'value' properties of the // cloned <input>: inputClone.name = groupName; inputClone.value = opt.value; // we use the Element.classList API to add the 'answer' // class-name to the created element: inputClone.classList.add('answer'); // we set the textContent of the created <span> to be // equal to the text from the current <option>: spanClone.textContent = opt.text; // we use the parentNode.append() method to append the // cloned <input> and the cloned <span> elements to // the cloned <label>: labelClone.append(inputClone, spanClone); // then we append the cloned <label> to the documentFragment: fragment.append(labelClone); }); // we use parentNode.insertBefore() method to insert the // documentFragment into the DOM before the current <select> // element: sel.parentNode.insertBefore(fragment, sel); // and then use childNode.remove() method to remove // the current <select> element - and its descendants - // from the DOM: sel.remove(); }); }, score = () => { // here we use the same approach as above to create an Array of // element-nodes that have the 'answer' class-name and are also checked: const answers = [...document.querySelectorAll('.answer:checked')], // here we take the answers Array, and then call: result = answers // Array.prototype.map() to create a new Array based on the // contents of the current Array; here that new Array is // an Array of Numbers: .map( // we again use Arrow syntax for the anonymous function, // here 'el' is a reference to the current checked <input> // element; the function body (the single line following // the '=>' characters) simply returns the value of the // checked <option> parsed as a Number in base 10. Any // attribute-value (from which the value property is // derived is returned as a String from JavaScript, which // is why we're parsing it to a Number): (el) => parseInt(el.value, 10)) // we then pass the Array of Numbers to the // Array.prototype.reduce() function in order to reduce // the chained Array to a single result; again we use an // Arrow function syntax; here the body of the function // is taking the previously-held (or default) value ('a') and // adding it to the value of the current Array-element (b); // the '0' at the end of the function is the default-value with // which the reduce function starts: .reduce((a, b) => a + b, 0); // we then find the element that will show the result, and update its // text-content to be equal to the resulting score: document.querySelector('#scoreDisplay').textContent = result; // here we return the result to the calling context in the event you want // to also store or otherwise manipulate the derived score: return result; }; // here we call the selectToRadio() function, passing in an // appropriate CSS selector: selectToRadio('select'); // here we retrieve the first or only <button> element in the document: document.querySelector('button') // and use EventTarget.addEventListener() to bind the named score() // function (note the deliberate lack of parentheses) as the // event-handler for the 'click' event: .addEventListener('click', score);
 fieldset { margin: 0.5em 0; } legend { padding: 0 0.6em; } label { display: flex; gap: 0 0.6em; margin: 0.3em 0; } input+span { border-radius: 0.6em; } input:checked+span { color: limegreen; }
 <form action=""> <fieldset> <legend>How old are you?</legend> <select class="question" id="howold" name="ageRange"> <option value="3">19 - 26</option> <option value="4">27 - 36</option> <option value="2">37 - 40</option> <option value="1">41+</option> </select> </fieldset> <fieldset> <legend>What's your relationship status?</legend> <select class="question" id="relationshipstatus" name="relationshipStatus"> <option value="4">Single and excited to see what's out there.</option> <option value="3">Recently single and emotionally destroyed.</option> <option value="2">Got some casual things on the go, not looking for anything too serious.</option> <option value="1">In a committed relationship and only taking this quiz for the lols.</option> </select> </fieldset> <fieldset> <legend>What is your location?</legend> <select class="question" id="location" name="location"> <option value="4">Location Independent</option> <option value="3">London/The UK</option> <option value="2">Europe</option> <option value="1">Elsewhere</option> </select> </fieldset> <button type="button">Submit</button> </form> <div id="scoreDisplay"></div>

JS Fiddle demo .

In the above code we used the following approach:

[...document.querySelectorAll('CSS-selector')]

to efficiently create Arrays from the NodeList result of the document.querySelectorAll() , this is unnecessary in the selectToRadio() function as we only use Array.prototype.forEach() which could be replaced with NodeList.prototype.forEach() with no changes to the syntax.

However in the composition of the functions I hadn't entirely decided how I was going to implement the code in advance, so I converted the NodeList s to Arrays simply in order that I might be able to use other Array methods – such as Array.prototype.map() , Array.prototype.filter() – as I did in the score() function.

I don't believe it will increase efficiency by much, if at all, but if you'd rather not waste processing time converting a NodeList into an Array then simply convert the following lines:

const selects = [...document.querySelectorAll(selectSelector)],

and:

options = [...sel.querySelectorAll('option')],

into:

const selects = document.querySelectorAll(selectSelector),

and:

options = sel.querySelectorAll('option'),

other than that no changes need to be made and when, for example:

selects.forEach(
  (sel) => ....

is called the JavaScript engine will use NodeList.prototype.forEach() automatically in place of Array.prototype.forEach() (proof of concept: JS Fiddle demo ).

In the score() function, however, I do use the other methods of the Array.prototype which are not translated to, or available on, NodeLists so that must remain an Array.

References:

Bibliography:

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM