Binding form request with ManyToOne field on play framework 2 - java

I get this error when i add a manytoone field to my Model "Item" & i try to bind the corresponding form.
Execution exception
[IllegalStateException: No value] at line 31
=> Item item = itemForm.bindFromRequest().get();
the "Item" Model:
package models;
#Entity
public class Item extends Model {
#Id
public Long id;
#ManyToOne
#Constraints.Required
#Formats.NonEmpty
public Category category;
#Constraints.Required
public String title;
#Constraints.Required
public String content;
public String picture;
(..)
}
the form View :
#helper.form(action = routes.Application.newItem(), 'id -> "item_form", 'method -> "POST", 'enctype -> "multipart/form-data"){
<fieldset>
#helper.inputText(
itemForm("title"),
'_label -> "Titre" )
#helper.select(
itemForm("category"),
helper.options(Category.list),
'_label -> "Categorie")
#helper.textarea(
itemForm("content"),
'_label -> "Description")
#helper.inputFile(
field = itemForm("picture"),
'_display -> "Attachment",
'_label -> Messages("Image") )
<input type="submit" value="Ajouter">
</fieldset>
}
the controller:
public static Result newItem(){
Item item = itemForm.bindFromRequest().get(); //SOMETHING GO WRONG HERE
MultipartFormData body = request().body().asMultipartFormData();
FilePart picture = body.getFile("picture");
if (picture != null) {
(...)
}
else{
(...)
}
}

The form view for category field should be, considering Category model has id field.
#helper.select(
itemForm("category.id"),
helper.options(Category.list),
'_label -> "Categorie")

Related

Saving an object with foreign key does not return ID in database

