I have a table where a user can (un)check checkboxes among other features in each row.
I faced the problem that I don't receive unchecked checkboxes to my backend. This led to a bigger issue since every row should have synchronized fields of course.
For example:
- Row 1 - checked
- Row 2 - unchecked
- Row 3 - checked
My backend receives now an array like this:
clickBoxReduce
[true, true]
Backend-Function like this
@PostMapping()
public String selectPost(
@RequestParam(required = false, name = "clickboxReduce") String[] clickboxReduce,
Model model) {
System.out.println("clickBoxReduce");
System.out.println(Arrays.toString(clickboxReduce));
return "fillDetails";
}
Obviously I cannot see which row exactly is unchecked.
My solution was to give each input tag a value no
and yes
and update this value with every checkbox click.
function handleClickOnReduceStart (element) {
if (element.value === 'yes') {
element.value = 'no'
} else if (element.value === 'no') {
element.value = 'yes'
}
}
And set all checkboxes on checked before submitting the form with the function interceptSubmit
blow.
With that I get all checkbox values in the correct order.
It is visible for the user that the checkboxes change again if there is a slight delay what is not so good.
Does anyone have a better idea?
Here the table:
<!--- start table -->
<div class="container">
<br>
<h4 style="text-align: center">Please specify below</h4>
<br>
<form method="POST" id="postDetails" onsubmit="return interceptSubmit()">
<table id="buyTable" class="table table-hover">
<thead>
<tr>
<th>Type</th>
<th>Brand</th>
<th>Buy-Price</th>
<th>Bottom</th>
<th>Top</th>
<th>Stop</th>
<th>Reduce</th>
<th>Start</th>
</tr>
</thead>
<tbody>
<tr th:each="car : ${cars}">
<td>
<select th:id="${car.getBrand()}" title="selectBuyOrSell" onchange="updateBuyTable(this)">>
<option value="buy">Buy</option>
<option value="sell">Sell</option>
</select>
</td>
<td>
<a th:text="${car.getBrand()}"></a><input type="hidden" name="brandBuy"
th:value="${car.getBrand()}"/>
</td>
<td><input type="number" step="0.00000001" placeholder="Enter price"
name="buyPrice"/></td>
<td><input type="number" step="0.00000001" placeholder="Enter bottom"
name="bottom"/></td>
<td><input type="number" step="0.00000001" placeholder="Enter top"
name="top"/></td>
<td><input type="number" step="0.00000001" placeholder="Enter stop"
name="stop"/></td>
<td>
<label class="custom-control custom-checkbox">
<input id="clickboxReduce" name="clickboxReduce" type="checkbox" class="custom-control-input"
value="yes"
onchange="handleClickOnReduceStart(this)" checked>
<span class="custom-control-indicator"></span>
</label>
</td>
<td>
<label class="custom-control custom-checkbox">
<input id="clickboxStart" type="checkbox" class="custom-control-input"
onchange="handleClickOnReduceStart(this)" checked>
<span class="custom-control-indicator"></span>
</label>
</td>
<!-- <td><input id="clickboxReduce" type="checkbox"></td>
<td><input id="clickboxStart" type="checkbox"></td>-->
</tr>
</tbody>
</table>
<br/>
<br/>
<button style="float: right!important;" class="btn btn-outline-primary">Continue</button>
</form>
</div>
and the function
function interceptSubmit () {
let form = document.getElementsByClassName('custom-control-input')
for (i = 0; i < form.length; i++) {
if (!form[i].checked) {
console.log('unchecked')
form[i].checked = true
}
}
return true // return false to cancel form action
}
Output of my backend controller
clickBoxReduce
[yes, no, yes]
1 Answer 1
Your question
Does anyone have a better idea?
There are multiple posts on SO like Force a checkbox to always submit, even when unchecked, which has an answer that suggests adding a hidden input with the same name as the checkbox and the opposite value (e.g. "no").
<input type="hidden" name="checkbox1" value="off">
<input type="checkbox" name="checkbox1" value="on"> My checkbox
Perhaps you should consider using different elements - e.g. radio buttons, which can be styled like more modern toggle inputs (e.g. iOS style)
Review of existing code
Looking at interceptSubmit()
I see that:
Lines aren't terminated with semi-colons. While they are only required after a handful of statements, it could lead to errors if somehow whitespace got removed.
The variable name
form
is slightly misleading - typically a form refers to a<form>
element, but in this case the variable is an HTMLCollection of elements with a particular class name, which appears to be two checkboxes with the given HTML.let form = document.getElementsByClassName('custom-control-input')
A more appropriate name might be
customControlInputs
form
isn't re-assigned so it could be declared withconst
to avoid accidental re-assignment and other bugs.document.getElementsByClassName()
returns a Live HTMLCollection 1 so that assignment can take place outside of the function - perhaps an ideal place would be as soon as the DOM is readyfor
loop variablei
is a global variable because it is not declared withvar
,let
, etc. In general it is best to avoid global variables.a
for...of
loop could be used instead of a regularfor
loop if ecmascript-6 is supported by all target browsers. That would allow simplification of accessing elements of the collection.
th:name="${'clickboxReduce_' + car.id}"
. \$\endgroup\$!!+"0"
equals false and!!+"1"
equals true. Don’t you think it would be easier to maintain and require less logic and take up less data using 0 & 1? I’m sure java has a typecast equivalent. \$\endgroup\$