Search code examples
springcheckboxradio-buttonthymeleafchecked

Thymeleaf - Checked attribute of checkbox is not set in th:each OR how to properly restore a list of checkboxes some of which were previously checked


In my app I want to create a new Risk ( an instance of Risk object ) and when it is created I want to display 5 checkboxes and three radio buttons. Selected options are specific to each instance of Risk object.

Later I want to display a list of all added Risks with an Edit option button on each Risk. I want my app to restore the view specific to a selected Risk ( when an Edit button on a selected risk is clicked ) - with Risk name, all checkboxes and radio-buttons checked as selected previously. And I want to be able to edit these checkbox selections again so that all new changes were properly reflected in MySQL.

As a newbie in Thymeleaf I did the following:

<div th:each="top : ${topic}">
    <input type="checkbox" th:field="*{topic}" th:checked="${top.checked}" th:value="${top.name}"/><label th:text="${top.name}">Something is wrong !</label>
</div>

I am sure that Controller and Hibernate/MySQL part works properly ( I checked using Logs ).

This works just fine - but only if I have selected only one checkbox ( initially when I added a risk ).

If I select more than one checkbox (when adding a risk) and later select this risk for editing no checkboxes are checked.

What is wrong ?


Solution

  • After some research I found the following text in Thymeleaf’s documentation:

    “… th:field would have taken care of that and would have added a checked="checked" attribute to the corresponding input tags.”.

    Also I found this guidance :

    http://forum.thymeleaf.org/The-checked-attribute-of-the-checkbox-is-not-set-in-th-each-td3043675.html

    Then I managed to develop a couple of small apps and I want to share what I found out and hope it will help someone. ( may be it is too detailed for experienced people, but I want it to be clear to all )

    I do not want to repeat what is already in the above-mentioned Thymeleaf’s forum page ( see Administrator’s first response / explanation for detail - second in forum thread ) - just want to make a small summary and stress out few points:

    • you indeed do not need to add ‘checked’ when using th:each;

    • you must add th:field=“{…}” which should have the name of the field in your model class (referred by Thymeleaf as form-backing bean - th:object ) to which checkboxes are related. More on this: I stated above that my ‘form-backing bean’ was Risk.java. And for each Risk object instance the selected checkboxes represent topics(s) specific to this Risk instance. And selected topics are assigned to field ‘topic’ of Risk.java's instance (and hence in related table in MySQL when instance is saved). That field’s name should go inside th:field=“{…}” as th:field=“*{topic}” in my case. When you select checkboxes Thymeleaf will save selected values to Risk.java’s topic field using its setTopic method and when it needs to restore view Thymeleaf will use Risk.getTopic method to get info on earlier selected items.

    • all values of checkboxes (or radio-buttons ) should come from another source - it could be an Enum if you need a static set of checkboxes or if you need checkboxes to be dynamically generated you can use a class ( I needed static set of checkboxes for my app, but I decided to try to create a dynamic one as well - see links to my Github repo’s below to see the code I managed to develop ). So for my app I created an Enum Topics with all values for checkboxes and Enum Types with all values for radio-buttons. Then in your controller class you should add all values to Model’s attribute - I did this as I used an Enum:

      model.addAttribute("topics", Topics.values());
      model.addAttribute("types", Types.values());
      

    (if you need dynamic ones do the following:

        model.addAttribute("topics", topicsService.findAll());
        model.addAttribute("types", typesService.findAll());
    

    )

    Then you should have something similar to:

        <div>
                <div th:each="top : ${topics}">
                    <input type="checkbox" th:field="*{topic}"  th:value="${top.id}"/><label th:text=" | &nbsp; ${top.name}|">Something is wrong !</label>
                </div>
        </div>
    
        <div>
                <div th:each="typ : ${types}">
                    <input type="radio" th:field="*{type}"  th:value="${typ.id}"/><label th:text="| &nbsp; ${typ.name} |">Something is wrong !</label>
                </div>
        </div>
    

    where:

    • as mentioned, th:field=“{topic}" corresponds to form-backing Model class - Risk.java’s field. Same for th:field=“{type}" ;

    • topics in th:each="top : ${topics}” should match with attribute name you provided in controller.

    And the MOST important part is that th:field=“*{topic}” should return an array.

    th:field=“*{topic}” returning an array of selected items and th:each returning array of all options Thymeleaf should now be able to mark checkboxes / radio buttons as checked when values in first array match values in second array.

    Since in case of radio buttons you can select only one option th:field=“*{type}” does not actually return an array - it returns only one item. But in case of checkboxes it should be an array - so the ‘topic’ field in Risk.java must return an array.

    For this we need a converter - a class named e.g. StringListConverter that implements AttributeConverter….

    ( I learned how I can do it here. If not this answer in www.stackoverflow.com I would not be able to finalyze this app and would not be writing all this: https://stackoverflow.com/a/34061723/6332774 )

    Then in your form-backing model class - Risk.java in my case you need to do something like :

    @Convert(converter = StringListConverter.class)
    private List<String> topic = new ArrayList<>();
    
    private String type;
    

    Type can simply be a String.

    That is it.

    ( I wanted to display checkboxes in table form indicating number of columns needed - I could do it, but I am not sure how clean it is or how safe it is. Related code is in riskformtable.html in example project linked below.

    I posted a related question here - Thymeleaf - Displaying checkboxes in table form when using th:each - is what I am doing safe?

    Also I wanted to use different color for all risk items with even sequential number in my risk’s list - it is in index.html

    See full example code using links below )

    Links to my GitHub repos: