Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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.

like image 519
Kristof Avatar asked Aug 25 '15 05:08

Kristof


People also ask

Is Thymeleaf supported by Spring MVC?

Thymeleaf offers a set of Spring integrations that allow you to use it as a fully-featured substitute for JSP in Spring MVC applications.

What is Thymeleaf in MVC?

Thymeleaf is a Java template engine for processing and creating HTML, XML, JavaScript, CSS and text. In this tutorial, we will discuss how to use Thymeleaf with Spring along with some basic use cases in the view layer of a Spring MVC application.

What is Thymeleaf model?

This model map allows for the complete abstraction of the view technology and, in the case of Thymeleaf, it is transformed into a Thymeleaf context object (part of the Thymeleaf template execution context) that makes all the defined variables available to expressions executed in templates.

What is thymeleaf in Spring MVC?

The Thymeleaf is a template engine that is used to create view pages in Spring MVC. It provides full Spring integration and was designed to replace JSP as a view page in Spring MVC. It is better to use Thymeleaf if you are working with a web application.

Is Ajax overkill for thymeleaf?

For applications that use Thymeleaf views, AJAX may be overkill. But form handling is not simple. At least in the context of Spring MVC. One way to handle form submission is to read all request parameters using @RequestParam annotation.

How to handle form submission in Spring MVC using Ajax?

For applications that use Thymeleaf views, AJAX may be overkill. But form handling is not simple. At least in the context of Spring MVC. One way to handle form submission is to read all request parameters using @RequestParam annotation. For example, take a look at this form. You can read the submitted form like this.

Should we use Spring Boot or thymeleaf for Java web development?

We will use Spring Boot as it simplifies Java web development and Thymeleaf as it keeps HTML templates still look like HTML when mixed with code (Thymeleaf is well-integrated with Spring MVC).


2 Answers

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.

like image 119
yorgo Avatar answered Oct 08 '22 16:10

yorgo


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/new_project.html

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
like image 22
Yepadee Avatar answered Oct 08 '22 15:10

Yepadee