In the post "Hello World" phase of my Spring journey. Creating an app that POSTS two fields which RESULT in three fields.
My controller has three fields. Two are populated on POST, and all three should populate the RESULT fields.
The Controller:
#Slf4j
#Controller
public class GreetController {
#GetMapping("/greeting")
public String greetingForm(Model model) {
model.addAttribute("greeting", new Greeting());
}
#PostMapping("/greeting")
public String greetingSubmit(#ModelAttribute Greeting greeting, Model model) {
model.addAttribute("greeting", greeting);
}
}
The Model:
public class Greeting {
//fields correspond to greeting.html
private long id;
private String content;
private String numbah;
public String getNumbah() { return numbah; }
public void setNumbah() {
this.numbah = SomeFunctions.functionOne(this.getContent());
}
public long getId() { return id; }
public void setId(long id) { this.id = id; }
public String getContent() { return content; }
public void setContent(String content) { this.content }
}
The View:
<!DOCTYPE HTML>
this is greeting.html
<html xmlns:th="https://www.thymeleaf.org">
<head>
<title>Getting Started: Handling Form Submission</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
<h1>Form</h1>
<!--th.fields correspond to fields in th.object (ie greeting.java)-->
<form action="#" th:action="#{/greeting}" th:object="${greeting}" method="post">
<p>Id: <input type="text" th:field="*{id}" /></p>
<p>Message: <input type="text" th:field="*{content}" /></p>
<p><input type="submit" value="Submit" /> <input type="reset" value="Reset" /></p>
</form>
<h1>Result</h1>
<p th:text="'id: ' + ${greeting.id}" />
<p th:text="'content: ' + ${greeting.content}" />
<p th:text="'numbah: ' + ${greeting.numbah}" />
Submit another message
</body>
</html>
Note:
The ${greeting.numbah} field, as configured above, is resulting in null. I had a log statement in that function that never returned anything, indicating that the function in the model is never firing. However, when the view was configured like this...
<body>
<h1>Form</h1>
<!--th.fields correspond to fields in th.object (ie greeting.java)-->
<form action="#" th:action="#{/greeting}" th:object="${greeting}" method="post">
<p>Id: <input type="text" th:field="*{id}" /></p>
<p>Message: <input type="text" th:field="*{content}" /></p>
<p>Some Number: <input type="text" th:field="*{numbah}"></p>
<p><input type="submit" value="Submit" /> <input type="reset" value="Reset" /></p>
</form>
<h1>Result</h1>
<p th:text="'id: ' + ${greeting.id}" />
<p th:text="'content: ' + ${greeting.content}" />
<p th:text="'numbah: ' + ${greeting.numbah}" />
Submit another message
</body>
...the field was populating, so the function itself....functions.
Long short, what I'm attempting to do here is take id and content, do someFunction(content)=numbah and return id, content, and numbah.
Thank you in advance.
It always gets down to something simple, doesn't it?
There was a typo in the setNumbah constructor in the model.
This is why I hardly ever post on sites like this: because you almost always figure out what you did wrong if you just sleep on it and have another look.
Thanks for checking this out and apologies that it's not a "high quality" problem/answer. Will delete if that's the protocol for a self-answered question.
Related
I am building an App where a new website can be added to the list and I would now like to be able to redirect the user to this given website. How do I do that?
For example, a user can add to the list: www.example.com. Clicking the link (inside Index.html) will take the user to the example homepage.
index.html is where I would like the link to appear to the user
<td><a th:href="#{'/website.link'}">Link</a></td>
new.html page can add links
<div alight="left">
<tr>
<label class="form-label">Link</label>
<td><input type="text" th:field="*{link}" class="form-control"
placeholder="Link" /></td>
</tr>
</div>
Notice I don't have anything in the Controller yet.
Consider a Contr-application like:
#Controller
#SpringBootApplication
public class Application {
private final List<URI> allLinks = Collections.synchronizedList(new ArrayList<>());
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
#ModelAttribute("allLinks")
public List<URI> allLinks() {
return allLinks;
}
#GetMapping("/")
public String index() {
return "index";
}
#PostMapping(value = "/", params = "send")
public void sendMeThere(#RequestParam String link, HttpServletResponse response) throws IOException {
// response send redirect! (relative/absolute/throws exception)
response.sendRedirect(link);
}
#PostMapping(value = "/")
public String addLink(#RequestParam String link) throws URISyntaxException {
links.add(new URI(link)); // throws exception!
return "redirect:/";
}
}
We can try it out with an index.html like this:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org" lang="en">
<head>
<title>Hello Links</title>
</head>
<body>
<h2>Add More Lnks</h2>
<form action="" th:action="#{/}" method="post">
<label>Link</label>
<input type="text" name="link"
placeholder="http://www.example.com" />
<input type="submit" value="Add Link" />
</form>
<hr/>
<h2>Naviagte via form submit</h2>
<form action="" th:action="#{/}" method="post">
<select name="link">
<option th:each="link : ${allLinks}" th:value="${link}" th:text="${link}" />
</select>
<input type="submit" value="Send me There" />
<input type="hidden" name="send" />
</form>
<hr/>
<h2>Naviagte via link</h2>
<ul>
<li th:each="link : ${allLinks}">
<!-- encapsulate ${link} in thymeleaf url #{...} (relative or absolute) -->
<a th:href="#{${link}}" th:text="${link}"/>
</li>
</ul>
</body>
</html>
It looks like:
References:
HttpServletResponse#sendRedirect
Thymeleaf URL Syntax
Spring Boot 2.5, Thymeleaf
I need when click submit to pass object Product and additional extra param (quantity)
html template:
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title th:text="${appName}">Category template title</title>
<link th:href="#{/public/style.css}" rel="stylesheet"/>
<meta charset="UTF-8"/>
</head>
<body>
<div class="container">
<h3 th:text="*{title}"/>
<form method="post" action="#" th:object="${product}" th:action="#{/product}">
<input type="hidden" id="id" th:field="*{id}"/>
<input type="text" placeholder="Name" id="name" th:field="*{name}" th:disabled="${isView}"/>
<input type="hidden" id="created" th:field="*{created}"/>
<textarea placeholder="Description" rows="5" id="description"
th:field="*{description}" th:disabled="${isView}"></textarea>
<input type="number" placeholder="Price" id="price" th:field="*{price}" th:disabled="${isView}"/>
<input type="text" placeholder="Currency" id="currency" th:field="*{currency}" th:disabled="${isView}"/>
<input type="text" placeholder="Images URL(separate by comma)" id="images" th:field="*{images}" th:disabled="${isView}"/>
<input th:type="${isView} ? hidden : submit" value="Submit"/>
</form>
</div>
</body>
</html>
and here my controller:
#RequestMapping("cart/add")
public String addProduct(Model model) {
logger.info("addProduct");
model.addAttribute("isAdd", true);
model.addAttribute("product", new Product());
model.addAttribute("title", "Add Product");
model.addAttribute("viewMode", ViewMode.ADD);
return "product";
}
#PostMapping(value = "/product")
public String submitProduct(Product product, Model model) {
logger.info("submitProduct = " + product);
if (product.getId() == 0) { // add category
product.setCreated(new Date());
} else { // update category
product.setUpdated(new Date());
}
return "redirect:/cart";
}
So when click button Submit call submitProduct with fill object Product. But I need to pass extra param (as second param in method submitProduct) - quantity.
How I can pass this extra int param from html to controller?
One option is to access the value directly from the request parameters.
Assuming the quantity value is available in the form as an input field, with a name of quantity (looks like it is not there at the moment), then you can alter your controller to use this:
import org.springframework.web.bind.annotation.RequestParam;
And then change the relevant method signature to something like this:
public String submitProduct(Product product, Model model,
#RequestParam(name = "quantity") String quantity) {...}
(Field validation of some kind would also be needed, I assume.)
I have an Employee class that looks like this.
public class Employee {
private String Name;
private List<Address> address;
*****Getters and Setters****
}
And my Address class looks like this
public class Address {
private int addressid;
private Employee employee;
#NotNull(message="Field Cannot be Empty")
private String description;
*****Getters and Setters****
}
I want to bind List of addresses (Employee can have more than one address - 1:M) to the employee class with the data that is parsed through the form. Which looks like this.
I have 3 input address fields, one of the sample input fields look like this...
<div class="form-group">
<div class="row">
<label class="col-sm-3" for="exampleInputEmail1">Address
1</label>
<div class="col-sm-7">
<form:input class="form-control" placeholder="" path="" />
<form:errors path="" cssClass="error" />
</div>
</div>
</div>
Note that I have cut down many unnecessary form fields to demonstrate the problem more clearly. Please help me bind the list of input fields with the relevant class. Any advice for this design is also welcome.
You can use Thymeleaf for this as below.
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
<head>
<meta charset="UTF-8">
<title>form|data</title>
</head>
<body>
<div class="starter-template">
<form th:action="#{employeeData}" method="post" th:object="${employee}">
<input th:field="*{name}" type="text" th:name="name" name="name" placeholder="name" class="input u-one-third js-get-form-data"/>
<th:block th:each="add,addStat:*{address}">
<input th:field="*{address[__${addStat.index}__].description}" type="text" th:name="add.description" name="description" placeholder="description" class="input u-one-third js-get-form-data"/>
</th:block>
<input type="submit" value="SEND"/>
</form>
</div>
</body>
</html>
Controller.
#RequestMapping(value = "/formData", method = RequestMethod.GET)
public String formData(Map<String, Object> model) {
Employee employee = new Employee();
Address address = new Address();
address.setDescription("TEST");
address.setEmployee(employee);
employee.getAddress().add(address);
model.put("employee",employee);
return "formData";
}
#RequestMapping(value = "/employeeData", method = RequestMethod.POST)
public void employeeData(#Valid Employee employeeData, BindingResult errors) {
System.out.println(employeeData.getName());
}
Please find the working commit here.
I have a simple snippet of form like:
<form th:action="#{'/save'}" th:object="${account}" method="post">
<div class="form-group">
<label for="expirationDate">Expiration date</label>
<input type="datetime-local" class="form-control" id="expirationDate"
placeholder="Expiration date" th:field="*{expirationTime}"/>
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
I'm passing there a filled object account and expirationTime is a LocalDateTime field. The problem is that the expirationTime is not bind with the value passed to the form (object which is passed is 100% correct).
Any idea why?
Edit: Simply put, Spring/Thymeleaf doesn't format a Java 8 date correctly for the datetime-local input type. Tell Spring how to format the value correctly with #DateTimeFormat(pattern = "yyyy-MM-dd'T'HH:mm").
The annotation #DateTimeFormat tells Thymeleaf how to format the date string when parsing/formatting. That includes producing the value= annotation in the HTML input, and reading in POST data submitted by the form. The datetime-local input type expects a value formatted yyyy-MM-dd'T'HH:mm, so we use that as the formatter in the model class.
Model Class:
public class DateContainer {
private String name;
#DateTimeFormat(pattern = "yyyy-MM-dd'T'HH:mm")
private LocalDateTime dateTime;
public LocalDateTime getDateTime() {
return dateTime;
}
public void setDateTime(LocalDateTime dateTime) {
this.dateTime = dateTime;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Controller:
#RequestMapping("/dateTest")
public String dateTest(final DateContainer dateContainer) {
if (dateContainer.getDateTime() == null) {
dateContainer.setDateTime(LocalDateTime.now());
}
return "dateTest";
}
Template:
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org/">
<head></head>
<body>
<h1>Hello World!</h1>
<form th:action="#{/dateTest}" th:object="${dateContainer}">
<label>Name: </label><input type="text" th:field="*{name}"/>
<label>Date Time: </label><input type="datetime-local" th:field="*{dateTime}"/>
<input type="submit" value="Submit"/>
</form>
</body>
</html>
HTML Produced:
<!DOCTYPE html>
<html>
<head></head>
<body>
<h1>Hello World!</h1>
<form action="/dateTest">
<label>Name: </label><input type="text" id="name" name="name" value="" />
<label>Date Time: </label><input type="datetime-local" id="dateTime" name="dateTime" value="2017-08-28T13:01" />
<input type="submit" value="Submit" />
<input type="hidden" name="_csrf" value="437b30dc-a103-44d0-b4e9-791d8de62986" /></form>
</body>
</html>
Screenshot:
Screenshot in Chrome 60
Comptibility:
The datetime-local input type is a new HTML5 type and isn't supported in all browsers. See compatibility: https://caniuse.com/#search=datetime-local
In non-compliant browsers, the datetime-local field will simply appear as a text field, and will contain the date and time with the T in between. That may lead to usability issues depending on your use case.
Make sure that you have this dependency:
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-java8time</artifactId>
<version>2.1.0.RELEASE</version>
</dependency>
After that just add the org.thymeleaf.extras.java8time.dialect.Java8TimeDialect class to the list of dialects in your TemplateEngine implementation, and you will have the #temporals object available to be used in your templates.
More details can be found here.
Hihi,
Having trouble model binding with this class (the following concrete, not the abstract).
Ignore the basic properties, its the Lists that I'm interested in binding.
public abstract class MessageModel
{
public string Tag { get; set; }
public string Message { get; set; }
public int Id { get; set; }
public const int DefaultIdValue = Int32.MinValue;
public List<LinkModel> Linked { get; set; }
public List<LinkModel> NotLinked { get; set; }
protected MessageModel()
{
Id = DefaultIdValue;
Linked = new List<LinkModel>();
NotLinked = new List<LinkModel>();
}
protected MessageModel(string tag, string message):this()
{
Tag = tag;
Message = message;
}
}
public class TextModel:MessageModel
{
public int TextId { get; set; }
public TextModel()
{
TextId = DefaultIdValue;
}
}
This is the submission I get on the server side on submit (formatted for sanity):
Tag=
&Message=
&NotLinked.index=35fda83a053645e6809bbb8b0ea00103
&NotLinked.index=14c2e286e28b4c9d8f889fb3eb437e5f
&NotLinked.%5b35fda83a053645e6809bbb8b0ea00103%5d.RecipientId=1
&NotLinked.%5b35fda83a053645e6809bbb8b0ea00103%5d.RecipientName=Bob+Biggins
&NotLinked.%5b14c2e286e28b4c9d8f889fb3eb437e5f%5d.RecipientId=2
&NotLinked.%5b14c2e286e28b4c9d8f889fb3eb437e5f%5d.RecipientName=Billy+Oswold
&Submit=Submit
When the function is called that accepts that model the NotLinked collection is set to null. D:
The (relevant) output html looks like this (im trying to "faux" bind to:
<ol> and <li>
with jQuery doing the work of moving stuff around)
<div id="NotLinkedContainer">
<ol id="NotLinked" name="NotLinked" style="width: 500px;height: 200px">
<li value="1">Bob Biggins
<input id="NotLinked_index" name="NotLinked.index" type="hidden" value="a0ab331bee2a461084b686e13a87090b" />
<input id="NotLinked__a0ab331bee2a461084b686e13a87090b__RecipientId" name="NotLinked.[a0ab331bee2a461084b686e13a87090b].RecipientId" type="hidden" value="1" />
<input id="NotLinked__a0ab331bee2a461084b686e13a87090b__RecipientName" name="NotLinked.[a0ab331bee2a461084b686e13a87090b].RecipientName" type="hidden" value="Bob Biggins" />
</li>
<li value="2">Billy Oswold
<input id="NotLinked_index" name="NotLinked.index" type="hidden" value="d7d294d3174c4bd98d583e92010359e7" />
<input id="NotLinked__d7d294d3174c4bd98d583e92010359e7__RecipientId" name="NotLinked.[d7d294d3174c4bd98d583e92010359e7].RecipientId" type="hidden" value="2" />
<input id="NotLinked__d7d294d3174c4bd98d583e92010359e7__RecipientName" name="NotLinked.[d7d294d3174c4bd98d583e92010359e7].RecipientName" type="hidden" value="Billy Oswold" />
</li>
</ol>
</div>
Any ideas? Haven't done this sort of complex binding before so I'm at a loss at the simple mistake I've probably made.
Try removing the dot notation before the brackets and since this isn't a dictionary, but a list, you need to use indices, not keys. The correct syntax for a list in a razor view should look more like this:
<div id="NotLinkedContainer">
<ol id="NotLinked" name="NotLinked" style="width: 500px;height: 200px">
<li value="1">Bob Biggins
<input id="NotLinked_index" name="NotLinked.index" type="hidden" value="a0ab331bee2a461084b686e13a87090b" />
<input id="NotLinked__a0ab331bee2a461084b686e13a87090b__RecipientId" name="NotLinked[0].RecipientId" type="hidden" value="1" />
<input id="NotLinked__a0ab331bee2a461084b686e13a87090b__RecipientName" name="NotLinked[0].RecipientName" type="hidden" value="Bob Biggins" />
</li>
<li value="2">Billy Oswold
<input id="NotLinked_index" name="NotLinked.index" type="hidden" value="d7d294d3174c4bd98d583e92010359e7" />
<input id="NotLinked__d7d294d3174c4bd98d583e92010359e7__RecipientId" name="NotLinked[1].RecipientId" type="hidden" value="2" />
<input id="NotLinked__d7d294d3174c4bd98d583e92010359e7__RecipientName" name="NotLinked[1].RecipientName" type="hidden" value="Billy Oswold" />
</li>
</ol>
</div>
Here is an article that covers what you are attempting to do:
http://haacked.com/archive/2008/10/23/model-binding-to-a-list.aspx