Dynamic queries and JpaSpecificationExecutor in Spring - spring-data-jpa

I am trying to create a simple Spring project where restaurants can add menu items to shared database and users can use a html form to search the dishes based on a range of criteria- especially dietary requirements
Form example:
Restaurant Name: Chez Hans
Gluten Free: (X)
Egg Free: (X)
Vegan: ()
Example SQL command
Select all FROM "dishes" Dish WHERE restaurant_name = "Chez Hans" AND egg_free = TRUE AND
gluten_Free = TRUE;
A list of dishes that fit their criteria would then be returned to the user.
Any field in the form can be left blank, and not checking a box for example, "vegan" does not mean that criteria should be set as 'false', but rather not included within the query.
Because of this it seemed the best way to handle the issue was using JpaSpecificationExecutor to create dynamic SQL queries (similar to the implementation in the answer to the problem below)
Filtering database rows with spring-data-jpa and spring-mvc
I have created a solution based on my research and prior knowledge. However, when I implement my solution, no dishes are returned- even though there are dishes in the database that fit the search criteria. No errors are being produced, but simply a blank table, so I am not sure where I am going wrong.
I have researched countless articles/videos regarding JpaSpecificationExecutor/dynamic queries but there are parts of the that theory I am still unsure about. This is what I gather about JpaSpecificationExecutor/dynamic queries (but I may be wrong)
The meta model is not need to create dynamic queries but to verify the correctness of database query statements
To create queries using meta-model classes a wrapper class is required (In my example- DishSearch)
The following lines are to cast metamodel SingularAttribute class back to the original class value.
Path dname = root.get(Dish_.dname);
Path vegan= root.get(Dish_.vegan);
I am quite new to Spring and still finding it pretty difficult. Any help or advice would be very much appreciated!
Please see below my DishSpecification class:
package com.bron.demoJPA.specification;
public class DishSpecification implements Specification<Dish> {
private final DishSearch criteria;
public DishSpecification(DishSearch ds) {
criteria =ds;
}
#Override
public Predicate toPredicate(Root<Dish> root, CriteriaQuery<?> query,
CriteriaBuilder cb) {
Path<String> dname = root.get(Dish_.dname);
Path<Boolean> vegan= root.get(Dish_.vegan);
Path<Boolean> eggFree= root.get(Dish_.eggFree);
Path<Boolean> glutenFree= root.get(Dish_.glutenFree);
final List<Predicate> predicates = new ArrayList<Predicate>();
if(criteria.getDname()!=null) {
predicates.add(cb.equal(dname, criteria.getDname()));
}
if(criteria.isVegan()!=false) {
predicates.add(cb.equal(vegan, criteria.isVegan()));
}
if(criteria.isEggFree()!=false) {
predicates.add(cb.equal(eggFree, criteria.isEggFree()));
}
if(criteria.isGlutenFree()!=false) {
predicates.add(cb.equal(glutenFree, criteria.isGlutenFree()));
}
return cb.and(predicates.toArray(new Predicate[predicates.size()]));
}
}
Please see my DishSearch Class:
package com.bron.demoJPA.specification;
#AllArgsConstructor
#NoArgsConstructor
#Getter
#Setter
public class DishSearch {
private Long dishId;
private String dname;
private String description;
private double price;
private boolean vegan;
private boolean glutenFree;
private boolean eggFree;
private AppUser app;
}
Please see my SearchController Class:
#Controller
public class SearchController {
#Autowired
DishRepository drep;
#GetMapping("/showSearchForm")
public String showNewDishForm(Model model) {
// Create model attribute to bind form data
DishSearch dishSearch = new DishSearch();
model.addAttribute("dishSearch", dishSearch);
return "search_Dish";
}
#PostMapping("/showDishList")
public String saveUser(#ModelAttribute("dishSearch")DishSearch dishSearch) {
Specification<Dish> spec = new DishSpecification(dishSearch);
drep.findAll(spec);
return "show_dish_List";
}
}
Please see my DishRepository Class:
#Repository
public interface DishRepository extends JpaRepository<Dish, Long>, JpaSpecificationExecutor<Dish>{
#Transactional
#Modifying
List<Dish> findAll(Specification<Dish> spec);
}
Please see my search_Dish.html Thymeleaf Template:
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="ISO-8859-1">
<title>Dish Management System</title>
<link rel="stylesheet"
href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css">
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<br>
<div class="col-sm-10 offset-sm-1 text-center">
<div class="container">
<h2> Manage Dishes </h2>
<hr>
</div>
<form action="#" th:action="#{/showDishList}" th:object="${dishSearch}" method="POST">
<div class="col-sm-10 offset-sm-1 text-center">
<input type="text" th:field="*{dname}"
placeholder="Dish Name" class="form-control mb-4 col-10">
</div>
<div class="form-check form-check-inline">
<label class=" form-check-label" for="inlineCheckbox1 ">Vegan?</label>
<input type="checkbox" th:field="*{vegan}" />
<label class="form-check-label" for="inlineCheckbox1">Gluten Free?</label>
<input type="checkbox" th:field="*{glutenFree}" />
<label class="form-check-label" for="inlineCheckbox1">Egg Free?</label>
<input type="checkbox" th:field="*{EggFree}" />
</div>
<br>
<br>
<br>
<br>
<button type="submit" class="btn btn-info col-4"> Search Database</button>
</form>
</div>
<hr>
</body>
</html>
Please see my show_dish_List.html Thymeleaf Template:
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<title>Search Results</title>
<link rel="stylesheet"
href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css">
</head>
<body>
<br>
<div class="col-sm-10 offset-sm-1 text-center">
<h1>Dish List</h1>
</div>
<table border="1" class="table table-striped table-responsive-md">
<thead>
<tr>
<th>Dish Name</th>
<th>Dish description</th>
<th>Dish Price</th>
<th>Restaurant</th>
</tr>
</thead>
<tbody>
<tr th:each="dishSearch : ${listDishSearch}">
<td th:text="${dishSearch.dname}"></td>
<td th:text="${dishSearch.description}"></td>
<td th:text="${dishSearch.price}"></td>
</tr>
</tbody>
</table>
<div class="col-sm-10 offset-sm-1 text-center">
<a th:href="#{/showNewDishForm}"
class="btn btn-primary btn-sm mb-3"> Search Again</a>
</div>
----------------------------Update------------------------------
In addition to the answer provided below, in the Dish Specification Class I changed
if(criteria.getDname()!=null) {
predicates.add(cb.equal(dname, criteria.getDname()));
}
to
if(criteria.getDname()!="") {
predicates.add(cb.equal(dname, criteria.getDname()));
}
and the search is working fine now!

