1. 概要

このチュートリアルでは、ThymeleafテンプレートでSpringベースのバックエンドアプリケーションから発生したエラーメッセージを表示する方法を説明します。

デモンストレーションの目的で、単純なSpring Bootユーザー登録アプリを作成し、個々の入力フィールドを検証します。 さらに、グローバルレベルのエラーを処理する方法の例を示します。

まず、バックエンドアプリをすばやくセットアップしてから、UIの部分に進みます。

2. SpringBootアプリケーションのサンプル

ユーザー登録用のシンプルなSpring Bootアプリを作成するには、コントローラー、リポジトリ、エンティティが必要です。

ただし、その前でも、Mavenの依存関係を追加する必要があります。

2.1. Mavenの依存関係

必要なすべてのSpring Bootスターターを追加しましょう–MVCビット用のWeb 、休止状態エンティティ検証用の Validation 、UI用の Thymeleaf リポジトリのJPA。 さらに、メモリ内データベースを作成するには、H2依存関係が必要です。

<dependency> 
    <groupId>org.springframework.boot</groupId> 
    <artifactId>spring-boot-starter-web</artifactId> 
    <version>2.4.3</version> 
</dependency> 
<dependency> 
    <groupId>org.springframework.boot</groupId> 
    <artifactId>spring-boot-starter-validation</artifactId> 
    <version>2.4.3</version> 
</dependency> 
<dependency> 
    <groupId>org.springframework.boot</groupId> 
    <artifactId>spring-boot-starter-thymeleaf</artifactId> 
    <version>2.4.3</version> 
</dependency> 
<dependency> 
    <groupId>org.springframework.boot</groupId> 
    <artifactId>spring-boot-starter-data-jpa</artifactId> 
    <version>2.4.3</version> 
</dependency> 
<dependency> 
    <groupId>com.h2database</groupId> 
    <artifactId>h2</artifactId> 
    <scope>runtime</scope> 
    <version>1.4.200</version> 
</dependency>

2.2. エンティティ

Userエンティティは次のとおりです。

@Entity
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @NotEmpty(message = "User's name cannot be empty.")
    @Size(min = 5, max = 250)
    private String fullName;

    @NotEmpty(message = "User's email cannot be empty.")
    private String email;

    @NotNull(message = "User's age cannot be null.")
    @Min(value = 18)
    private Integer age;

    private String country;

    private String phoneNumber;

    // getters and setters
}

ご覧のとおり、ユーザー入力にいくつかの検証制約を追加しました。 たとえば、フィールドはnullまたは空であってはならず、特定のサイズまたは値を持つ必要があります。

特に、countryまたはphoneNumberフィールドに制約は追加されていません。 これは、グローバルエラー、または特定のフィールドに関連付けられていないエラーを生成するための例としてそれらを使用するためです。

2.3. リポジトリ

基本的なユースケースには、単純なJPAリポジトリを使用します。

@Repository
public interface UserRepository extends JpaRepository<User, Long> {}

2.4. コントローラー

最後に、バックエンドですべてを一緒に配線するために、UserControllerをまとめましょう。

@Controller
public class UserController {

    @Autowired
    private UserRepository repository;
    @GetMapping("/add")
    public String showAddUserForm(User user) {
        return "errors/addUser";
    }

    @PostMapping("/add")
    public String addUser(@Valid User user, BindingResult result, Model model) {
        if (result.hasErrors()) {
            return "errors/addUser";
        }
        repository.save(user);
        model.addAttribute("users", repository.findAll());
        return "errors/home";
    }
}

ここでは、パス /addGetMappingを定義して、登録フォームを表示しています。 同じパスにあるPostMappingは、フォームが送信されたときの検証を処理し、問題がなければリポジトリに保存します。

3. エラーメッセージ付きのThymeleafテンプレート

基本がカバーされたので、問題の核心に到達しました。つまり、UIテンプレートを作成し、エラーメッセージがある場合はそれを表示します。

表示できるエラーの種類に基づいて、テンプレートを少しずつ作成してみましょう

3.1. フィールドエラーの表示

Thymeleafには、特定のフィールドにエラーが存在するかどうかに応じてブール値を返す組み込みのfield.hasErrorsメソッドが用意されています。 th:if と組み合わせると、エラーが存在する場合はそれを表示するように選択できます。

<p th:if="${#fields.hasErrors('age')}">Invalid Age</p>

次に、にスタイルを追加する場合は、th:classを条件付きで使用できます

<p  th:if="${#fields.hasErrors('age')}" th:class="${#fields.hasErrors('age')}? error">
  Invalid Age</p>

単純な埋め込みCSSクラスerrorは、要素の色を赤に変えます。

<style>
    .error {
        color: red;
    }
</style>

別のThymeleaf属性th:errors は、指定されたセレクターですべてのエラーを表示する機能を提供します。たとえば、 email:

<div>
    <label for="email">Email</label> <input type="text" th:field="*{email}" />
    <p th:if="${#fields.hasErrors('email')}" th:errorclass="error" th:errors="*{email}" />
</div>

