Search code examples
javahtmlformsspring-bootthymeleaf

Java Thymeleaf: Get data from one form into another and then use data from both on submit


I am using Spring Boot with Thymeleaf. I want to get the value from the first form, display it on the second form where I get extra data and then, when the submit button on the 2nd form is pressed, have both values display in a result Page.

  1. When trying to add validation to "Age" I get the following error: Neither BindingResult nor plain target object for bean name 'formTwo' available as request attribute
  2. How can I get the name that I input on the first form to display on the second form?

Update - I think I have sorted Point 2. now by using th:attr and changing my Controller slightly. I have updated the code snippets to reflect my progress so far.

  1. How can I read both the Name (from form One but displayed on form Two) and Age (from form Two) to display in the results page?

This is what I have so far. What am I doing wrong?

The 1st form (One.html)

<body>
 <form action="#" th:action="@{/}" th:object="${formOne}" method="post">
  <table>
   <tr>
    <td>Name:</td>
    <td><input type="text" th:field="*{name}" /></td>
    <td th:if="${#fields.hasErrors('name')}" th:errors="*{name}">Name Error</td>
   </tr>
   <tr>
    <td><button type="submit">Submit</button></td>
   </tr>
  </table>
 </form>
</body>

FormOne.class

public class FormOne {

    @NotBlank
    @Size (min=2, max=10)
    private String name;

    public FormOne() {
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "FormOne{" + "name=" + name + "}";
    }
    
}

The 2nd form "Two.html"

<body>
    <form th:action="@{/name_age}" th:object="${formTwo}" method="post">
    <table>
        <tr>
            <td>Name:</td>
            <td><input type="text" th.value="*{name}" /></td>
        </tr><tr>
            <td>Age:</td>
            <!-- This has been removed <td><input type="text" th:value="*{age}" /></td> -->
            <td><input type="text" th:name="name" th:attr="value = ${formOne.name}" th.field="*{name}" /></td>
            <td th:if="${#fields.hasErrors('age')}" th:errors="*{age}">Age Error</td>
        </tr><tr>
            <td><button type="submit">Submit</button></td>
        </tr>
    </table>
    </form>
</body>

FormTwo.class

public class FormTwo {

    private String name;
    
    @NotNull
    @Min(10)
    private Integer age;
    
    public FormTwo() {
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "FormTwo{" + "name=" + name + ", age=" + age + "}";
    }
}

AppController.class

@Controller
public class AppController {

    @GetMapping("/")
    public String showStart(FormOne formOne) {
        return "One";
    }

    @PostMapping("/")
//    public String checkStartInfo(@Valid FormOne formOne, BindingResult bindingResult) {
    public ModelAndView checkStartInfo(@Valid FormOne formOne, BindingResult bindingResult) {
        System.out.println("==Inside checkStartInfo() - " + formOne.toString());
        ModelAndView mav = new ModelAndView();
        if (bindingResult.hasErrors()) {
            mav.setViewName("One");
            return mav;
//            return "One";
        }
        mav.setViewName("Two");
        mav.addObject("formOne", formOne);
        return mav;
//        return "Two";
    }
        
    @PostMapping("name_age")
    public String setName_Age(@Valid FormTwo formTwo, BindingResult bindingResult) {
        System.out.println("==Inside setName_Age() - " + formTwo.toString());
        if (bindingResult.hasErrors()) {
            return "Two";
        }
        return "Result";
    }
   
}

Solution

  • So I have found a way to get it to work. Comments if it is incorrect would be appreciated. I was making it really complicated. But below are my code snippets of how I got it to work.

    The 1st form (One.html) - Remains unchanged

    FormOne.class - Remains unchanged

    The 2nd Form (Two.html) - Looks as follows:

    <body>
        <form action="#" th:action="@{/name_age}" th:object="${formTwo}" method="post">
        <table>
            <tr>
                <td>Name:</td>
                <td><input type="text" th:name="name" th:attr="value = ${formTwo.name}" readonly/></td>
            </tr><tr>
                <td>Age:</td>
                <td><input type="text" th:field="*{age}" /></td>
                <td th:if="${#fields.hasErrors('age')}" th:errors="*{age}">Age Error</td>
            </tr><tr>
                <td><button type="submit">Submit</button></td>
            </tr>
        </table>
        </form>
    </body>
    

    FormTwo.class - Remains unchanged

    AppController.class - This is where MOST of the changes happened.

    @Controller
    public class AppController {
    
        @GetMapping("/")
        public String showFormOne(FormOne formOne) {
            return "One";
        }
    
        @PostMapping("/")
        public String checkFormOneInfo(@Valid FormOne formOne, BindingResult bindingResult, Model m) {
    
            if (bindingResult.hasErrors()) {
                return "One";
            }
    
            FormTwo ft = new FormTwo();
            ft.setName(formOne.getName());
            m.addAttribute("formTwo", ft);
            return "Two";
        }
        
        @GetMapping("/name_age")
        public String showFormTwo(FormTwo formTwo) {
            return "Two";
        }
    
        @PostMapping("/name_age")
        public String checkPersonInfo(@Valid FormTwo formTwo, BindingResult bindingResult, Model m) {
    
            if (bindingResult.hasErrors()) {
                return "Two";
            }
    
            m.addAttribute("bothForms", formTwo);
            return "Result";
        }
    }
    

    In summary I had to do the following:

    1. Create a GetMapping in the Controller in addition to the PostMapping I previously had to display the 2nd form. I think this mapping is called when the submit button is pressed to continue past the checkFormOneInfo() POST mappong on the first form.
    2. I also had to create a new FormTwo object and set the value which was captured in FormOne in it (this meant i need a field to hold this variable in FormTwo).

    My Results.html are addressed in the following page: Results.html

    <body>
        Congratulations! You Got here!
        <p th:text="'Name: ' + ${bothForms.name} + '!'" />
        <p th:text="'Age: ' + ${bothForms.age} + '!'" />
    </body>
    

    This works for what I need it to do. I would be interested if any gurus had better ways of doing this.

    Hope it helps someone in the future.