I have a cinema reservation system where user can add movies and then they can add date to each movie. It works fine, but when a user adds a date, there is no error, but it saves to the database with movie_id = null.
How can I solve it?
Movie.java
#Data
#Entity
public class Movie {
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Id
#Column(name = "id")
private Long id;
#Column(unique = true)
private String title;
private String category;
#Column( columnDefinition = "TEXT")
private String description;
private Integer lenght;
private Integer minAge;
#Column(columnDefinition = "TEXT")
private String imageUrl;
#OneToMany(mappedBy = "movie", orphanRemoval = true)
private List<Repertoire> repertoires;
public Movie() {
}
}
Repertoire.java
#Data
#Entity
public class Repertoire {
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Id
#Column(name = "id")
private Long id;
#DateTimeFormat(pattern = "yyyy-MM-dd'T'HH:mm")
private LocalDateTime date;
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "movie_id")
private Movie movie;
}
MovieController.java
#Controller
#RequestMapping("/movies")
public class MovieController {
private MovieRepo movieRepo;
private RepertoireRepo repertoireRepo;
#Autowired
public MovieController(MovieRepo movieRepo, RepertoireRepo repertoireRepo) {
this.movieRepo = movieRepo;
this.repertoireRepo = repertoireRepo;
}
#GetMapping("showForm")
public String showStudentForm(Movie movie) {
return "add-movie";
}
#GetMapping("list")
public String getMovies(Model model) {
model.addAttribute("movies", movieRepo.findAll());
return "movieIndex";
}
#PostMapping("add")
public String movies(#Validated Movie movie, BindingResult result, Model model) {
if(result.hasErrors()) {
return "add-movie";
}
movieRepo.save(movie);
return "redirect:/movies/list";
}
#GetMapping("edit/{id}")
public String showUpdateForm(#PathVariable ("id") long id, Model model) {
Movie movie = movieRepo.findById(id)
.orElseThrow(() -> new IllegalArgumentException("Nieprawidłowe ID: " + id));
model.addAttribute("movie", movie);
return "update-movie";
}
#PostMapping("update/{id}")
public String updateMovie(#PathVariable("id") long id, #Validated Movie movie, BindingResult result, Model model) {
if(result.hasErrors()) {
movie.setId(id);
return "update-movie";
}
movieRepo.save(movie);
model.addAttribute("movies", movieRepo.findAll());
return "movieIndex";
}
#GetMapping("delete/{id}")
public String deleteMovie(#PathVariable ("id") long id, Model model) {
List<Repertoire> repertoires = repertoireRepo.findByMovieId(id);
repertoires.forEach(r -> repertoireRepo.deleteById(r.getId()));
Movie movie = movieRepo.findById(id)
.orElseThrow(() -> new IllegalArgumentException("Nieprawidłowe ID : " + id));
movieRepo.delete(movie);
model.addAttribute("movies", movieRepo.findAll());
return "movieIndex";
}
// HERE'S WHERE I ADD THE TIME:
#GetMapping("/admin/{movieName}/newRepertoire")
public String showRepertoireForm(Model model, #PathVariable ("movieName") String movieName) {
Movie movieRepertoire = movieRepo.findByTitle(movieName);
model.addAttribute("movieRepertoire", movieRepertoire);
model.addAttribute("repertoire", new Repertoire());
return "repertoire";
}
#PostMapping("/admin/newRepertoire")
#Transactional
public String addRepertoire(#ModelAttribute ("repertoire") Repertoire repertoire,
#ModelAttribute("movieRepertoire") Movie movie, BindingResult result) {
// if(result.hasErrors()) {
// return "repertoire";
// }
repertoire.setMovie(movieRepo.findByTitle(movie.getTitle()));
repertoireRepo.save(repertoire);
return "redirect:/movies/list";
}
}
RepertoireRepo.java
#Repository
public interface RepertoireRepo extends JpaRepository<Repertoire, Long> {
List<Repertoire> findByMovieId(Long movieId);
}
MovieRepo.java
#Repository
public interface MovieRepo extends JpaRepository<Movie, Long> {
Movie findByTitle(String title);
}
repertoire.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
<head>
<meta charset="UTF-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<title>c</title>
</head>
<body>
<div class="container my-5">
<div class="card">
<div class="card-body">
<div class="col-md-10">
<h1 th:text="${movieRepertoire.title}"> MovieName</h1>
<form action="#" th:action="#{/movies/admin/newRepertoire}" th:object="${repertoire}" method="post">
<div class="row">
<div class="form-group col-md-8">
<label for="date" class="col-form-label">Date</label>
<input type="datetime-local" th:field="*{date}" class="form-control" id="date" value="2021-01-20T13:01">
<span th:if="${#fields.hasErrors('date')}" th:errors="*{date}" class="text-danger"></span>
</div>
<div class="col-md-6">
<input type="submit" class="btn btn-primary" value="Add">
</div>
<div class="form-group col-md-8"></div>
</div>
<!-- <input type = "hidden" th:value="${movieRepertoire}">-->
</form>
</div>
</div>
</div>
</div>
</body>
</html>
Movie structure:
id | category | description | imageurl| lenght| minage| title
-------------------------
36 | Action | Simple desc. | photo.jpg | 137 | 7 | Iron Man |
Repertoire structure:
id | date | movie_id
-------------------------
37 | 2021-01-01 14:00:00 | null |
Both sides of the relationship must be updated and you should cascade save from the parent. Your code:
repertoire.setMovie(movieRepo.findByTitle(movie.getTitle()));
repertoireRepo.save(repertoire);
Should become:
Movie movie = movieRepo.findByTitle(movie.getTitle());
movie.getRepertoires().add(repertoire);
repertoire.setMovie(movie);
session.saveOrUpdate(movie);
In fact really you should add a helper method to Movie that performs both actions to ensure both sides of the relationship are always in sync:
public void addRepertoire(Repertoire repertoire) {
repertoires.add(repertoire);
repertoire.setMovie(this);
}

How to iterate input value in thymeleaf