I believe the issue is that you are not adding the result in the Model which is being used to render the page show_dish_List.html, therefore nothing is being populated in the UI. Your UI is expecting the data to be in listDishSearch and there is nothing in that variable.
Update your code to:
#PostMapping("/showDishList")
public String saveUser(#ModelAttribute("dishSearch") DishSearch dishSearch, Model model) {
Specification<Dish> spec = new DishSpecification(dishSearch);
model.addAttribute("listDishSearch", drep.findAll(spec));
return "show_dish_List";
}
and everything should be working fine.
Remove the method findAll from your DishRepository repository. It is already being provided by the interface JpaSpecificationExecutor.

Related

Using One Thymeleaf Form for Partial Table Update and #OneToMany Persistence with Validation

I have a user table and a user_details table. The user table gets partially populated by a script and the remaining age and birthplace values get added manually by a user via a Thymeleaf form. As a part of the form I also want to add details in the user_details table. The relationship is a #OneToMany.
I am running into a few issues with Validation that I am not able to figure out properly:
Since only 2 fields of the 5 are in the form to save to table user, is using hidden inputs the best way to pass all the User field values to the save method in the event of a validation failure or is the a cleaner way (i.e., a JPA annotation type way, etc.)?
On the initial loading of the user, the UserDetails are iterated through in the user.html file with th:each="detail: ${user.userDetailsById}". However, when the save method fails validation and the user.html file is returned from the result.hasErrors() block, those details revert to null and do not display. Do I have to manually query those again?
The object in the form is a User object. If I want to pass a user_details comment in the form input how would this be structured and persisted into user.user.userDetailsById.comment?
I've included the code below.
create table user
(
id varchar(10) not null
primary key,
first_name varchar(100) not null,
last_name varchar(100) not null,
age int unsigned null,
birthplace varchar(100) null,
constraint user_id_uindex
unique (id)
);
create table user_details
(
id int unsigned auto_increment
primary key,
user varchar(10) null,
comments longtext null,
constraint user_details_id_uindex
unique (id),
constraint user_details_user_id_fk
foreign key (user) references user (id)
);
#Entity
#Data
#Table(name = "user", schema = "demo")
public class User {
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Id
#Column(name = "id", nullable = false, length = 10)
private String id;
#Basic
#Column(name = "first_name", nullable = false, length = 100)
private String firstName;
#Basic
#Column(name = "last_name", nullable = false, length = 100)
private String lastName;
#Basic
#Column(name = "age", nullable = true)
private Integer age;
#Basic
#NotEmpty
#Column(name = "birthplace", nullable = true, length = 100)
private String birthplace;
#OneToMany(mappedBy = "userByUser")
private Collection<UserDetails> userDetailsById;
}
#Entity
#Data
#Table(name = "user_details", schema = "demo")
public class UserDetails {
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Id
#Column(name = "id", nullable = false)
private int id;
#Basic
#Column(name = "user", nullable = true, length = 10)
private String user;
#Basic
#Column(name = "comments", nullable = true)
private String comments;
#ManyToOne
#JoinColumn(name = "user", referencedColumnName = "id", insertable = false, updatable = false)
private User userByUser;
}
#Controller
public class UserController {
private final UserRepository userRepository;
private final UserDetailsRepository userDetailsRepository;
public UserController(UserRepository userRepository, UserDetailsRepository userDetailsRepository) {
this.userRepository = userRepository;
this.userDetailsRepository = userDetailsRepository;
}
#GetMapping("/all")
public String getAllUsers(Model model) {
model.addAttribute("users", userRepository.findAll());
return "users";
}
#GetMapping("/{id}")
public String getUser(#PathVariable("id") User user, Model model) {
model.addAttribute("user", user);
return "user";
}
#PostMapping("/save")
public String saveUser(#Valid User user, BindingResult result) {
if (result.hasErrors()) {
return "user";
}
userRepository.save(user);
return "redirect:/all";
}
}
<!DOCTYPE html>
<html lang="en" xmlns:th="https://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Users</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap#5.2.0/dist/css/bootstrap.min.css">
</head>
<body>
<main class="container p-5">
<section>
<div class="row">
<div class="col">
<table class="table">
<thead>
<tr>
<th>ID</th>
<th>First Name</th>
<th>Last Name</th>
<th>Age</th>
<th>Birthplace</th>
</tr>
</thead>
<tbody>
<tr th:each="user : ${users}">
<td><a class="text-dark" th:href="#{'/' + ${user.id}}" th:text="${user.id}"></a></td>
<td th:text="${user.firstName}"></td>
<td th:text="${user.lastName}"></td>
<td th:text="${user.age}"></td>
<td th:text="${user.birthplace}"></td>
</tbody>
<tfoot></tfoot>
</table>
</div>
</div>
</section>
</main>
<script src="https://cdn.jsdelivr.net/npm/bootstrap#5.2.0/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>
<!DOCTYPE html>
<html lang="en" xmlns:th="https://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Users</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap#5.2.0/dist/css/bootstrap.min.css">
</head>
<body>
<main class="container p-5">
<section>
<p th:text="'User: ' + ${user.firstName + ' ' + user.lastName}"></p>
<!-- THE USER DETAILS BECOME NULL WHEN THE VALIDATION RETURNS BACK TO THE FORM -->
<div th:if="${user.userDetailsById}">
<div th:each="detail: ${user.userDetailsById}">
<p th:text="'Comment: ' + ${detail.comments}"></p>
</div>
</div>
</section>
<section>
<form action="#" th:action="#{'/save'}" th:object="${user}" method="post">
<!-- ARE MULTIPLE HIDDEN INPUTS THE BEST WAY ENSURE THAT WHEN VALIDATION FAILS THE TABLE FIELDS GET PASSED BACK TO THE FORM? -->
<input type="hidden" th:field="*{id}">
<input type="hidden" th:field="*{firstName}">
<input type="hidden" th:field="*{lastName}">
<div class="row">
<div class="col">
<div class="mt-3">
<label class="form-label" for="age">Age</label>
<input class="form-control" id="age" type="number" th:field="*{age}">
<div class="text-danger mt-2" th:if="${#fields.hasErrors('age')}"
th:errors="*{age}"></div>
</div>
</div>
</div>
<div class="row">
<div class="col">
<div class="mt-3">
<label class="form-label" for="birthplace">Birthplace</label>
<input class="form-control" id="birthplace" type="text" th:field="*{birthplace}">
<div class="text-danger mt-2" th:if="${#fields.hasErrors('birthplace')}"
th:errors="*{birthplace}"></div>
</div>
</div>
</div>
<!-- HOW DO I IMPLEMENT THIS 'ADD USER DETAILS' INPUT TO POPULATE A NEW COMMENT TO THE USER DETAILS? -->
<div class="row">
<div class="col">
<div class="mt-3">
<label class="form-label" for="userDetail">User Details</label>
<input class="form-control" id="userDetail" type="text" th:field="*{????????}">
<div class="text-danger mt-2" th:if="${#fields.hasErrors('userDetail')}"
th:errors="*{??????????}"></div>
</div>
</div>
</div>
<div class="row">
<div class="col-2">
<div class="mt-3">
<button class="btn btn-dark w-100" type="submit">Save</button>
</div>
</div>
<div class="col-2">
<div class="mt-3">
<a class="btn btn-secondary w-100" th:href="#{/all}"
role="button">Cancel</a>
</div>
</div>
</div>
</form>
</section>
</main>
<script src="https://cdn.jsdelivr.net/npm/bootstrap#5.2.0/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>
When offering a form with a partial edit on an entity I would personally use a seperate bean for the form submit with only the editable fields and a hidden id field, then in the service layer find the entity with that id and edit it's values depending on what was submitted in the form.
No you don't have to manualy query the fields again. The reason those fields are empty is because they'r not correctly bound when posting the form aslong as you post them with your form they wil come back when you return an error.
List fields need a name like *{userDetailsById__[iter.index]__.comments} where iter comes from the each loop th:each="iter, detail: ${user.userDetailsById}". for more details on how to do this you can look at https://www.baeldung.com/thymeleaf-list#list-selection-expression
see 2
P.s. You don't need the SQL in your question nor the overview page html as they do not relate to the question your asking. Just the edit page html, controller and entities (and service if relevant) is enough I'd say

