Search code examples
javahtmldatatablesthymeleafmaterialize

Checkbox input using MaterializeCSS and Thymeleaf


Hey there so I am using Spring Boot and Spring Web with Thymeleaf. For the frontend I use MaterializeCSS and datatables with this styling CSS/JS.

I want to display a table where the user can select datasets that are parsed in there with Thymeleaf iteration. I tried doing it with a form of a CheckBoxWrapper that contains a list of CheckBox elements. The CheckBox Elements themselves contain a single boolean value. You can find my full code at the end of this question.

But if I try to do it like this (I think?) Thymeleaf generates another hidden input field directly after the checkbox. The HTML in the browser then looks like this which results in the checkbox not being displayed by the browser. If I (re-)move the generated hidden input outside the label the checkbox gets displayed correctly. See the difference on this picture

Is there a way to get the checkbox displayed correctly while still being able to get the checkbox input to the server? (with Thymeleaf)

If you need some additional information regarding anything problem related please let me know.

Code - HTML

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:th="http://www.thymeleaf.org"
      lang="en">
<head>
    <meta charset="UTF-8">
    <title>This is some really important detail</title>
    <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet" />
    <link rel="stylesheet" th:href="@{/css/materialize.css}" />
    <link rel="stylesheet" th:href="@{/css/datatables.css}" />
</head>
<body>

<div class="container">
    <div id="admin" class="col s10 offset-s1">
        <div class="card material-table">
            <div class="table-header">
                <span class="table-title">Table Headline</span>
                <div class="actions">
                    <a href="#" class="search-toggle waves-effect btn-flat nopadding"><i class="material-icons">search</i></a>
                </div>
            </div>
            <form action="/submit" th:object="${checkBoxWrapper}">
                <table id="datatable">
                    <thead>
                    <tr>
                        <th>Check</th>
                        <th>Name</th>
                    </tr>
                    </thead>
                    <tbody>
                    <th:block th:each="element,status : ${persons}">
                        <tr>
                            <td>
                                <div>
                                    <label>
                                        <input type="checkbox" th:id="'checkbox'+${status.index}"
                                               th:field="*{list[__${status.index}__].val}"/>
                                        <span>Desc</span>
                                    </label>
                                </div>
                            </td>
                            <td>
                                <th:block th:text="${element.name}"> -</th:block>
                            </td>
                        </tr>
                    </th:block>
                    </tbody>
                </table>
            </form>
        </div>
    </div>
</div>

</body>
<script th:src="@{/webjars/datatables/1.10.20/js/jquery.dataTables.js}"></script>
<script th:src="@{/webjars/jquery/3.4.1/jquery.js}"></script>
<script th:src="@{/script/datatables.js}"></script>
<script th:src="@{/script/materialize.js}"></script>
</html>

Code - Controller

@Controller
public class MyController {

    @GetMapping("/")
    public String home(Model model){

        List<Person> pList = new ArrayList<>();
        pList.add(new Person("Mick"));
        pList.add(new Person("Kevin"));
        pList.add(new Person("Joe"));
        model.addAttribute("persons", pList);

        List<CheckBox> cList = new ArrayList<>();
        for (int i = 0; i < pList.size(); i++) {
            cList.add(new CheckBox(false));
        }
        CheckBoxWrapper checkBoxWrapper = new CheckBoxWrapper(cList);
        model.addAttribute("checkBoxWrapper", checkBoxWrapper);

        return "/home";
    }

    @PostMapping("/submit")
    @ResponseBody
    public String submit(@ModelAttribute CheckBoxWrapper checkBoxWrapper){

        return "success";
    }
}

Code - CheckBox

public class CheckBox {

    private boolean val;

    //Getter, Setter, Constructor

}

Code - CheckBoxWrapper

public class CheckBoxWrapper {

    List<CheckBox> list = new ArrayList<>();

    //Getter, Setter, Constructor

}

Code - Person

public class Person{

    String name;

    //Getter, Setter, Constructor

}

Solution

  • If you can move the hidden input ABOVE the checkbox, you're good:

    <label>
       <input type="hidden" name="_list[0].val" value="on"><!-- First is ok! -->
       <input type="checkbox" /><!-- I still display -->
       <span>Red</span>
    </label>
    

    Codepen

    Just to note, the markup is very strict here - as in each unique checkbox and span pairing must be wrapped in their own label. Any variation from this will break the component.

    https://materializecss.com/checkboxes.html