6
\$\begingroup\$

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 noand 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]

\$\endgroup\$
  • 2
    \$\begingroup\$ You could just give the check boxes different names. Every row (car?) needs/should have a unique identifier, so for example th:name="${'clickboxReduce_' + car.id}". \$\endgroup\$ – RoToRa Aug 16 '18 at 14:30
  • \$\begingroup\$ Additionally, why use “yes” or “no” when true/false already have a binary representation? Just a thought. For example with JavaScript !!+”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\$ – soulshined Nov 29 '19 at 11:43
1
\$\begingroup\$

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 HTML collection 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 with const to avoid accidental re-assignment
  • document.getElementsByClassName() returns a Live HTMLCollection1 so that assignment can take place outside of the function - perhaps an ideal place would be as soon as the DOM is ready
  • for loop variable i is a global variable because it is not declared with var, let, etc. In general it is best to avoid global variables to avoid accidental re-assignment.
  • a for...of loop could be used instead of a regular for loop if is supported by all target browsers. That would allow simplification of accessing elements of the collection.
| improve this answer | |
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.