Redirect to page a certain amount of times - Spring Boot

I am trying to make a Quiz Creation application.
In the beginning, I ask the user to enter the quiz title, description, and the number of questions included in the quiz.
Based on the number of questions I want to redirect the user to the 'question and answers' page. I thought of adding another variable named 'count' which would keep the number of times the page is accessed so I can show the next or submit button.
I am not sure how to calculate the number of times the page is redirected and how to redirect the code to a certain page based on the number of questions.
This is the saveQuiz method in the QuizController class:
#PostMapping("/saveQuiz/{cid}")
public String saveQuiz(#PathVariable("cid") Long cid, #Valid #ModelAttribute Quiz quiz,
Model model, #RequestParam("questionNumber") int noOfQuestions) throws ParseException {
Chapter chapter = chapterService.findChapterById(cid);
quiz.setQuizName(quiz.getQuizName());
quiz.setGuidelines(quiz.getGuidelines());
quiz.setChapter(chapter);
quizService.saveQuiz(quiz);
model.addAttribute("quiz", quiz);
model.addAttribute("noOfQuestions", noOfQuestions);
return "redirect:/add_quiz_questions/"+quiz.getId();
}
Then in my QuestionController class I have the below methods
#Controller
public class QuestionController {
#Autowired
QuizService quizService;
#Autowired
QuestionService questionService;
#Autowired
AnswerService answerService;
private static int count = 0;
#GetMapping("/add_quiz_questions/{qid}")
public String addQuestions(#PathVariable("qid") Long qid, Model model) {
count++;
Quiz quiz = quizService.findQuizById(qid);
model.addAttribute("quiz", quiz);
model.addAttribute("count", count);
return "add_quiz_questions";
}
#PostMapping("/saveQuizQuestion/{qid}")
public String saveQuestions(#PathVariable("qid") Long qid, #Valid #ModelAttribute QuizForm quizForm,
Model model, #RequestParam("noOfQuestions") int noOfQuestions) throws ParseException {
Quiz quiz = quizService.findQuizById(qid);
Question question = new Question();
question.setQuestion(quizForm.getQuestion());
//Add answers
Set<Answer> answers = new HashSet<>();
Answer a = new Answer();
a.setAnswer(quizForm.getOption1());
a.setCorrect(1);
answers.add(a);
a.setAnswer(quizForm.getOption2());
a.setCorrect(0);
answers.add(a);
a.setAnswer(quizForm.getOption3());
a.setCorrect(0);
answers.add(a);
answerService.saveAnswers(answers);
question.setAnswers(answers);
questionService.saveQuestion(question);
Chapter chapter = quiz.getChapter();
Course course = chapter.getCourse();
Set<File> files = chapter.getFiles();
int nrFiles = files.size();
model.addAttribute("chapter", chapter);
model.addAttribute("course", course);
model.addAttribute("files", files);
model.addAttribute("numberOfFiles", nrFiles);
model.addAttribute("quiz", quiz);
if(count == noOfQuestions) //check if the page has been redirected as many times as there were questions then redirect to chapter page
return "redirect:/chapter_details/"+chapter.getId();
else
return "redirect:/add_quiz_questions/"+quiz.getId();
}
}
This is the Thymeleaf page:
<!DOCTYPE html>
<html lang="en"
xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5">
<div class="card">
<h5 class="card-header info-color white-text text-center py-4">
<strong>Quiz questions</strong>
</h5>
<div class="card-body px-lg-5">
<!-- Form -->
<form class="text-center" style="color: #757575;" th:action="#{/saveQuizQuestion/{qid}(qid=${quiz.id})}" method="post" th:object="${quizForm}">
<p>Create your quiz</p>
<!-- Question -->
<div class="md-form mt-3">
<input type="text" id="question" class="form-control" name="question">
<label for="question">Question</label>
</div>
<!-- Right answer -->
<div class="md-form">
<input type="text" id="ans1" class="form-control" name="option1">
<label for="ans1">Answer 1</label>
</div>
<!-- Answer 2 -->
<div class="md-form">
<input type="text" id="ans2" class="form-control" name="option2">
<label for="ans2">Answer 2</label>
</div>
<!-- Answer 3 -->
<div class="md-form">
<input type="text" id="ans3" class="form-control" name="option3">
<label for="ans3">Answer 3</label>
</div>
<input type="hidden" th:value="${count}" name="count"/>
<input type="hidden" th:value="${noOfQuestions}" name="noOfQuestions"/>
<button th:if="${noOfQuestions < count}" class="btn btn-outline-info btn-rounded btn-block z-depth-0 my-4 waves-effect" type="submit">Next</button>
<button th:if="${noOfQuestions == count}" class="btn btn-outline-info btn-rounded btn-block z-depth-0 my-4 waves-effect" type="submit">Submit</button>
</form>
<!-- Form -->
</div>
</div>
</html>
I believe the way that I am using the count variable is wrong but it's there just to give an idea. If anyone could help me clarify the question that I have, I'd be grateful.
Thank you in advance.
you can make a count variable in session with #SessionAttribute annotation.
and whenever they submit you will again set count variable to default value.

