Рекомендуемый способ обработки Thymeleaf Spring Формы MVC AJAX и их сообщения об ошибках

Каков рекомендуемый способ обработки форм AJAX и их сообщений об ошибках на стороне Thymeleaf?

В настоящее время у меня есть контроллер Spring, который возвращает обзор полей JSON и их соответствующие сообщения об ошибках, но прибегать к использованию полностью написанного вручную JQuery (или просто обычного Javascript) просто чувствует себя немного не так и медленно; особенно из-за большого количества форм, которые я намерен иметь в приложении.

Ответ 1

Мне нравится делать замену всей формы при возникновении ошибки. Ниже приведен пример супер примитивный. Я не собираюсь использовать тонну фрагментов для визуализации формы... просто сохраняя ее просто.

Это было написано в Spring 4.2.1 и тимелеафе 2.1.4

Основной класс, представляющий информационную форму пользователя: 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;
}

Контроллер: 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";
  }
}

Файл, используемый для отображения формы и сообщения об успешности: 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>

Код JS просто передаст форму URL-адресу, указанному в атрибуте действия формы. Когда ответ возвращается на обратный вызов JS, проверьте наличие ошибок. Если есть ошибки, замените форму на ответ.

(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));

Опять же, это всего лишь базовый пример. Как уже упоминалось выше, нет правильного или неправильного пути для этого. Это не совсем мое предпочтительное решение, есть определенно несколько хитростей к этому, я бы сделал, но общая идея есть.

ПРИМЕЧАНИЕ. В моем коде JS есть и недостатки. Если вы замените форму на ответ, обработчик отправки формы не будет применен к новой замененной форме. Вам нужно будет убедиться, что вы повторно инициализируете обработчик формы после замены формы, если собираетесь на этот маршрут.

Ответ 2

Очень небольшая документация по этому вопросу, но если вы уже знакомы с веб-потоком, вам может не понадобиться больше. Я не уверен, как эта техника работает с обычным привязкой bean в Thymeleaf. Мне бы очень хотелось увидеть полное приложение для демонов для домашних животных, чтобы я мог видеть контроллеры.

docs

Ответ 3

Не уверен, что это можно считать лучшей практикой, но вот что я сделал:

Map<String, String> errorMap = binding.getFieldErrors()
   .stream().collect(Collectors.toMap(
        e -> e.getField(), e -> messageSource.getMessage(e, locale)));

Затем я отправил карту обратно в ответ ajax на процесс в разделе "успех".

Ответ 4

Не знаю, заинтересован ли кто-нибудь еще, но я оставлю это здесь ради любого, кто ищет пример решения.

Я реализовал решение yorgo и обошел этот "недостаток", включив скрипт во фрагмент. Был также еще один небольшой недостаток: если было несколько кнопок отправки с дополнительным параметром (который часто используется для реализации динамических форм spring + тимелист), информация теряется при сериализации формы. Я справился с этим, вручную добавив эту информацию в сериализованную форму.

Вы можете просмотреть мою реализацию решения yorgo с исправлениями здесь: https://github.com/Yepadee/spring-ajax/blob/master/src/main/resources/templates/project_form.html?fbclid=IwAR0kr_-t05_vFrPJxvbsG1EtayHHHHPH

Что приятно, так это то, что вы можете реализовать его в любой существующей форме тимелина, просто выполнив следующее:

  1. поместите скрипт в форму и сделайте так, чтобы он ссылался на идентификатор формы
  2. положить форму в фрагмент
  3. измените методы post в контроллере так, чтобы они возвращали фрагмент формы, а не весь шаблон