Issue
What is the recommended way to handle AJAX forms and their error messages on the Thymeleaf side of things?
I currently have a Spring controller which returns a JSON overview of fields and their respective error messages, but having to resort to using fully handwritten JQuery (or just regular Javascript) just feels a bit wrong, and slow; especially because of the large amount of forms I intend to have in the application.
Solution
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;
@Data
public 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.
Answered By - yorgo