I'm trying to iterate an input field thru GET in Java Controller and thymeleaf but it's giving me the error of "result returns more than one elements"
My GET can receive the Array of String ID's but I can't put them in my Form, I will use my Form to POST it and delete the Array of String ID's. It's working if the user input is only one but when the user input becomes 2 or more it shows the error "result returns more than one elements"
This is my code on Java Controller
#RequestMapping(value="/delete-asset", method = RequestMethod.GET)
public String deleteAsset(#RequestParam String[] assetID, Model model) {
//to populate the form
model.addAttribute("DeleteCategoryObject", assetService.getAssetInfo(assetID));
return "delete-asset";
}
This is my code on Html Form
<form method="POST" name="deleteFormAdd" id="deleteFormAdd" th:object="${DeleteCategoryObject}"enctype="multipart/form-data">
<input type="hidden" name="_csrf" th:value="${_csrf.token}" />
//The code can't iterate the String of array given by the GET
<input type="hidden" th:field="*{assetID}" />
//This button is for submitting the String of array ID to be deleted
<input type="button" class="btn btn-primary btn-block" value="Yes" th:onclick="'javascript:submitForm(\'deleteFormAdd\',\''+#{/delete-asset}+'\')'" />
<button type="reset" onclick="window.location.href = 'manage-assets.html';" class="btn btn-default btn-block"> Cancel</button>
</form>
The code can accept this link
But it shows the error "result returns more than one elements" When the given ID is 2 or more
Full Error
org.springframework.dao.IncorrectResultSizeDataAccessException: result returns more than one elements; nested exception is javax.persistence.NonUniqueResultException: result returns more than one elements
Java DAO/Query
#Query("From AssetCategory A WHERE A.assetID IN (:assetID)")
public AssetCategory getAssetInfo(#Param("assetID") String[] assetID);
AssetCategory
#Entity
#Table(name = "ASSET_TYPE")
public class AssetCategory implements Serializable{
private static final long serialVersionUID = 595169758417136780L;
#Id
#Column(name = "ASSET_ID")
#GenericGenerator(name="uuid", strategy="uuid")
#GeneratedValue(generator="uuid")
private String assetID;
#Column(name = "ASSET_TYPE")
private String assetType;
#Column(name = "CATEGORY")
private String category;
#Column(name = "DESCRIPTION")
private String description;
#Column(name = "CREATED_BY")
private String createdBy;
public AssetCategory() {
super();
public AssetCategory(String assetID, String assetType, String category, String description, String createdBy) {
super();
this.assetID = assetID;
this.assetType = assetType;
this.category = category;
this.description = description;
this.createdBy = createdBy;
}
//getters setters below
public String getAssetID() {
return assetID;
}
public void setAssetID(String assetID) {
this.assetID = assetID;
}
public String getAssetType() {
return assetType;
}
public void setAssetType(String assetType) {
this.assetType = assetType;
}
public String getCategory() {
return category;
}
public void setCategory(String category) {
this.category = category;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public String getCreatedBy() {
return createdBy;
}
public void setCreatedBy(String createdBy) {
this.createdBy = createdBy;
}
Controller for Java Delete Post
#RequestMapping(value = "/delete-asset", method = RequestMethod.POST)
public #ResponseBody String deleteAsset(#ModelAttribute AssetCategory assetCategory) {
JsonObject result = new JsonObject();
assetService.deleteAssets(assetCategory.getAssetID());
result.addProperty("result", "Success");
result.addProperty("status", 1);
result.addProperty("message", "Asset Deleted!");
return result.toString();
}
Query of Delete POST
#Query("Delete From AssetCategory A WHERE A.assetID IN (:assetID)")
List<AssetCategory> deleteAssets(#Param("assetID") String[] assetID);
Delete POST Service
#Override
public List <AssetCategory> deleteAssets(String[] assetID) {
return dao.deleteAssets(assetID);
}
Submit Form function
function submitForm(formID, url){
var formData = new FormData($("#" + formID)[0]);
$.ajax({
dataType: 'json',
url: url,
data : formData,
type : "POST",
enctype : "multipart/form-data" ,
processData : false,
contentType : false,
success : function(data) {
if (data.status == 1) {
openAlertDialog("Success", "The Asset type has been deleted!", "Continue", "manage-assets");
} else {
openAlertDialog("Error", data.message, "Continue", "manage-assets");
}
},
error : function(data) {
openAlertDialog("Error", data.message, "Continue", "manage-assets");
},
});
}
Problem is with your query. When you provide array of assetID Query trying to return List of AssetCategory objects. But currently method allows to return only one AssetCategory object. That's why the error comes when you pass array of assetID. So you have to change the return type of getAssetInfo method as below:
#Query("From AssetCategory A WHERE A.assetID IN (:assetID)")
public List<AssetCategory> getAssetInfo(#Param("assetID") String[] assetID);
Now getAssetInfo method returns List of AssetCategory objects. Which means you are now passing List<AssetCategory> to DeleteCategoryObject model attribute. So you have to do necessary changes in your front end.
You can now iterate it in front end as below:
<form method="POST" name="deleteFormAdd" id="deleteFormAdd" enctype="multipart/form-data">
<input type="hidden" name="_csrf" th:value="${_csrf.token}" />
//The code can't iterate the String of array given by the GET
<input type="hidden" class="assetId" th:each="deleteCategory, itemStat : ${DeleteCategoryObject}" th:name="|DeleteCategoryObject[__${itemStat.index}__].assetID|" th:value="${deleteCategory.assetID}"/>
//This button is for submitting the String of array ID to be deleted
<input type="button" class="btn btn-primary btn-block" value="Yes" th:onclick="'javascript:submitForm(\'deleteFormAdd\',\''+#{/delete-asset}+'\')'" />
<button type="reset" onclick="window.location.href = 'manage-assets.html';" class="btn btn-default btn-block"> Cancel</button>
</form>
Change the Post controller as below:
#RequestMapping(value = "/delete-asset", method = RequestMethod.POST)
public #ResponseBody String deleteAsset(#ModelAttribute List<AssetCategory> assetCategories) {
JsonObject result = new JsonObject();
if (assetCategories != null && !assetCategories.isEmpty()) {
String[] arr = new String[assetCategories.size()];
for (int i =0; i < assetCategories.size(); i++) {
arr[i] = assetCategories.get(i).getAssetID();
}
assetService.deleteAssets(arr);
result.addProperty("result", "Success");
result.addProperty("status", 1);
result.addProperty("message", "Asset Deleted!");
}
return result.toString();
}
Submit form function:
function submitForm(formID, url) {
var assetIdList = [];
var assetIdObj;
$("#" + formID).find('.assetId').each(function () {
assetIdObj = {};
assetIdObj.assetID = $(this).val();
assetIdList.push(assetIdObj);
});
$.ajax({
dataType: 'json',
url: url,
data: {assetCategories: assetIdList},
type: "POST",
enctype: "multipart/form-data",
processData: false,
contentType: false,
success: function (data) {
if (data.status === 1) {
openAlertDialog("Success", "The Asset type has been deleted!", "Continue", "manage-assets");
} else {
openAlertDialog("Error", data.message, "Continue", "manage-assets");
}
},
error: function (data) {
openAlertDialog("Error", data.message, "Continue", "manage-assets");
},
});
}

Thymeleaf form passing all but one inputs to java controller

My thymeleaf form has three inputs, when I submit it my controller is receiving the second and the third, but not the first one (first input is null)
I have similar forms and method to add records to a MySQL database which work with no problems.
I have diplayed the two first inputs in my controller, first (Id of the entity) is null, second is well informed
Here is my form:
addDepartment.html
<br/>
<form th:action="#{/university/addDepartment}"
th:object="${department}" method="POST">
Department Name
<input type="text" th:field="*{deptName}" />
<br/>
Department building
<input type="text" th:field="*{building}" />
<br/>
Budget
<input type="number" th:field="*{budget}" th:value=0/>
<br/>
<input type="submit" value="Add" />
</form>
<!-- Check if errorMessage is not null and not empty -->
<div th:if="${errorMessage}" th:utext="${errorMessage}"
style="color:red;font-style:italic;">
...
</div>
And my controller
// ADD a department to the database
// Show the addDepartment page
#RequestMapping(value= { "university/addDepartment" }, method = RequestMethod.GET)
public String showAddDepartment(Model model) {
Department department = new Department();
model.addAttribute("department", department);
return "addDepartment";
}
// Add the department to the database
#RequestMapping(value = { "university/addDepartment" }, method = RequestMethod.POST)
public String saveDepartment(Model model, //
#ModelAttribute("department")
#Validated Department department) {
System.out.println("DEPARTMENT: " + department.toString());
String deptName = department.getDeptName();
System.out.println("DEPARTMENT NAME: " + deptName);
String building = department.getBuilding();
System.out.println("BUILDING: " + building);
int budget = department.getBudget();
if (deptName != null && deptName.length() > 0 //
&& building != null && building.length() > 0 //
&& budget >= 0) {
Department newDepartment = new Department(deptName, building, budget);
departmentrepo.save(newDepartment);
return "redirect:/university/departmentList";
}
Object errorMessage = "All fields are mantatory";
model.addAttribute("errorMessage", errorMessage );
return "addDepartment";
}
Here is the Department class:
#Entity
#Table(name="department") // This annotation is needed only if the
table has a different name to this class
#EntityListeners(AuditingEntityListener.class)
public class Department {
#Id
private String deptName;
private String building;
private int budget;
//Constructors
public Department() {
}
public Department(String deptName, String building, int budget) {
this.deptName = deptName;
this.building = building;
this.budget = budget;
}
...GETTERS and SETTERS
The deptName is always null, but the other two inputs are ok
DEPARTMENT NAME: null
BUILDING: Main
UPDATE: I think I found the solution, but if someone can explain the reason...
I just passed the deptName in a #RequestParam annotation.
#RequestMapping(value = { "university/addDepartment" }, method =
RequestMethod.POST)
public String saveDepartment(Model model, //
#ModelAttribute("department") Department department,
#RequestParam("deptName") String deptName) {
//String deptName = department.getDeptName();
System.out.println("DEPARTMENT NAME: " + deptName);
String building = department.getBuilding();
System.out.println("BUILDING: " + building);
int budget = department.getBudget();
if (deptName != null && deptName.length() > 0 //
&& building != null && building.length() > 0 //
&& budget >= 0) {
Department newDepartment = new Department(deptName, building, budget);
departmentrepo.save(newDepartment);
return "redirect:/university/departmentList";
}
Object errorMessage = "All fields are mantatory";
model.addAttribute("errorMessage", errorMessage );
return "addDepartment";
}x`
There are a few issues with this code, but the first thing to try is removing the #Id annotation from your deptName property. Create a Long (or Integer) id property and annotate this instead. You can more simply call the department name property name too.
You will also want to make sure that you have the getters and setters done correctly, or more advisable - just use Project Lombok to get rid of extra text and potential for mistakes.
#Entity
#Table
#Data //see Project Lombok
public class Department {
#Id
private Long id;
private String name;
private String building;
private int budget; //I'd consider making this more precise by using BigDecimal
}
For your controller, let's more simply try this to see whether it is still breaking.
#GetMapping("/university/addDepartment")
public String showAddDepartment(Model model) {
model.addAttribute("department", new Department());
return "addDepartment";
}
#PostMapping("/university/addDepartment")
public String saveDepartment(#ModelAttribute("department") Department department) {
System.out.println("name="+department.getName()); //use a logger if you have one available
return "addDepartment";
}
In your form, you only need the th: notation if you want Thymeleaf to evaluate the logic. So you can change th:value=0 to value=0 if you really intend this.

Value filled in form not saved in database

I am trying to set a predefined value in Shop.email field in a form but it storing every field except one field i.e. email.
Shop.java
package models;
#Entity
public class Shop extends Model {
#Id
#SequenceGenerator(name="shop_gen", sequenceName="shop_id_seq", allocationSize=1)
#GeneratedValue(strategy=GenerationType.SEQUENCE, generator="shop_gen")
#Column(name="id")
public Long id;
#Required
public String name;
#Required
public String addressLine1;
public String addressLine2;
public String addressLine3;
#Required
public String city;
#Required
public String town;
#Required
public String phoneNumber;
#ManyToOne
#JoinColumn(name="email",insertable=false, updatable=false,nullable=false)
public Member email;
public static Model.Finder<Long,Shop> find = new Model.Finder(Long.class, Shop.class);
}
ShopController.java
package controllers;
public class ShopController extends Controller {
static Form<Shop> shopForm = Form.form(Shop.class);
public static Result blank() {
String loggedInUserEmail = session("email");
Shop shop = new Shop();
shop.email = Member.get(loggedInUserEmail);
shopForm.fill(shop);
return ok(views.html.shop.create.render(shopForm, loggedInUserEmail));
}
public static Result submit() {
Form<Shop> filledForm = shopForm.bindFromRequest();
if (filledForm.hasErrors()) {
String loggedInUserEmail = session("email");
return badRequest(views.html.shop.create.render(filledForm,
loggedInUserEmail));
} else {
Shop shop = filledForm.get();
Shop.create(shop);
return redirect(routes.ProductController.blank());
}
}
}
createShop.scala.html
#(userForm: Form[models.Shop], user: String)
#import helper._
#import helper.twitterBootstrap._
#main(Html("Create Shop")) {
<fieldset>
<legend>Add a new shop</legend>
<p>To add a shop to this website fill in the form given below.Add as much information about your shop so the customer may know abot your shop more.</p>
#form(action = routes.ShopController.submit(), 'id -> "shopCreationForm", 'class -> "form-horizontal", 'role->"form") {
#inputText(userForm("name"), '_label -> "Shop Name",'class -> "form-control")
#inputText(userForm("addressLine1"), '_label -> "Address Line 1",'class -> "form-control")
#inputText(userForm("addressLine2"), '_label -> "Address Line 2",'class -> "form-control")
#inputText(userForm("addressLine3"), '_label -> "Address Line 3",'class -> "form-control")
#inputText(userForm("city"), '_label -> "City",'class -> "form-control")
#inputText(userForm("town"), '_label -> "Town",'class -> "form-control")
#inputText(userForm("phoneNumber"), '_label -> "Phone",'class -> "form-control")
<div class="form-group">
<label for="exampleInputEmail1">Owner Email</label>
<input type="email" class="form-control" id="exampleInputEmail1" placeholder="#user" readonly>
</div>
<div class="actions">
<input type="submit" class="btn btn-primary" value="Create">
Cancel
</div>
</fieldset>
}
}
When i am submitting this form it saves every value in database except the value of email field.I am unable to understand what i am doing wrong
Thanks in advance.
The email input field does not include a name attribute. Therefore, your browser can't send data correctly to the server.
You need to either use the form helper to render this input or add the name="email" in your <input> :
<input type="email" name="email" class="form-control" id="exampleInputEmail1" placeholder="#user" readonly>

Get Object from Dropdownbox in Java Play using Scala

UPDATE:
This is my index.scala.html which displays a dropdown box:
#(helloForm: Form[Hello], dpts: List[Hello])
#import helper._
#main("InteractionService", "newsimulation") {
#form(action = routes.Application.sayHello(), args = 'id -> "helloform") {
<select class="selectpicker" data-size="auto" data-live-search="true" data-container="body" >
#for(dpt <- dpts){
<option value="#dpt.id">#dpt.name</option>
}
</select>
<p class="buttons">
<input type="submit">
<p>
}
}
This is my Hello object:
#Entity
public class Hello extends Model {
private static final long serialVersionUID = 1L;
#Id
#Constraints.Required
#Formats.NonEmpty
public Long id;
#Constraints.Required
public String name;
}
This is my controller:
public static Result sayHello() {
Form<Hello> dptForm = form(Hello.class).bindFromRequest();
DomainPracticeTemplate currentDPT = dptForm.get();
Logger.info("dptForm content = " + dptForm.toString());
}
When hitting the submit button, it returns:
[IllegalStateException: No value]
I need the id or the whole object, how?

Categories

Resources