Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to preserve attributes of nested objects on thymeleaf forms

I've got an object which I want to load the form fields for dynamically. Instantiating and presenting the fields is working appropriately on display, however, when I attempt to submit the form, all of the data is lost except the ${field.value} which is provided by the user. How does one preserve all of the additional attribute values?

Here's the controller method that generates the form object:

@RequestMapping(method = RequestMethod.GET)
public String newFooForm(@AuthenticationPrincipal UserDetails user, @RequestParam("fooType") String fooType, @RequestParam("isTeam") Boolean isTeam, Model model) {
    Employee currentUser = getCurrentUser(user);
    List<FooFieldDefinition> fooFieldDefinitions = fooFieldDefinitionService.findByOrganizationalUnitIdAndIsTeam(currentUser.getOrgId(), isTeam);
    FooViewModel fooViewModel = new FooViewModel(currentUser, fooType, isTeam, fooFieldDefinitions);
    model.addAttribute("fooViewModel", fooViewModel);
    return "newFooForm";
}

The following snippet is called in the constructor FooViewModel(currentUser, fooType, isTeam, fooFieldDefinitions) which populates fooViewModel.fooFields:

fooFields = new ArrayList<>();
for (FooFieldDefinition fooFieldDefinition : fooFieldDefinitions) {
    fooFields.add(new FooField(fooFieldDefinition));
}

Here's the snippet of the form:

<form id="foo-form" action="#" th:action="@{/fooForms}" th:object="${fooViewModel}"
      method="post">
    <div th:each="fooField,iterationStatus  : *{fooFields}">
        <label th:text="${fooField.label}">Label</label>
        <input th:type="text" th:field="*{fooFields[__${iterationStatus.index}__].value}"/>
    </div>
    <div style="position: absolute; right: 20px; bottom: 20px;">
        <input class="btn btn-primary" id="save" type="submit" name="save" value="Save"/>
        <input class="btn btn-primary" id="submit" type="submit" name="submit" value="Submit"/>
    </div>
</form>

I know that the fooField has the appropriate data associated with the instance when the form is generated for 2 reasons. The label prints, and through debugging, I can see that the instances have the appropriate data before the view name is returned in thenewFooForm method.

Whenever I submit the form, all of the data for each fooField is set to null, except the value which is passed from the form. I know this because, 1. The data is not preserved and 2. Debugging shows that. Here's the controller method that's meant to hand the submission:

@RequestMapping(method = RequestMethod.POST, params = {"save"})
public String fooFormDraftSubmit(@AuthenticationPrincipal UserDetails user, @ModelAttribute FooViewModel fooViewModel, Model model) {
    Employee currentUser = getCurrentUser(user);
    Foo foo = new Foo(currentUser, fooViewModel);
    fooService.save(foo);
    for (FooField fooField : fooViewModel.getFooFields()) {
       fooFieldService.save(fooField);
    }
    FooViewModel persistedFooViewModel = new FooViewModel(foo);
    model.addAttribute("fooViewModel", persistedFooViewModel);
    return "fooView";
}

Thank you for any advice!

like image 674
Joe Essey Avatar asked Dec 14 '25 06:12

Joe Essey


1 Answers

I figured this out thanks to this post: Spring MVC 3 Command Object values being lost between GET and POST operation. The suggestion specifically was to use a hidden field. You have to access the attribute value of the ViewModel which is rendered on the page and assign it to the ViewModel that you wish to pass to the POST endpoint.

<form id="foo-form" action="#" th:action="@{/fooForms}" th:object="${fooViewModel}"
      method="post">
    <div th:each="fooField,iterationStatus  : *{fooFields}">
        <label th:text="${fooField.label}">Label</label>
        <input th:type="text" th:field="*{fooFields[__${iterationStatus.index}__].value}"/>
<!-- The fix, this is an example of 1 attribute.  Of course I will extend this for appropriate attribute values -->
        <input th:type="hidden" th:field="*{fooFields[__${iterationStatus.index}__].label}" th:value="${fooViewModel.fooFields[__${iterationStatus.index}__].label}"/>
<!-- End new code -->
    </div>
    <div style="position: absolute; right: 20px; bottom: 20px;">
        <input class="btn btn-primary" id="save" type="submit" name="save" value="Save"/>
        <input class="btn btn-primary" id="submit" type="submit" name="submit" value="Submit"/>
    </div>
</form>
like image 111
Joe Essey Avatar answered Dec 16 '25 04:12

Joe Essey