Thymeleaf, default values does not appear in my update form

I'm learning java and I'm practicing with thymeleaf. I made an little app where I have a list of persons (arraylist). I can add a person through a form but also edit a person from the list to update the person's firstname, lastname or birthdate through a form. Here is my problem I want when I edit a person to have its default values(firstname, lastname, bithdate) on the update form so that we can then change only the fields of interest. I have this code:
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8" />
<title>Update Person</title>
<link rel="stylesheet" type="text/css" th:href="#{/css/style.css}"/>
</head>
<body>
<h1>Update a Person:</h1>
<!-- some tests I made to test if the value appears in the field -->
<!-- <input type="text" name="id" th:value="${person.id}" /> -->
<!-- <input type = "text" name = "firstName" th:value = "${person.firstName}" /> -->
<!-- <input type = "text" name = "sometext" th:value = "hello world" /> -->
<form th:action="#{/updatePerson/{id}(id=${person.id})}"
th:object="${person}" method="POST">
First Name:
<input type="text" th:field="*{firstName}"/>
<br/>
Last Name:
<input type="text" th:field="*{lastName}" />
<br/>
Date of Birth (DD/MM/YYYY):
<input type="date" th:field="*{birthDate}" />
<br/>
ID:
<input type="text" th:field="*{id}" />
<br/>
<input type="submit" value="Update" />
</form>
<br/>
<!-- Check if errorMessage is not null and not empty -->
<div th:if="${errorMessage}" th:utext="${errorMessage}"
style="color:red;font-style:italic;">
...
</div>
</body>
</html>
None of my default values appears in the fields except for the id. Whether I use th:field="{id}" or name="id" th:value="${person.id}". Both synthax work but the others (ie: th:field="{firstName}" or name = "firstName" th:value = "${person.firstName}" same goes for lastname and birthdate), nothing works. I even tried th:value = "hello world" (commented in the above code), it does appear! So why my person firstname, lastname, bithdate don't appear? What is wrong? My person.list html works though:
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8" />
<title>Person List</title>
<link rel="stylesheet" type="text/css" th:href="#{/css/style.css}"/>
</head>
<body>
<h1>Person List</h1>
Add Person
<br/><br/>
<div>
<table border="1">
<tr>
<th>First Name</th>
<th>Last Name</th>
<th>Date of Birth</th>
<th>Edit</th>
<th>Delete Name</th>
</tr>
<tr th:each ="person : ${list}">
<td th:utext="${person.firstName}">...</td>
<td th:utext="${person.lastName}">...</td>
<td th:text="${#temporals.format(person.birthDate,'dd-MM-yyyy')}">...</td>
<td><a th:href="#{/updatePerson/{id}(id=${person.id})}">
<span>
<img src="https://img.icons8.com/clouds/40/000000/edit.png">
</span>
</a></td>
<td>
<form th:action="#{/deletePerson}" th:method="POST">
<input type = "hidden" name = "firstName" th:value = "${person.firstName}" />
<input type = "hidden" name = "lastName" th:value = "${person.lastName}" />
<input type = "hidden" name = "id" th:value = "${person.id}" />
<input type = "hidden" name = "birthDate" th:value = "${person.birthDate}" />
<button type = "submit" >
<span>
<img src="https://img.icons8.com/metro/26/000000/delete.png" />
</span>
</button>
</form>
</td>
</tr>
</table>
</div>
<div>
<form th:action="#{/changeDao}" th:method="POST">
<select name="daoChoice">
<option th:value="none" disabled>Choisissez votre Dao</option>
<option id="jdbc" th:value="JDBC">Jdbc</option>
<option id="memory" th:value="MEMORY" th:selected="${isMemory}">Memory</option>
</select>
<button type="submit">Valider</button>
</form>
</div>
<div>
<form th:action="#{/excelLoad}" th:method="GET">
<button type="submit">Local Load</button>
</form>
</div>
<div>
<form th:action="#{/UploadFile}" method="POST" enctype="multipart/form-data">
<table>
<tr>
<td><label>Upload and Add to the table</label></td>
<td><input type="file" th:value = "file" th:name="file" /></td>
</tr>
<tr>
<td><input type="submit" value="Upload" /></td>
</tr>
</table>
</form>
</div>
<div>
<form th:action="#{/exportToExcel}" th:method="POST">
<button type="submit">Export to Excel</button>
</form>
</div>
</body>
</html>
Above my personList.html, person's firstName lastName and birthdate is printed correctly with this code:
<tr th:each ="person : ${list}">
<td th:utext="${person.firstName}">...</td>
<td th:utext="${person.lastName}">...</td>
<td th:text="${#temporals.format(person.birthDate,'dd-MM-yyyy')}">...</td>
but why in my update form this is not working ?
I'm a newbie in java programming and also in thymeleaf (also newbie), so I'd really appreciate some explanations along some tips! thanks a lot!
I found it with another post where there was a simple explanation about the key/value pair in modelAddAttribute:
You can access variables value by ${key}.
Example
model.addAttribute("key", value);
Understanding that I found my mistake in my controller:
#RequestMapping(value = { "/updatePerson/{id}" }, method = RequestMethod.GET)
public String showUpdatePersonPage(#PathVariable("id") int id, Person person, Model model) {
person = personDao.findPerson(id);
model.addAttribute("person", person);
return "updatePerson";
}
Before it was:
#RequestMapping(value = { "/updatePerson/{id}" }, method = RequestMethod.GET)
public String showUpdatePersonPage(#PathVariable("id") int id, Person person, Model model) {
person = personDao.findPerson(id);
model.addAttribute("personToModify", person);
return "updatePerson";
}
And in my html the code was:
<form th:action="#{/updatePerson/{id}(id=${person.id})}"
th:object="${person}" method="POST">
First Name:
<input type="text" th:field="*{firstName}"/>
<br/>
Last Name:
<input type="text" th:field="*{lastName}" />
<br/>
Date of Birth (DD/MM/YYYY):
<input type="date" th:field="*{birthDate}" />
<br/>
ID:
<input type="text" th:field="*{id}" />
<br/>
<input type="submit" value="Update" />
</form>
So that was because the key name used "personToModify" couldn't be found in the html as the object name used wasn't properly named:
th:object="${person}"
I can't see your Person class or controller but, for example, keeping it clean, you can create PersonForm class which can look like (might need to change Date)
import java.util.Date;
public class PersonForm {
private String firstName;
private String lastName;
private Date birthDate;
public PersonForm() {
}
public PersonForm(Person person) {
this.firstName = person.getFirstName();
this.lastName = person.getLastName();
this.birthDate = person.getBirthDate();
}
As you can see, it has fields which needs to populated and you set them in constructor, you can also apply validation annotations here if needed.
In your controller you would need to retrieve Person and using it, create and add PersonForm as model attribute. i.e.
#GetMapping("/person/edit/{id}") // you might not use id, might be username
public String editPerson(#PathVariable Long id, Model model) {
Person person = personRepository.getOne(id); // or service
PersonForm personForm = new PersonForm(person);
model.addAttribute("personForm", personForm);
// other stuff
// return html
}
and then change th:object="${person}" to th:object="${personForm}"
Now all th:field="*{firstName}" and others should be populated.

Posting Html with images (WYSIWYG) to ASP.net core controller

I'm having trouble posting my wysiwyg content to my controller in asp.net core. I can't seem to get any value from the form editor. The value for the Content property comes to the controller as null. I'm using the summernote form editor to handle my richtext box editor.
Here is my code
public class Editor
{
public int EditorId { get; set; }
public string Content { get; set; }
}
Controller:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create(Editor editor)
{
if (ModelState.IsValid)
{
_context.Add(editor);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
return View(editor);
}
View:
<h2>Create</h2>
<h4>Editor</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form asp-action="Create">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="Content" class="control-label"></label>
<textarea asp-for="Content" id="summernote" name="editordata"></textarea>
<span asp-validation-for="Content" class="text-danger"></span>
</div>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</form>
</div>
</div>
<div>
<a asp-action="Index">Back to List</a>
</div>
#section Scripts {
<script>
$(document).ready(function () {
$('#summernote').summernote();
});
</script>
#{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
<script src="http://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.js"></script>
<script src="http://netdna.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/summernote/0.8.9/summernote.js"></script>
}
#section Styles{
<!-- include libraries(jQuery, bootstrap) -->
<link href="https://netdna.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.css" rel="stylesheet">
<!-- include summernote css/js -->
<link href="http://cdnjs.cloudflare.com/ajax/libs/summernote/0.8.9/summernote.css" rel="stylesheet">
}
So the issue is when I post the form it's gets to the controller but the content comes over as null. I'm not sure how to post the content
Here are my thoughts, I'm thinking i'm missing a some attribute that allows html to come over the wire to my controller, but all the reserach i've found is that asp.net core doesn't require that. Or I need to handle this type of request in the middleware pipeline, but that doesn't make much sense since it's just html strings i'm sending over the wire to the controller.
It looks like the top of your view was not left out, I assume you have Editor as model.
The problem is on your text area you are using both asp-for and then setting the id and name to something that doesn't match your model property.
You should just use asp-for and let it decide the id and name instead of adding those yourself.
What is really getting posted is a string named editordata because you used that name on the textarea. remove that and it will be named Content to match the property of the model
You also don't need the [Bind] attribute shown in the controller action in your screenshot.
I have been sitting with the same issue and was able to resolve it due to Joe's answer!
Could I suggest working on the summernote class for the text area instead of using your id?
I noticed when I use the id that my textarea's display property doesn't get set to none, but it works when i use the class="summernote".
<textarea asp-for="Instructions" class="summernote"></textarea>
<script>
$(document).ready(function () {
$('.summernote').summernote();
});
</script>
Put this script in your page head:
<script src="https://cdn.ckeditor.com/4.13.0/standard/ckeditor.js"></script>
Lets say you have model called ForumModel where you save contents of editor. Property where you your content is saved is called answer:
public string Answer { get; set; }
So in your view you have following tag:
#model ForumModel
Therefore if you want to add editor:
<textarea id="editor1" asp-for="#Model.Answer" class="form-control" required=""></textarea>
<script>
CKEDITOR.replace("editor1");
</script>
And now all that is left is to call your controller on submit button. When your form is submitted you go to constructor that saves your contents.
public IActionResult Reply(ForumModel forumModel)
{
forumModel.SaveReply();
return RedirectToAction("SomeRandomPage");
}

