What You'll Learn

We have a form that allows the user to change the existing trustees. However, charities are not limited in the number of trustees that they have and trustees can share roles. Trustees are added and sometimes, trustees leave.

In this tutorial, we try to achieve an attractive and intuitive user interface without using client-side code.

The add will extend the form. The delete adds a checkbox to mark a trustee for deletion.

This processing is all done using a "replace" approach. We replace the list of trustees with a new list of trustees. This works well for us because the trustees are the "leaves" of the data structure. That is, there are no other objects hanging off the trustees and those objects aren't references by any other objects. If that was the case, then we'd have to apply a different strategy, but we would strive not to change the service interface.

We can amend the controller's method to handle updates by introducing a "add" request parameter (which is optional). The presence of this parameter indicates that the form is to be reloaded, but that the backing bean should have an additional "empty" record.

In CharityAdminController.java*

 @PostMapping("/update-charity/{furl}/trustees")
     public String trusteesFormPost(@PathVariable(value = "furl", required = true) String furl,
                                    @RequestParam(value = "add", required = false) Optional<String> addTrustee,
                                    @Valid TrusteesForm trusteesForm,
                                    BindingResult bindingResult,
                                    Model model) {

         CharityDTO charityDTO = charitySearch.findByAcronym(furl).get();

         if (addTrustee.isPresent()) {
             TrusteeForm extraTrusteeForm = new TrusteeForm();
             trusteesForm.addTrustee(extraTrusteeForm);
             model.addAttribute("charityDTO", charityDTO);
             return "admin/trustees-form";
         }

Notice the new request parameter. This is then checked, and if it is present, a new TrusteeForm object is added to the TrusteesForm object. The flow is redirected back to the form again.

There is a small change to TrusteesForm.java to make the adding of a trustee more readable.

## Add an "add" button.

To trigger this method with the "add" option, we need to pass the "add" request parameter. We do this using a th:formaction attribute.

In trustee-form.html.

 <button class="btn btn-primary"
                        th:formaction="@{/admin/update-charity/{furl}/trustees?add (furl=${charityDTO.acronym.toLowerCase()})}">
                    Add Trustee
                </button>

There is a separate "submit" button which will submit the whole form - including any new records.

We handle the delete by adding a small section of code to CharityAdminController.java in the trusteesFormPost method.

List<TrusteeDTO> trusteeDTOList = trusteesForm
        .getTrusteeForms()
        .stream()
        .filter(t -> !t.getToDelete())
        .map(t -> new TrusteeDTO(t.getId(), t.getName(), Trustee.Role.valueOf(t.getRole())))
        .collect(Collectors.toList());

We've added a filter that removes the trustees that should be deleted from the list of trustee DTOs. When this smaller list is passed to the service, our "replace" strategy takes care of everything.

Firstly, add a field to the TrusteeForm.java class.

private Boolean toDelete;

Remember to update the constructor as well! Check Gitlab if not sure.

Then add a checkbox to the trustees-form.html file.

<input th:field="*{trusteeForms[__${itemStat.index}__].toDelete}" type="checkbox"/>

This would come after the "select" tags.

This checkbox uses the same pattern as we've seen before for indexing and binds to the toDelete field.

Test this manually. Try adding, updating and removing trustees. Are there any edge-cases that are not covered at the moment?

We now covered most of the building blocks for building user interfaces with Spring Boot and Thymeleaf, while also introducing the idea of a service level interface.

There are a number of other options that can be used and part of the learning is to explore those yourselves and to think about things like the abstractions being offered, the consistency of approach (i.e. does it get easier to pick up new parts because the design approach is consistent) and the importance of other aspects such as documentation and community support (would you recommend this framework to others?)