0

I've found a few threads related to this, but most of the errors seem to be from incorrect naming, but I believe I'm doing that correctly by using @ModelAttribute. The validations are recognized and everything works correctly, besides the message display.

Here are my controllers:

         @GetMapping("/search")
            public String searchPage(Model model, @ModelAttribute("searchFormBacking") SearchParamModel search) {
                if (!model.containsAttribute("searchFormBacking")) {
                    model.addAttribute("searchFormBacking", new SearchParamModel());
                } else {
                    model.addAttribute("searchFormBacking", search);
                }
                return "search";
            }

            @PostMapping("/results")
            @SuppressWarnings("unchecked")
            public String resultSubmit(@ModelAttribute("searchFormBacking") @Valid SearchParamModel search, BindingResult result, final RedirectAttributes redirectAttributes) throws Exception{

                if (result.hasErrors()) {
                    //flash errors bound to "searchFormBacking"
                    redirectAttributes.addFlashAttribute("searchFormBacking",search);
                    redirectAttributes.addFlashAttribute("org.springframework.validation.BindingResult.searchFormBacking",result);
                    return "redirect:/search";
                }

                List<Object[]> queryList = GlobalMethods.baseQuery();

                //input into model&view
                List<CrimeModel> crimeList = GlobalMethods.analyzeQuery(search.getSearchAddress(),search.getSearchDistance(),search.getSearchTime(), queryList);
                List<CrimeRank> rankedList = GlobalMethods.distinctAsList(GlobalMethods.rankedMap(GlobalMethods.distinctCountMap(crimeList)));

                redirectAttributes.addFlashAttribute("searchFormBacking",search);
                redirectAttributes.addFlashAttribute("crimeModel", crimeList);
                redirectAttributes.addFlashAttribute("rankedModel", rankedList);


                return "redirect:/results";

            }

Here is the form:

        <!doctype html>
        <html lang="en" xmlns:th="http://www.thymeleaf.org">
        <head>
            <meta charset="UTF-8"/>
            <meta name="viewport"
                  content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"/>
            <meta http-equiv="X-UA-Compatible" content="ie=edge"/>
            <title>Crime Tracker | Search</title>
        </head>
        <body>
        <form th:action="@{/results}" th:object="${searchFormBacking}" method="post">

            <input type="text" th:field="*{searchAddress}" placeholder="Enter address."/>
            <div class="error-message" th:if="${#fields.hasErrors('searchAddress')}" th:errors="*{searchAddress}"></div>
            <br/>

            <input type="text" th:field="*{searchDistance}" placeholder="Enter the distance to search."/>
            <div class="error-message" th:if="${#fields.hasErrors('searchDistance')}" th:errors="*{searchDistance}"></div>
            <br/>

            <input type="text" th:field="*{searchTime}" placeholder="Time-span."/>
            <div class="error-message" th:if="${#fields.hasErrors('searchTime')}" th:errors="*{searchTime}"></div>
            <br/>

            <input type="submit" value="Submit"/>
            <br/>
            <input type="reset" value="Reset"/>
        </form>
        </body>
        </html>

And finally the form-backing class:

            public class SearchParamModel {

            @NotNull
            @Size(min = 6, max = 40)
            private String searchAddress;

            @NotNull
            private String searchDistance;

            @NotNull
            private String searchTime;


            public String getSearchAddress() {
                return searchAddress;
            }

            public void setSearchAddress(String searchAddress) {
                this.searchAddress = searchAddress;
            }

            public String getSearchDistance() {
                return searchDistance;
            }

            public void setSearchDistance(String searchDistance) {
                this.searchDistance = searchDistance;
            }

            public String getSearchTime() {
                return searchTime;
            }

            public void setSearchTime(String searchTime) {
                this.searchTime = searchTime;
            }

            @Override
            public boolean equals(Object o) {
                if (this == o) return true;
                if (o == null || getClass() != o.getClass()) return false;

                SearchParamModel that = (SearchParamModel) o;

                if (searchAddress != null ? !searchAddress.equals(that.searchAddress) : that.searchAddress != null)
                    return false;
                if (searchDistance != null ? !searchDistance.equals(that.searchDistance) : that.searchDistance != null)
                    return false;
                return searchTime != null ? searchTime.equals(that.searchTime) : that.searchTime == null;

            }

            @Override
            public int hashCode() {
                int result = searchAddress != null ? searchAddress.hashCode() : 0;
                result = 31 * result + (searchDistance != null ? searchDistance.hashCode() : 0);
                result = 31 * result + (searchTime != null ? searchTime.hashCode() : 0);
                return result;
            }
        }

The main error that people seem to have is that when they don't use @ModelAttribute, the default name becomes, in this case, searchParamModel. Additionally, I've handled the redirect on the /search get mapping to only create a new SearchParamModel if there wasn't already one. These seem to be the two most common reasons to lose the validation messages, so I'm wondering what I'm doing wrong.

Trevor Bye
  • 686
  • 1
  • 6
  • 26
  • Any suggestions are appreciated. I've looked at both Spring and thymeleaf tutorials and I really cannot figure out what I'm doing wrong, as my code is almost identical to what I'm seeing other people doing. – Trevor Bye Sep 17 '16 at 20:57
  • I feel like Will Smith in "I am Legend". Is anyone out there that can help? – Trevor Bye Sep 18 '16 at 14:59

2 Answers2

0

Passing BindingResult through RedirectionAttributes

After a considerable amount of searching, I found this thread and used the work-around near the bottom, where you override the flashAttribute and manually put the binding result in the model in the GET method. I still have no idea why this doesn't work without doing this. Also, the @NotNull annotation would not capture errors, but using @NotEmpty worked fine. My only guess is that these obscure issues are caused by different Spring versions, project setup etc., so hopefully if someone else out there has this problem, they will find this link that took me an eternity to find.

Community
  • 1
  • 1
Trevor Bye
  • 686
  • 1
  • 6
  • 26
0

Adding answer for future reference :

The issue is with the redirection. You are redirecting the page to search which will replace all your objects with the search controller objects.

Fix: We don't need to redirect it to search, We can just render the search template there itself

You can refer this answer: https://stackoverflow.com/a/60291568/8591032

You need to change the controller like this

@PostMapping("/results")
@SuppressWarnings("unchecked")
public String resultSubmit(@ModelAttribute("searchFormBacking") @Valid SearchParamModel search, BindingResult result, final RedirectAttributes redirectAttributes) throws Exception{

                    if (result.hasErrors()) {
                        return "search"; //changed from redirect:/search
                    }
}
 
Anu
  • 556
  • 6
  • 20