7
\$\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
  • 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, 2018 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, 2019 at 11:43

1 Answer 1

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 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 with const to avoid accidental re-assignment and other bugs.

  • 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.

  • 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.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.