Recommended way to handle Thymeleaf Spring MVC AJAX Forms and their error messages Recommended way to handle Thymeleaf Spring MVC AJAX Forms and their error messages ajax ajax

Recommended way to handle Thymeleaf Spring MVC AJAX Forms and their error messages


What I like to do is replace the entire form when an error occurs. The following is a super primitive example. I'm not going to use a ton of fragments for rendering a form... just keeping it simple.

This was written in a Spring 4.2.1 and Thymeleaf 2.1.4

A basic class representing a user info form: UserInfo.java

package myapp.forms;import org.hibernate.validator.constraints.Email;import javax.validation.constraints.Size;import lombok.Data;@Datapublic class UserInfo {  @Email  private String email;  @Size(min = 1, message = "First name cannot be blank")  private String firstName;}

The controller: UsersAjaxController.java

import myapp.forms.UserInfo;import myapp.services.UserServices;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Controller;import org.springframework.ui.Model;import org.springframework.validation.BindingResult;import org.springframework.web.bind.annotation.*;import javax.transaction.Transactional;@Controller@Transactional@RequestMapping("/async/users")public class UsersAjaxController {  @Autowired  private UserServices userServices;  @RequestMapping(value = "/saveUserInfo", method = RequestMethod.POST)  public String saveUserInfo(@Valid @ModelAttribute("userInfo") UserInfo userInfo,                             BindingResult result,                             Model model)  {    // if any errors, re-render the user info edit form    if (result.hasErrors()) {        return "fragments/user :: info-form";    }    // let the service layer handle the saving of the validated form fields    userServices.saveUserInfo(userInfo);    return "fragments/user :: info-success";  }}

The file used to render the form and the success message: fragments/user.html

<div th:fragment="info-form" xmlns:th="http://www.thymeleaf.org" th:remove="tag">  <form id="userInfo" name="userInfo" th:action="@{/async/users/saveUserInfo}" th:object="${userInfo}" method="post">    <div th:classappend="${#fields.hasErrors('firstName')}?has-error">      <label class="control-label">First Name</label>      <input th:field="*{firstName}" type="text" />    </div>    <div th:classappend="${#fields.hasErrors('first')}?has-error">      <label class="control-label">Email</label>      <input th:field="*{email}" ftype="text" />    </div>    <input type="submit" value="Save" />  </form></div><div th:fragment="info-success" xmlns:th="http://www.thymeleaf.org" th:remove="tag">  <p>Form successfully submitted</p></div>

The JS code will simply submit the form to the URL provided in the form action attribute. When the response is returned to the JS callback, check for any errors. If there are errors, replace the form with the one from the response.

(function($){  var $form = $('#userInfo');  $form.on('submit', function(e) {    e.preventDefault();    $.ajax({      url: $form.attr('action'),      type: 'post',      data: $form.serialize(),      success: function(response) {        // if the response contains any errors, replace the form        if ($(response).find('.has-error').length) {          $form.replaceWith(response);        } else {          // in this case we can actually replace the form          // with the response as well, unless we want to           // show the success message a different way        }      }  });})}(jQuery));

Again, this is just a basic example. As mentioned in the comments above, there is no right or wrong way to go about this. This isn't exactly my preferred solution either, there are definitely a few tweaks to this I would do, but the general idea is there.

NOTE: There's a flaw in my JS code as well. If you replace the form with the one from the response, the form submit handler will not be applied to the newly replaced form. You would need to ensure you reinitialize the form handler properly after replacing the form, if going this route.


Don't know if anybody's still interested, but I'll leave this here for the sake of anyone looking for an example solution.

I implemented yorgo's solution and got around the "flaw" by including the script in the fragment. There was also another small flaw in that if there was more than one submit button with an extra parameter (which is often used to implement dynamic forms in spring + thymeleaf) the information is lost when the form is serialised. I got around this by manually appending this information onto the serialised form.

You can view my implementation of yorgo's solution with the fixes here: https://github.com/Yepadee/spring-ajax/blob/master/src/main/resources/templates/project_form.html?fbclid=IwAR0kr_-t05_vFrPJxvbsG1Et7aLGir5ayXyPi2EKI6OHbEALgDfmHZ7HaKI

Whats nice about it is that you can implement it on any existing thymeleaf form simply by doing the following:

  1. put the script in the form and make it reference the form's id
  2. put the form into a fragment
  3. change the post methods in the controller to return the form fragment rather than the entire template


Very little documentation on this but if you are already familiar with Web Flow, you may not need more. I'm not sure how this technique works with normal bean binding in Thymeleaf. I would love to see a full pet-clinic demo app use this so I can see the controllers.

docs