3

I've got a Product object that contains a Set<Provider> providers. I've annotated within the Provider a variable url with @NotEmpty and now I want to display a error, if this field is empty. I'm not sure how I can access the field providers within the hasErrors method properly.

Form:

<form action="#" th:action="@{/saveDetails}" th:object="${selectedProduct}" method="post">

  <!-- bind each input field to list (working) -->
  <input th:each="provider, status : ${selectedProduct.providers}"
         th:field="*{providers[__${status.index}__].url}" />

  <!-- all the time 'false' -->
  <span th:text="'hasErrors-providers=' + ${#fields.hasErrors('providers')}"></span>
  <span th:text="'hasErrors-providers[0].url=' + ${#fields.hasErrors('providers[0].url')}"></span>

  <!-- not working -->
  <span class="help-block" th:each="provider, status : ${selectedProduct.providers}" 
     th:if="${#fields.hasErrors('providers[__${status.index}__].url')}" 
     th:errors="${providers[__${status.index}__].url}">Error Url
  </span>

  <!-- print errors (just for testing purpose) -->
    <ul>
      <li th:each="e : ${#fields.detailedErrors()}">
        <span th:text="${e.fieldName}">The field name</span>|
        <span th:text="${e.code}">The error message</span>
      </li>
    </ul>

</form>

Within the <ul> I receive for each error providers[].url as e.fieldName. I thought it would be having some indices like providers[0].url etc. So my question is, how can I access the field providers within the hasErrors method properly to display the error messages.

EDIT

Controller:

@RequestMapping(value = "/saveDetails", method = RequestMethod.POST)
public String saveDetails(@Valid @ModelAttribute("selectedProduct") final Product selectedProduct,
                          final BindingResult bindingResult, SessionStatus status) {
    if (bindingResult.hasErrors()) {
        return "templates/details";
    }
    status.setComplete();
    return "/templates/overview";
}
baris1892
  • 981
  • 1
  • 16
  • 29

1 Answers1

5

You cannot get an item from a Set using their index because sets don't have ordering. Set interface doesn't provide a method of getting an item based on index, so doing .get(index) to a Set will give you compile error. Use List instead. This way, you can access the objects using their index.

So change Set<Provider> providers to :

@Valid
List<Provider> providers;

Don't forget the @Valid annotation so that it will cascade down to the child objects.

Also, if th:errors is inside a form, it should be pointing to a property of the object that backs that form, using Selection Expression (*{...})

<span class="help-block" th:each="provider, status : ${selectedProduct.providers}" 
    th:if="${#fields.hasErrors('providers[__${status.index}__].url')}" 
    th:errors="*{providers[__${status.index}__].url}">Error Url
</span>

EDIT

I see that you want to access the errors collectively, instead of iterating through them. In that case, you can create your custom JSR 303 validator. See the following useful code fragments :

Usage

@ProviderValid
private List<Provider> providers;

ProviderValid annotation

//the ProviderValid annotation.
@Target({ ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = ProviderValidator.class)
@Documented
public @interface ProviderValid {
    String message() default "One of the providers has invalid URL.";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};
}

ConstraintValidator

public class ProviderValidator implements ConstraintValidator<ProviderValid, List<Provider>>{

    @Override
    public void initialize(ProviderValid annotation) { }

    @Override
    public boolean isValid(List<Provider> value, ConstraintValidatorContext context) {

        //...
        //validate your list of providers here
        //obviously, you should return true if it is valid, otherwise false.
        //...

        return false;
    }
}

After doing these, you can easily get the default message you specified in the @ProviderValid annotation if ProviderValidator#isValid returns false by simply doing #fields.hasErrors('providers')

Bnrdo
  • 5,325
  • 3
  • 35
  • 63
  • Thanks for the hint! But is there a way to access the errors using the `#fields.hasErrors('providers')` method? Because this doesn't work for me...but showing errors for each index is working now using a `List`. – baris1892 Dec 14 '15 at 13:25
  • Thanks, this helped me a lot! – baris1892 Dec 14 '15 at 21:28