Unable to return to the desired URL or view of the JSP

<script type="text/javascript" src="${pageContext.request.contextPath}/resources/js/jquery.validate.min.js"></script>
<script>
function setHiddenVal(){
var goAhead = true;
var myVal="I am hidden value";
document.getElementById("secretValue").value = myVal;
if (goAhead == true) {
document.forms["register-form"].submit();
}
}
</script>
</head>
<body>
<!--Main Container Starts here-->
<div class="main_container">
<div class="header">
<div class="right_panel">
<h2 align="center"><u>User Master</u></h2>
<div class="top-form">
<div>
**<form:form action="/usermaster" modelAttribute="CustomerForm" id="register-form" method="POST">**
<table cellspacing="0" cellpadding="0" border="" class="form1">
<tr>
<td class="label">Name:</td>
<td>
<form:input path="firstname"/>
</td>
</tr>
<tr>
<td class="label">Password:</td>
<td>
<form:input path="password"/>
</td>
</tr>
</tbody>
</table>
<div>
<table>
<tr>
<td> </td>
<td>
<input type="button" class="btn blue px16" value="Search" />
<input type="button" name="submit" id="btnsubmit" value="Submit" onclick="setHiddenVal();"/>
<input type="button" class="btn blue px16" value="Clear" />
<input type="button" class="btn blue px16" value="Change Password" />
<input type="button" class="btn blue px16" value="Manage User Notification Profile" />
</td>
</tr>
</table>
</div>
</form:form>
</div>
</div>
<div class="clear"></div>
</div>
</div>
</div>
</body>
</html>
so above one is my code for jsp and below is the code of controller
#RequestMapping(value={"/usermaster" }, method = RequestMethod.POST)
public final String addUserMaster(#ModelAttribute("CustomerForm") CustomerForm pricing, Map<String, Object> map,
Model model, HttpServletRequest request) {
System.out.println("the first name is "+pricing.getFirstname());
System.out.println("the password is "+pricing.getPassword());
return "usermaster";
}
#RequestMapping(value={"/showusermaster" }, method = RequestMethod.GET)
public String showPage(ModelMap model){
model.addAttribute("CustomerForm", new CustomerForm());
return "usermaster";
}
But my page gets open up using a popup with the url:
C:\Users\ganganshu.s\AppData\Local\Microsoft\Windows\Temporary Internet Files\Content.IE5\YW6383E8\usermaster
so it should open like
http://localhost:8080/enbee/usermaster
Could you please tell me what should I put in the form action.. as I think some mistake is there in the form action does in spring MVC we put the action like in the case I mentioned above.
Spring confg file is given below :
<mvc:interceptors>
<bean class="com.enbee.admin.interceptor.AuthenticationInterceptor" />
<!-- Declare a view resolver-->
<bean id="jspViewResolver"
class="org.springframework.web.servlet.view.InternalResourceViewResolver"
p:prefix="/WEB-INF/jsp/" p:suffix=".jsp" p:order="1" />
and the jsp name is usermaster.jsp
and in the sidemenu.jsp I have changed to this :
<li>User Master</li>
Change the method parameter of RequestMapping annotation to RequestMethod.POST:
#RequestMapping(value="/usermaster", method = RequestMethod.POST)
public final String addUserMaster(...){
...
}
so that when you submit your form to the URL /usermaster using method="POST" this method will get executed.
You also need to have a method (mapped to an URL) that will show this page to the user. You can use a method as below for this:
#RequestMapping(value = "/showusermaster", method = RequestMethod.GET)
public String showPage(ModelMap model){
model.addAttribute("CustomerForm", new CustomerForm());
return "usermaster";
}
With this method in place, the URL
http://localhost:8080/enbee/showusermaster
will show the usermaster.jsp page to the user. Now when you submit this form, the above addUserMaster method will be invoked.
You don't have to create new jsp file. The url /showusermaster will return the usermaster.jsp to the user, where the user can add form values and submit the form:
<%# taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
...
<c:url var="submitUrl" value="/usermaster">
<form:form id="form" action="${submitUrl}" modelAttribute="CustomerForm" method="POST">
Now when the user clicks on the submit button, this form will be submitted to /usermaster URL and will be handled by addUserMaster method.
Try to specify the content type returned by your controller method, adding produces = "text/html" param to your #RequestMapping annotation.