The problem is that you are creating a closure around your looping variable, i
.
What this means is that you use i
in the event handler function that you are assigning out to each of the checkboxes and by the time a user comes along and clicks on one of them, the loop is done and its final value is checked.length
or 4
in this case. Since there is no element within the checkbox node list with an index of 4
(the highest index is 3
, so you are referring to an element that doesn't exist, hence your error message).
There are several solutions here. The simplest is to change the scope of your looping variable so that instead of all the event callbacks sharing the same i
value, which is 4
by the time any of those callbacks run, you give your i
variable its own scope for each loop iteration, meaning that each event handler will get its own value instead of sharing it. This can be done simply by declaring the loop variable with let
:
<form action="/question" method="post" id="myForm">
<label for="sessionName">Session Name</label><input type="text" name="sessionName">
<div class="question">
<label for="question">Question: </label><input type="textarea" name="question">
<ul>
<li><label for="answer">Answer 1: </label><input type="textarea" name="answer"><input type="hidden" name="checked" value="false"><input type="checkbox" name="tick"></li>
<li><label for="answer">Answer 2: </label><input type="textarea" name="answer"><input type="hidden" name="checked" value="false"><input type="checkbox" name="tick"></li>
<li><label for="answer">Answer 3: </label><input type="textarea" name="answer"><input type="hidden" name="checked" value="false"><input type="checkbox" name="tick"></li>
<li><label for="answer">Answer 4: </label><input type="textarea" name="answer"><input type="hidden" name="checked" value="false"><input type="checkbox" name="tick"></li>
</ul>
</div>
<input type="submit" name="" value="Create Session">
</form>
<script>
let checked = document.getElementsByName("checked");
let tick = document.getElementsByName("tick");
for (let i = 0; i < checked.length; i++) {
tick[i].addEventListener("change", () => {
console.log(document.getElementsByName("checked")[i].value)
})
}
</script>
Now another way to solve this issue is to do away with the loop counter entirely and use much more modern code to solve the problem. All modern browsers support the Array.forEach()
method on the node lists that are returned from DOM queries and forEach()
does the iterations on its own without you needing to declare and manage a counting variable. If you don't have the variable, then you don't set up the closure, which is the cause of the problem in the first place.
<form action="/question" method="post" id="myForm">
<label for="sessionName">Session Name</label><input type="text" name="sessionName">
<div class="question">
<label for="question">Question: </label><input type="textarea" name="question">
<ul>
<li><label for="answer">Answer 1: </label><input type="textarea" name="answer"><input type="hidden" name="checked" value="false"><input type="checkbox" name="tick"></li>
<li><label for="answer">Answer 2: </label><input type="textarea" name="answer"><input type="hidden" name="checked" value="false"><input type="checkbox" name="tick"></li>
<li><label for="answer">Answer 3: </label><input type="textarea" name="answer"><input type="hidden" name="checked" value="false"><input type="checkbox" name="tick"></li>
<li><label for="answer">Answer 4: </label><input type="textarea" name="answer"><input type="hidden" name="checked" value="false"><input type="checkbox" name="tick"></li>
</ul>
</div>
<input type="submit" name="" value="Create Session">
</form>
<script>
let checked = document.getElementsByName("checked");
let tick = document.getElementsByName("tick");
tick.forEach(function(chk) {
chk.addEventListener("change", () => {
console.log(chk.previousSibling.value)
});
});
</script>
Now finally, you've got some very old code that you are using that you really shouldn't be. getElementsByName()
returns a "live" node list that can cause serious performance issues. What you should be using is .querySelectorAll()
, which allows for a CSS selector to be passed in.
Additionally, you've written input type="textarea"
, but there is no input
with a type of textarea
, so you are getting the default input
type, which is text
. A textarea is written like this: <textarea></textarea>
.
Also, I'm not sure what value the hidden form fields play in your scenario. What is most common is for checkboxes to have a value, which you don't have currently.
See comments inline below:
<form action="/question" method="post" id="myForm">
<label for="sessionName">Session Name</label><input type="text" name="sessionName">
<div class="question">
<label for="question">Question: </label><input type="textarea" name="question">
<ul>
<li><label for="answer">Answer 1: </label><input type="text" name="answer1"><input type="checkbox" name="tick"></li>
<li><label for="answer">Answer 2: </label><input type="text" name="answer2"><input type="checkbox" name="tick"></li>
<li><label for="answer">Answer 3: </label><input type="text" name="answer3"><input type="checkbox" name="tick"></li>
<li><label for="answer">Answer 4: </label><input type="text" name="answer4"><input type="checkbox" name="tick"></li>
</ul>
</div>
<input type="submit" value="Create Session">
</form>
<script>
// Get all the checkboxes into a collection
let checkboxes = document.querySelectorAll("input[type='checkbox']");
// Loop over the checkboxes the modern way
checkboxes.forEach(function(cb, i) {
// Use the click event since change only fires when the field gets a new value
cb.addEventListener("click", () => {
// Set the value of the checkbox to the value of the textbox that comes
// right before it.
cb.value = cb.previousSibling.value;
console.log(cb.value); // Log the checkbox value
});
});
</script>