Thymeleaf Spring Boot: Binding form to object,some fields are empty? - spring-boot

So I am binding a form to an object to send to a spring controller,the "chosenAdvertisementsIds" being set with a hidden tag of ${advertisement.id} ends up with blank strings,the correct quantity of strings but empty strings.The tag for advertisement.description prints correctly on the page so the content is in the model correctly.
public class EditScheduleForm {
private String[] chosenAdvertisementsIds;
private String[] chosenAdvertisementsTime;//parralel arrays
private String dates;
//getters setters
}
My html,
<form th:action="#{|/web/editSchedule/${schedule.id}|}"
th:object="${EditScheduleForm}" method="post">
<h1>Advertisement Items</h1>
<div th:each="advertisement : ${chosenAdvertisements}">
<p th:text="${advertisement.description}"></p>
<input type="hidden" th:value="${advertisement.id}" th:field="*{chosenAdvertisementsIds}"/>
<p>Type below what times you want this advertisement to play at(type it like this 10:15/11:15/14:15 )</p>
<input name="text" th:field="*{chosenAdvertisementsTime}" type="text"/>
My controller method
#RequestMapping(value="/web/editSchedule/{scheduleId}",method = RequestMethod.POST)
public String editScheduleFormPost(Model model,
#ModelAttribute EditScheduleForm editScheduleForm,
#PathVariable Long scheduleId,
RedirectAttributes redirectAttributes) {
Advertisement
#Entity
public class Advertisement {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#ManyToOne
#JoinColumn(name = "usergroup_id")
private UserGroup userGroup;
private String description;
#Basic
#Temporal(TemporalType.DATE)
private java.util.Date dateAdded;
//getters setters
}
How I populate the model that the html page is on
#RequestMapping(value="/web/editschedule/{scheduleId}",method = RequestMethod.GET)
public String editOneSchedule(#PathVariable Long scheduleId,
Model model) {
// Get the schedult given by Id
Schedule schedule = scheduleService.findOne(scheduleId);
model.addAttribute("schedule",schedule);
ArrayList<Music> chosenMusics = new ArrayList<>();
for(int i=0;i<schedule.getMusicScheduleItems().size();i++){
chosenMusics.add(schedule.getMusicScheduleItems().get(i).getMusic());
}
model.addAttribute("chosenMusics",chosenMusics);
ArrayList<Advertisement> chosenAdvertisements = new ArrayList<>();
for(int i=0;i<schedule.getAdvertisementScheduleItems().size();i++){
chosenAdvertisements.add(schedule.getAdvertisementScheduleItems().get(i).getAdvertisement());
}
model.addAttribute("chosenAdvertisements",chosenAdvertisements);
model.addAttribute("EditScheduleForm", new EditScheduleForm());
return "editschedule";
}

Related

Thymeleaf form returns null values

when I submit the form, the pageBook.id and loggedUser.id values become null (in GetMapping method they have values). Any ideas why?
This is my form:
<div sec:authorize="isAuthenticated()">
<form th:action="#{/} + ${pageBook.id}" th:object="${transaction}" method="post">
<input type="hidden" th:field="${transaction.bookTransaction}" th:value="${pageBook.id}">
<input type="hidden" th:field="${transaction.userTransaction}" th:value="${loggedUser.id}">
<input type="submit" value="Submit!" />
</form>
</div>
My Entity:
#Entity
#Table(name = "transactions")
public class Transaction {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name="book_id", nullable=false)
private Book bookTransaction;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name="user_id", nullable=false)
private User userTransaction;
... consturctors getters and setters
and my controller (pageBook and loggedUser aren't null):
#GetMapping("/{id}")
public String showBookPage(#AuthenticationPrincipal UserDetails userDetails, #PathVariable("id") Long id, Model model, RedirectAttributes redirectAttributes){
try {
Book pageBook = bookService.getBookById(id);
User loggedUser = (User) userService.loadUserByUsername(userDetails.getUsername());
model.addAttribute("transaction", new Transaction());
model.addAttribute("pageBook", pageBook);
model.addAttribute("loggedUser", loggedUser);
} catch (BookNoFoundException e) {
redirectAttributes.addFlashAttribute("message", e);
}
return "users/users_book_page";
}
#PostMapping("/{id}")
public String newTransaction(#PathVariable("id") Long id, #ModelAttribute("transaction") Transaction transaction){
log.info(transaction.getUserTransaction());
log.info(transaction.getBookTransaction());
transactionService.newTransaction(transaction);
return "redirect:/" + id;
}

How to create confirmation dialog when error occurs

I would like to implement following functionality: If user write "wrong" PID, alert should show up saying: Do you really save this PID with invalid format? If user say yes, the whole "playerform" (if other fields are correct) will be saved to the database (eventhough that PID would be in incorrect format.)
So how can I achieve this? I would add #Patern(with right regex) and message - this message will be displayed to the user because of the template down below, but how can I add this kind of dialog (yes - save the form with invalid PID, no - do not save it).
Spring boot and thymeleaf are used.
Method in the controller
#PostMapping("/create")
public String createPlayer(#Valid #ModelAttribute("playerForm") PlayerForm playerForm,
BindingResult bindingResult,
Model model,
RedirectAttributes redirectAttributes) {
// Check for errors
if (bindingResult.hasErrors()) {
formViewPreparer.prepare(model, new PlayerFormViewPreparer.ParamObj(true, null));
return "admin/players/create";
}
// Prepare object for service layer
CreatePlayerIn createPlayerIn = CreatePlayerInMapper.fromPlayerForm(playerForm);
// Call service layer
playerService.createPlayer(createPlayerIn);
redirectAttributes.addFlashAttribute("message", "player.toast.created");
return "redirect:/admin/players";
}
Player form class
#Getter
#Setter
public class PlayerForm {
#NotBlank
private String firstName;
#NotBlank
private String lastName;
#NotNull
private GenericState playerState;
#DateTimeFormat(pattern = "yyyy-MM-dd")
private LocalDate birthDate;
private String PID;
private Long schoolId;
private Long teamId;
private String street;
private String city;
private String zipCode;
private String phoneNumber;
private String email;
private String motherPhoneNumber;
private String motherEmail;
private String fatherPhoneNumber;
private String fatherEmail;
#DateTimeFormat(pattern = "yyyy-MM-dd")
private LocalDate validityOfMedicalExam;
#NumberFormat(style = NumberFormat.Style.NUMBER)
#Min(0)
private Integer annualFee;
private String annualFeeNote;
#NumberFormat(style = NumberFormat.Style.NUMBER)
#Min(0)
private Integer firstSemiAnnualFee;
private String firstSemiAnnualFeeNote;
#NumberFormat(style = NumberFormat.Style.NUMBER)
#Min(0)
private Integer secondSemiAnnualFee;
private String secondSemiAnnualFeeNote;
create.html template utilizing thymeleaf
<form th:object="${playerForm}"
th:action="#{/admin/players/create}"
novalidate
enctype="multipart/form-data"
method="post">
<div class="form-group col-md-6">
<input type="text" th:field="*{PID}" class="form-control" th:errorclass="is-invalid"
th:placeholder="#{player.input.PID}"/>
<label for="PID" th:text="#{player.input.PID}" class="form-control-placeholder"></label>
<div th:if="${#fields.hasErrors('PID')}" class="invalid-feedback"
th:errors="*{PID}"></div>
</div>

Spring MVC Error: Failed to convert property value of type java.lang.String to required type

I can't let this exception go:
Failed to convert property value of type java.lang.String to required type com.company.springdemo.entity.Product for property productId; nested exception is java.lang.IllegalStateException: Cannot convert value of type java.lang.String to required type com.company.springdemo.entity.Product for property productId: no matching editors or conversion strategy found
Order Model
#Entity
#Table(name = "orders") // naming the table only order, will throw exception
public class Order {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "order_id")
private Integer orderId;
#OneToOne(cascade = {CascadeType.DETACH,CascadeType.MERGE,CascadeType.PERSIST,CascadeType.REFRESH})
#JoinColumn(name = "product_id")
private Product productId;
#ManyToOne(fetch = FetchType.EAGER, cascade = {CascadeType.DETACH,CascadeType.MERGE,CascadeType.PERSIST,CascadeType.REFRESH})
#JoinColumn(name = "client_id")
private Client client;
....
Product Model
#Entity
#Table(name = "product")
public class Product {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "product_id")
private Integer id;
#Column(name = "product_name")
private String productName;
#Column(name = "product_serial")
private String productSerial;
...
Client Model
#Entity
#Table(name = "clients")
public class Client {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
#NotEmpty
#Column(name = "first_name")
private String firstName;
#NotEmpty
#Column(name = "last_name")
private String lastName;
#NotEmpty
#Email
#Column(name = "email")
private String email;
#NotEmpty
#Column(name = "location")
private String location;
#OneToMany(mappedBy = "client",cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private List<Order> orders;
Controller, where I save the order with related client and product
#PostMapping("add")
public ModelAndView addOrder( #Validated #ModelAttribute("ords") Order order, BindingResult bindingResult ){
if (bindingResult.hasErrors()) {
System.out.println("Having errors: " + bindingResult.getAllErrors());
Iterable<Product> products = productService.listProducts();
Iterable<Client> clients = clientService.listClients();
System.out.println("Error "+ bindingResult.getAllErrors());
ModelAndView mv = new ModelAndView("orders/add-order");
mv.addObject("products",products);
mv.addObject("clients",clients);
return mv;
}
try {
orderService.saveOrder(order);
} catch (Exception e) {
e.printStackTrace();
}
ModelAndView mv = new ModelAndView("redirect:list");
return mv;
}
Finally, my JSP form View page
<form:form action="add" method="post" modelAttribute="ords">
<label for="productId" >Product Id</label>
<form:select path="productId" >
<c:forEach var="product" items="${products}">
<form:option value="${product.id}">${product.productName}</form:option>
</c:forEach>
</form:select>
<form:errors path="productId"/>
<br>
<label for="client" >Client Id</label>
<form:select path="client" >
<c:forEach var="client" items="${clients}">
<form:option value="${client.id}">${client.id} - ${client.lastName}</form:option>
</c:forEach>
</form:select>
<form:errors path="client"/>
<br>
<input type="submit" value="Place Order">
</form:form>
What am I doing wrong?
You most likely need to build a converter class such as this one :
#Component("facilityConverter")
public class FacilityConverter implements Converter<String, Facility>
{
#Autowired
FacilityService facilityService;
#Override
public Facility convert(String id)
{
return facilityService.findById(Integer.parseInt(id));
}
}
Then, you need to register it by implementing the addFormatters method inside of a configuration class implementing WebMvcConfigurer like so :
#Override
public void addFormatters (FormatterRegistry registry)
{
registry.addConverter((FacilityConverter)ctx.getBean("facilityConverter"));
}
Your entities will then correctly be mapped from a dropdown selection. Also, this might not be part of your issue but you can just build your dropdowns like this :
<form:select name="linkedInterface" path="linkedInterface" id="linkedInterface">
<form:options items="${interfaces}" itemLabel="name" itemValue="id"/>
</form:select>
The productId field is actually a Product object, not an ID (String/int). You need your JSP to use path="productId.id" rather than path="productId".
(Although I'd also suggest you also rename the field product rather than productId.)
<form:select path="product.id">
I think you'll hit the same issue on your <form:select path="client"> too.

Pass object in spring form input hidden SpringMVC

i have problem with pass object(CarType) in spring input form.
My Car model:
#Entity
#Table(name="CAR")
public class Car implements Serializable{
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name = "CAR_ID", unique=true, nullable=false)
private long id;
#NotEmpty
#Column(name = "REG_NO", nullable=false)
private String regNo;
#NotEmpty
#Column(name = "YEAR", nullable=false, length = 4)
private String year;
#Column(name = "AVAILABLE", nullable=false)
private boolean available = true;
#Column(name = "START_DATE")
private String startDate;
#Column(name = "RETURN_DATE")
private String returnDate;
#OneToOne
#JoinColumn(name="CAR_TYPE_ID")
private CarType carType;
//getters and setters
Car Type model:
#Entity
#Table(name = "CAR_TYPE")
public class CarType {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name = "CAR_TYPE_ID", unique=true, nullable=false)
private int id;
#NotEmpty
#Column(name = "MARK", nullable=false)
private String mark;
#NotEmpty
#Column(name = "MODEL", nullable=false)
private String model;
//getters and setters
Controllers:
#RequestMapping(value = { "/rent-car-{regNo}" }, method = RequestMethod.GET)
public String rentCar(#PathVariable String regNo, ModelMap model) {
Car car = carService.findCarByRegNo(regNo);
model.addAttribute("car", car);
return "rentcar";
}
#RequestMapping(value = { "/rent-car-{regNo}" }, method = RequestMethod.POST)
public String saveRentCar(#Valid Car car, BindingResult result, ModelMap model) {
carService.updateCar(car);
model.addAttribute("success", "Car " + car.getRegNo() + " rented successfully");
return "registrationsuccess";
}
JSP file
<form:form method="POST" modelAttribute="car" class="form-horizontal">
<form:input type="hidden" path="id" id="id"/>
<form:input type="hidden" path="year" id="year"/>
<form:input type="hidden" path="regNo" id="regNo"/>
<form:input type="hidden" path="available" id="available"/>
<form:input type="hidden" path="carType" id="carType"/>
<form:input type="text" path="startDate" id="startDate"/>
<form:input type="text" path="returnDate" id="returnDate"/>
I have problem with that code
<form:input type="hidden" path="carType" id="carType"/>
how can i pass object CarType to Car form? I always have that same error: column 'CAR_TYPE_ID' cannot be null. It looks like I'm transferring a null CarType to Car. I dont know why?
Someone can help me? In registercar.jsp i used converter (convert regNo to class CarType) and its works.
You need to know how the Car object gets bound in the Controller handler method from http request from the client. Spring MVC maps the the request parameters to contruct the Car object. Hence the CarType is an associated object. You need to provide a minimal clue to Spring MVC to construct that for you.
<form:form method="POST" action="/rent-car${regNo}" modelAttribute="car">
......
<form:input type="hidden" path="carType.id"/>
<form:input type="hidden" path="carType.model"/>
<form:input type="hidden" path="carType.mark"/>
<input type="submit" value="Submit"/>
</form:form>
you will find a complementary example here is this article Spring MVC - Binding Request Parameters and Path Variables to Java Backing Objects

Error when a form with a select field is submitted

In my current spring project, my forms are implement with a structure like this:
<form class="form" id="Pagina" role="form" method="POST" action="/loja/Pagina/cadastra" enctype="multipart/form-data">
...
</form>
and it's processed in server by this methos:
controller
#RequestMapping(value="cadastra", method=RequestMethod.POST)
#ResponseBody
#ResponseStatus(HttpStatus.CREATED)
public E cadastra(#ModelAttribute("object") E object, BindingResult result, #RequestParam(value="file", required=false) MultipartFile file, #RequestParam(value="icone", required=false) MultipartFile icone, #RequestParam(value="screenshot", required=false) MultipartFile screenshot[]) throws Exception {
E ret = serv.cadastra(object, file, icone, screenshot);
if (ret != null)
return ret;
else
throw new Exception();
}
service
#PreAuthorize("hasPermission(#user, 'cadastra_'+#this.this.name)")
#Transactional
public E cadastra(E e, MultipartFile file, MultipartFile icone, MultipartFile[] screenshot) {
return dao.persist(e);
}
My problem it's when the form have a field like this:
<label>pagina</label>
<select name="pagina.id" class="form-control select" data-lista="/loja/Pagina/listagem.json">
...
</select>
<label>produto</label>
<select name="produto.id" class="form-control select" data-lista="/loja/Produto/listagem.json">
...
</select>
which maps a atribute like this in the entiy class:
#OneToOne(fetch = FetchType.EAGER)
#JoinColumn(name="pagina_mae", nullable = true)
#Order(value=5)
#Select(name="pagina", ordem = 5)
#Sidebar
private Pagina pagina;
#OneToOne(fetch = FetchType.EAGER)
#JoinColumn(name="produto_mae", nullable = true)
#Order(value=6)
#Select(name="produto", ordem = 6)
#Sidebar
private Produto produto;
Where the options inside looks like this:
<option value="">.</option>
<option value="...">...</option>
If I submit the form with the blank option selected, I get this error:
object references an unsaved transient instance - save the transient instance before flushing: com.spring.loja.model.pagina.persistence.model.Pagina; nested exception is org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: com.spring.loja.model.pagina.persistence.model.Pagina
but if, for instance, insert a record manually in the database (in my case, using pgAdmin3), and select this item in the select, the form is submitted without errors.
Anyone can tell me how I fix that, to allow me submit the form with or without selected data from the <select>.
UPDATE
code for the class Pagina:
#Entity
#Table(name="pagina")
#MainForm(grupo = 2, icone = "file")
public class Pagina extends ModelEntity {
#Id
#Column(name = "id")
#GeneratedValue(strategy=GenerationType.IDENTITY)
private Integer id;
#Column(name = "nome", unique = true)
#Order(value=1)
#Input(type="hidden", name="nome", ordem = 1)
private String nome;
#Column(name = "titulo", nullable = false)
#Order(value=2)
#Input(name="titulo", ordem = 2)
private String titulo;
#Column(name = "descricao", length=65535)
#Order(value=4)
#Textarea(name="descricao", ordem = 4)
private String descricao;
#OneToOne(fetch = FetchType.EAGER)
#JoinColumn(name="pagina_mae", nullable = true)
#Order(value=5)
#Select(name="pagina", ordem = 5)
#Sidebar
private Pagina pagina;
#OneToOne(fetch = FetchType.EAGER)
#JoinColumn(name="produto_mae", nullable = true)
#Order(value=6)
#Select(name="produto", ordem = 6)
#Sidebar
private Produto produto;
}
UPDATE 2
PaginaEditor.java
#Component
public class PaginaEditor extends PropertyEditorSupport {
#Inject
private PaginaService paginaService;
#Override
public void setAsText(String text) {
if (!text.isEmpty()) {
Pagina pagina = paginaService.getObject(text);
setValue(pagina);
}
}
}
method added to my controller:
#InitBinder
public void initBinder(WebDataBinder binder) {
binder.registerCustomEditor(Pagina.class, new PaginaEditor());
}
Selects are tricky ones in Spring MVC.
I think your problem is that when your main entity is getting to the data layer to be persisted, the relationship is not there.
Try to debug and check if affirmation above is true.
There are two ways to get this sorted.
Let's assume a Company / Contact relationship in a contacts system.
A company has many contacts and a contact has one company.
Company snippet.
// package declaration imports and all
#Entity
#Table(name = "company")
public class Company {
private String name;
#OneToMany(mappedBy = "company")
private List<Contact> contacts = new ArrayList<Contact>();
// getter and setters and any extra stuff you fancy putting here
}
Contact snippet
// package declaration imports and all
#Entity
#Table(name = "contact")
public class Contact {
private String name;
#ManyToOne
private Company company;
// getter and setters and any extra stuff you fancy putting here
}
And a jsp snippet with the select.
We assume there is a "contact" object and a list of customers in the model.
<form:form modelAttribute="contact">
<form:input path="name" />
<form:select path="customer" items="${customers}" itemValue="id" itemLabel="name" />
</form:form>
With this code in place, you can use a PropertyEditor like this.
#Component
public class CustomerEditor extends PropertyEditorSupport {
#Inject
private CustomerService customerService;
#Override
public void setAsText(String text) {
if (StringUtils.isNotBlank(text)) {
Customer customer = this.customerService.findById(Integer
.valueOf(text));
this.setValue(customer);
}
}
}
and register in your Spring Context like this.
#InitBinder
public void initBinder(WebDataBinder binder) {
binder.registerCustomEditor(Customer.class, this.customerEditor);
}
What happens here is that, whenever Spring finds an Object of type Customer, it will use the PropertyEditor to convert the (in this case) Id to the object and by the type the contact (in this case) gets to the data layer (Hibernate), the proper Customer entity will be there waiting as happy as Larry.
That is automagic way to do it.
Another way to do it is to create a form/DTO or whatever you would like to call and add the fields including a customerId field (in this case) and convert your self before you save your entity.
I hope I understood your problem right because it took me quite a few minutes to write this... :)

Resources