上記のスニペットでは、CSSスタイルの使用のバリエーションも確認できます。 ここではth:errorclassを使用しています。これにより、CSSを適用するための条件付き属性を使用する必要がなくなります。

または、 th:each を使用して、特定のフィールドのすべての検証メッセージを反復処理することもできます。

<div>
    <label for="fullName">Name</label> <input type="text" th:field="*{fullName}" 
      id="fullName" placeholder="Full Name">
    <ul>
        <li th:each="err : ${#fields.errors('fullName')}" th:text="${err}" class="error" />
    </ul>
</div>

特に、ここでは別のThymeleafメソッド fields.errors()を使用して、fullNameフィールドに対してバックエンドアプリから返されるすべての検証メッセージを収集しました。

次に、これをテストするために、ブートアプリを起動し、エンドポイント http:// localhost:8080 /addにアクセスします。

これは、入力をまったく提供しない場合のページの外観です。

3.2. すべてのエラーを一度に表示

次に、すべてのエラーメッセージを1つずつ表示するのではなく、すべてを1か所に表示する方法を見てみましょう。

そのために、 Thymeleafのfields.hasAnyErrors()メソッドを使用します。

<div th:if="${#fields.hasAnyErrors()}">
    <ul>
        <li th:each="err : ${#fields.allErrors()}" th:text="${err}" />
    </ul>
</div>

ご覧のとおり、ここでは別のバリアント fields.allErrors()を使用して、HTMLフォームのすべてのフィールドのすべてのエラーを繰り返し処理しました。

fields.hasAnyErrors()の代わりに、#fields.hasErrors(’*’)を使用することもできます。 同様に、#fields.errors(’*’)は、上記で使用された#fields.allErrors()の代替です。

効果は次のとおりです。

3.3. フォーム外でのエラーの表示

次。 HTMLフォームの外部に検証メッセージを表示したいシナリオを考えてみましょう。

その場合、選択または(* {…。})を使用する代わりに、($ {…。})の形式で完全修飾変数名を使用する必要があります。

<h4>Errors on a single field:</h4>
<div th:if="${#fields.hasErrors('${user.email}')}"
 th:errors="*{user.email}"></div>
<ul>
    <li th:each="err : ${#fields.errors('user.*')}" th:text="${err}" />
</ul>

これにより、emailフィールドにすべてのエラーメッセージが表示されます。

それでは、すべてのメッセージを一度に表示する方法を見てみましょう

<h4>All errors:</h4>
<ul>
<li th:each="err : ${#fields.errors('user.*')}" th:text="${err}" />
</ul>

そして、これが私たちがページに見るものです:

3.4. グローバルエラーの表示

実際のシナリオでは、特定のフィールドに特に関連付けられていないエラーが発生する可能性があります。 ビジネス条件を検証するために複数の入力を考慮する必要があるユースケースがあるかもしれません。 これらはグローバルエラーと呼ばれます。

これを示す簡単な例を考えてみましょう。 countryおよびphoneNumberフィールドの場合、特定の国の電話番号が特定のプレフィックスで始まる必要があるというチェックを追加する場合があります。

この検証を追加するには、バックエンドでいくつかの変更を加える必要があります。

まず、 Service を追加して、この検証を実行します。

@Service
public class UserValidationService {
    public String validateUser(User user) {
        String message = "";
        if (user.getCountry() != null && user.getPhoneNumber() != null) {
            if (user.getCountry().equalsIgnoreCase("India") 
              && !user.getPhoneNumber().startsWith("91")) {
                message = "Phone number is invalid for " + user.getCountry();
            }
        }
        return message;
    }
}

ご覧のとおり、些細なケースを追加しました。 国インドの場合、電話番号はプレフィックス91で始まる必要があります。

次に、コントローラーのPostMappingを微調整する必要があります。

@PostMapping("/add")
public String addUser(@Valid User user, BindingResult result, Model model) {
    String err = validationService.validateUser(user);
    if (!err.isEmpty()) {
        ObjectError error = new ObjectError("globalError", err);
        result.addError(error);
    }
    if (result.hasErrors()) {
        return "errors/addUser";
    }
    repository.save(user);
    model.addAttribute("users", repository.findAll());
    return "errors/home";
}

最後に、Thymeleafテンプレートで、定数グローバルを追加して、このようなタイプのエラーを表示します

<div th:if="${#fields.hasErrors('global')}">
    <h3>Global errors:</h3>
    <p th:each="err : ${#fields.errors('global')}" th:text="${err}" class="error" />
</div>

または、定数の代わりに、メソッド#fields.hasGlobalErrors()および#fields.globalErrors()を使用して同じことを実現できます。

これは、無効な入力を入力したときに表示されるものです。

4. 結論

このチュートリアルでは、シンプルなSpring Bootアプリケーションを作成して、Thymeleafでさまざまなタイプのエラーを表示する方法を示しました。

フィールドエラーを1つずつ表示し、次にすべてを一度に表示すること、HTMLフォーム外のエラー、およびグローバルエラーを表示することを検討しました。

いつものように、ソースコードはGitHubから入手できます。