I have a page where I get a list of entries. Now, I want to be able to search from those list.
my current url for retrieving list is this /show/products. I want to add a search form in this page so that I can search with request parameter.
Yes, I can use ajax but I have to do it with request parameters.
So if I search for a product name, then - /show/products?name=someName
<form ui-jp="parsley" th:action="#{/show/products(name=${pName})}" th:object="${pName}" method="get">
<div class="row m-b">
<div class="col-sm-6">
Search by Name:
<input id="filter" type="text" th:field="*{pName}" class="form-control input-sm w-auto inline m-r"/>
<button class="md-btn md-fab m-b-sm indigo">
<i class="material-icons md-24"></i>
</button>
</div>
</div>
</form>
And this is what I tried in controller:
#GetMapping("/show/products")
public String getProduct(Model model,
#RequestParam(required = false) String name,
#ModelAttribute String pName) {
List<Product> products = this.productService.getAllProducts(name)
model.addAttribute("products", products);
return "show_product";
}
I am getting this error:
Neither BindingResult nor plain target object for bean name 'pName' available as request attribute
at org.springframework.web.servlet.support.BindStatus.<init>(BindStatus.java:153)
at org.springframework.web.servlet.support.RequestContext.getBindStatus(RequestContext.java:897)
You are trying to use variable pName (Model attribute) as a form object.
In your view you are passing a model attribute to form like this th:object="${pName}" but instead you need to pass a form object.
A form object is not a class but rather a simple java object (POJO). You can think of form object as your form but on server side.
Before you can use form object in your view, you need to create it and add it to the Model.
you will define it like this
public class MyFormObject{
private String pName;
public String getPName(){
return pName;
}
public void setPName(String pName){
this.pName = pName;
}
}
now your controller method will become
#GetMapping("/show/products")
public String getProduct(Model model,
#ModelAttribute("myFormObject") MyFormObject myFormObject,
BindingResult result) {
List<Product> products = this.productService.getAllProducts(myFormObject.getPName());
model.addAttribute("products", products);
return "show_product";
}
Then you can pass the form object to your form like this
<form ui-jp="parsley" th:action="#{/show/products}" th:object="${myFormObject}" method="get">
<div class="row m-b">
<div class="col-sm-6">
Search by Name: <input id="filter" type="text" th:field="*{pName}" class="form-control input-sm w-auto inline m-r"/>
<button class="md-btn md-fab m-b-sm indigo"><i class="material-icons md-24"></i></button>
</div>
</div>
</form>
You need to read the documentation, all these are explained there in detail.
Related
I have two hidden input fields to implement Friend system. I pass user and friend's ids in Model and then use them in thymeleaf page to pass them in form to PostMapping and save changes. However, PostMapping cannot see my second #RequestParam.
Both customer and friend are properly passed to model as I tried to output them on website using th:text
Snippets of code:
Adding both users to model:
#GetMapping("/customer/{customerId}")
public String getCustomer(Model theModel, #PathVariable int customerId, #AuthenticationPrincipal MyUserDetails user) {
Customer currUser = customerService.findById(user.getCustomer().getId());
Customer foundCustomer = customerService.findById(customerId);
theModel.addAttribute("friend", foundCustomer);
theModel.addAttribute("customer", currUser);
return "customerdetails";
Snippet of Thymeleaf code:
<form action="#" th:action="#{/home/addFriend}" th:object="${friend}" method="post">
<input type="hidden" th:field="${friend.id}" th:attr="name='friendId'" />
<input type="hidden" th:field="${customer.id}" th:attr="name='customerId'" />
<input type="submit" value="Add Friend" class="btn btn-primary flex-grow-1" />
</form>
PostMapping (where issue occurs):
#PostMapping("/addFriend")
public String getPost(#RequestParam("friendId") int friendId, #RequestParam("customerId") int customerId) {
Customer friendCustomer = customerService.findById(friendId);
Customer currCustomer = customerService.findById(customerId);
System.out.println(currCustomer.getFirstName());
System.out.println(friendCustomer.getFirstName());
return "redirect:/home";
}
Code of error:
[org.springframework.web.bind.MissingServletRequestParameterException: Required request parameter 'friendId' for method parameter type int is not present]
It will be a lot easier to implement using a custom form object.
For example, create this class:
public class AssignFriendFormData {
private String friendId;
private String customerId;
// getter and setters here
}
Use this in your #GetMappping:
#GetMapping("/customer/{customerId}")
public String getCustomer(Model theModel, #PathVariable int customerId, #AuthenticationPrincipal MyUserDetails user) {
Customer currUser = customerService.findById(user.getCustomer().getId());
Customer foundCustomer = customerService.findById(customerId);
AssignFriendFormData formData = new AssignFriendFormData();
formData.setFriendId(foundCustomer.getId());
formData.setCustomerId(currUser.getId());
theModel.addAttribute("formData", formData);
return "customerdetails";
Change the form to this:
<form action="#" th:action="#{/home/addFriend}" th:object="${formData}" method="post">
<input type="hidden" th:field="*{friendId}" />
<input type="hidden" th:field="*{customerId}" />
<input type="submit" value="Add Friend" class="btn btn-primary flex-grow-1" />
</form>
Finally, update the #PostMapping to use the form data object:
#PostMapping("/addFriend")
public String getPost(#Valid #ModelAttribute("formData") AssignFriendFormData formData) {
Customer friendCustomer = customerService.findById(formData.getFriendId());
Customer currCustomer = customerService.findById(formData.getCustomerId());
System.out.println(currCustomer.getFirstName());
System.out.println(friendCustomer.getFirstName());
return "redirect:/home";
}
See Form handling with Thymeleaf for a more in-depth tutorial on this.
I am having a small issue with Spring Boot forms displaying the information of the path value instead of the placeholder once you get to the editProfile.jsp. I want the input field to look like this;
Edit Profile Page instead of this Wrong Edit Profile. I do not want my users to have to click, select and delete the auto completed value. I want it to show the placeholder only and allow them to overwrite what is shown with ease.
This is the editProfile.jsp
<%--#elvariable id="editProfile" type=""--%>
<form:form method="POST" modelAttribute="editProfile">
<div class="MyForm form-group">
<h1>Edit Profile</h1>
<form:input type="email" class="MyInput" id="email" path="email" placeholder="${editProfile.email}" />
<form:button type="submit" class="from-control">Submit</form:button>
</div>
<div>
<img src="images/reg1.png" alt="picture">
</div>
</form:form>
</body>
</html>
This is the code specified in the Controller
#RequestMapping(value = "edit/{email}", method = RequestMethod.GET)
public String getEditUserData(#PathVariable("email") String email, Model model) {
AccountEntity accountInstance = accountRepo.findByEmail(email);
model.addAttribute("editProfile", accountInstance);
return "editProfile";
}
#RequestMapping(value = "edit/{email}", method = RequestMethod.POST)
public String enterEditUserData(#ModelAttribute("login") AccountEntity accountForm, #PathVariable("email") String email, Model model ) {
AccountEntity accountInstance = accountRepo.findByEmail(email);
accountInstance.setEmail(accountForm.getEmail());
accountRepo.save(accountInstance);
return "redirect:/login";
}
I have figured it out; You have to add a model of a new Entity, so the path variable does not fill in with the instance of the specific path value. Here is the new code, and compare it to the one I sent above.
#RequestMapping(value = "edit/{email}", method = RequestMethod.GET)
public String getEditUserData(#PathVariable("email") String email, Model model) {
AccountEntity accountInstance = accountRepo.findByEmail(email);
model.addAttribute("editProfile2", new AccountEntity());
model.addAttribute("editProfile1", accountInstance);
return "editProfile";
}
<%--#elvariable id="editProfile" type=""--%>
<%--#elvariable id="editProfile2" type=""--%>
<form:form method="POST" modelAttribute="editProfile2">
<div class="grid form-group">
<h1>Edit Profile</h1>
<form:input type="email" class="MyInput" id="email" path="email" placeholder='${editProfile1.email}' />
<form:button type="submit" class="from-control">Submit</form:button>
</div>
A springboot-thymeleaf newbie, I have been reading many similar questions on this topic but I'm still missing something in the syntax and overall Springboot Thymeleaf paradigm.
The web application pulls project data from the backend database which is rendered by Thymeleaf templates. The user also can generate a pdf report of the data which is also rendered in the browser.
The entity:
#Entity
#Table(name = "REPORT_CS")
public class ReportItemCs {
public ReportItemCs () {}
#Id
#Column(name = "ITEM_ID")
private Long itemId;
#Column(name = "PROJ_NUM")
private String projectNumber;
#Column(name = "REGION")
private String region;
// additional fields, getters, setters
First, something that works:
A simple text field in thymeleaf is used to pass a string in post request. The controller picks it up where it's passed to the repo as a query parameter on projectNumber. The query returns a list of objects which thymeleaf renders in a table. Note there's no binding to any object - it's just posting the string from the UI then passing it as a query param.
Html:
//Works without binding to backend
<section layout:fragment="content">
<p>Quick Project Search:</p>
<form method="post">
<input type="text" name="keyword"
placeholder="Project number keywords" /> <input type="submit"
value="Search" />
</form>
<br /> <span th:text=" ${noProjectsMessage}"></span>
</section>
Controller:
#RequestMapping(value = "/", method = RequestMethod.POST)
public String showProject(String keyword, ModelMap model) {
List<ProjectView> p = repository.findByProjectsContaining(keyword);
if (p.size() == 0) {
model.put("noProjectsMessage",
String.format("Project with id containing \"%s\" not found...", keyword));
return "home";
} else {
model.put("projectViews", p);
}
return "show-projects";
}
Repository:
#Query("SELECT p FROM ProjectView p WHERE p.projectNumber like %?1%")
List<ProjectView> findByProjectsContaining(#Param("keyword") String keyword);
So, now, I need to add some checkboxes to provide additional filtering by region, project category, etc. I plan to use the checkboxes in two ways: 1) to dynamically filter the project list in the UI using jQuery and, also, to pass the checkbox values back to the controller so they can be used to populate a pdf template header. I'd then either do another database query or use Stream() to filter the list that was generated by the original query and send the filtered list to the pdf service. When the user clicks the "PDF" button, the checkbox values are forwarded to the pdf service where the report header and report are generated and returned as a byte stream in a separate tab.
Html
<div class="form-check">
<form th:action="#{/cs-report}" method="post">
<label for="form-check">Region</label>
<input class="form-check-input" type="checkbox" value="all" name="regions" id="allOffice" />
<label class="form-check-label" for="allOffice">Select All</label>
<input class="form-check-input" type="checkbox" value="region1" name="regions" id="region1"/>
<label class="form-check-label" for="region1">Region 1</label>
<input class="form-check-input" type="checkbox" value="region2" name="regions" id="region2"/>
<label class="form-check-label" for="region2">Region 2</label>
<input class="form-check-input" type="checkbox" value="region3" name= "regions" id="region3"/>
<label class="form-check-label" for="region1">Region 3</label>
<button type="submit" class="btn btn-primary">Test Checkboxes</button>
</form>
</div>
Controller
//Test the post method
#RequestMapping(value = "/cs-report", method = RequestMethod.POST)
public void printCheckboxValues(List<String> regions)
{
regions.foreach(s -> System.out.println(s));
}
Where, if this approach worked, the repo would look something like:
#Query("SELECT p FROM ProjectView p WHERE p.region IN 1")
List<ProjectView> findByRegion(#Param("regions") List<String> regions);
I think the controller uses the name attribute to reference the list of checkbox values, but I'm unclear on how to set up the controller to do this. Most of the examples I've seen have had the checkboxes bound to their parent object, and maybe that's what needs to be done. I have the checkboxes hard coded as there aren't that many and I don't expect the values to change in the database. But if I do need to bind the checkbox "region" values to the reportCs entity, an example of syntax would be greatly appreciated.
Any other suggestions on approach are greatly appreciated, and big bonus if the code can be generalized to take multiple params from multiple checkbox groups. Thank you.
OK, the checkboxes need to bind to a form-backing bean. It took some tinkering with the Thymeleaf syntax, but doing it this way is actually is quite convenient for binding multiple checkbox groups to multiple query parameters. Also, I've realized hard coding the checkbox values in the templates is a bad idea (Not loosely-coupled code and will create problems down the line) so my next step is to get the checkbox values dynamically from the database. Thank you for reading.
html:
<div class="form-check">
<form action="#" th:action="#{/cs-report}" th:object="${queryDto}" method="post">
<button type="submit" class="btn btn-primary">Get Report</button>
<input class="form-check-input" type="checkbox" value="all" name="all" id="all" />
<label class="form-check-label" for="all">Select All</label>
<input class="form-check-input" type="checkbox" value="region1" name="regions" th:field="*{regions}" id="region1" />
label class="form-check-label" for="region1">Region 1</label>
<input class="form-check-input" type="checkbox" value="region2" name="regions" th:field="*{regions}" id="region2" />
label class="form-check-label" for="region2">Region 2</label>
<input class="form-check-input" type="checkbox" value="region3" name="regions" th:field="*{regions}" id="region3" />
label class="form-check-label" for="region3">Region 3</label>
</form>
</div>
The DTO object:
// Form-backing bean to hold checkbox values on post submission
public class QueryDto {
private List<String> regions;
// Getter, setters
Controller
#PostMapping(value = "/cs-report")
public String testCheckboxes(#ModelAttribute QueryDto queryDto) throws IOException {
List<ReportDto> dtos = repository.findByRegion(queryDto.getRegions());
dtos.foreach(s -> System.out.println(s.getProjectRegion()));
}
Repository
#Query("SELECT p FROM ProjectView p WHERE p.region IN :regions")
List<ProjectView> findByRegion(#Param("regions") List<String> regions);
Html
<form th:object="${klient}" th:action="#{/osoba}" method="post">
<div class="form-row">
<div class="form-group col-md-4">
<label >Imię</label>
<input type="text" class="form-control" th:field="*{imie}" >
</div>
<div class="form-group col-md-4">
<label >Nazwisko</label>
<input type="text" class="form-control" th:field="*{nazwisko}" >
</div>
<div class="select-list" id="selectlist">
<select th:field="*{UserId}" >
<option> -- </option>
<option th:each=" users : ${user}"
th:value="${users.UserId}"
th:utext="${users.lastName}"/>
</select>
</div>
Cod
#RequestMapping (value = "/osoba", method = RequestMethod.POST)
public String dodaj (Klient klient){
System.out.print(klient);
return "redirect:/osoba";
}
#RequestMapping (value = "/dodaj" , method = RequestMethod.GET)
public String tworz (Model model){
model.addAttribute("klient" , new Klient());
List<User> lista = userService.getAllUser();
model.addAttribute("user" , lista);
return "dodaj";
}
I want to create a form in which he completes the fields for the client and assign an existing user to him.The problem is that I can't get the selected user id.
I get a message about the first select
Error during execution of processor 'org.thymeleaf.spring5.processor.SpringSelectFieldTagProcessor
The error message shows that there is a problem with a property named uzytkownik , which I don't see anywhere in your template. Of course, you didn't include the complete template, so all I can say is that the problem is coming from somewhere else... apparently from line 178.
Create the getter and setter methods for userId in Klient:
public String getUserId()
public void setUserId(String userId)
I found a solution:
in select I changed
<select id="UserId" name="UserId" >
in controller
#RequestMapping (value = "/osoba", method = RequestMethod.POST)
public String dodaj (#ModelAttribute("UserId") Set<User> user, Klient klient){
klient.setUsers(user);
System.out.print(klient);
klientServicee.createOrUpdateKlient(klient);
return "redirect:/osoba";
}
it works but is it correct?
In my html I display all my assignment, exam and attendance objects. I want to find the ids of those objects and pass it over to the controller because I want the user to be able to edit those objects and in order to do that I need to find their id. The button to edit the object will be beside the objects name. That is how I want to do it anyway.
Here is my HTML:
div th:each="subject : ${subjects}">
Subject: <h4 th:text="${subject.subjectName}" />
/
<h4> assignments:</h4>
<div th:each="assignment : ${subject.assignment}">
<h4 th:text="${assignment.assignmentTitle}"/>
</div>
<h4> exams:</h4>
<div th:each="exam : ${subject.exam}">
<h4 th:text="${exam.examTitle}"/>
<h4 th:text="${exam.examId}"/>
</div>
<h4>Attendance:</h4>
<div th:each="attendance : ${subject.attendance}">
<h4 th:text="${attendance.attendanceTitle}"/>
</div>
Here is my Controller:
#GetMapping("/allSubjects")
public String shoSubjects(#ModelAttribute("subject") #Valid UserRegistrationDto userDto, BindingResult result, Model model) {
Authentication loggedInUser = SecurityContextHolder.getContext().getAuthentication();
String email = loggedInUser.getName();
User user = userRepository.findByEmailAddress(email);
List<Subject> subjects = user.getSubject();
model.addAttribute("subjects", user.getSubject());
Here is the solution. I was able to find the ID of the exam that the user selected to edit and it brings them to the edit HTML page.
My HTML:
<h4> exams:</h4>
<div th:each="exam : ${subject.exam}">
<h4 th:text="${exam.examTitle}"/>
<a th:href="#{/editExam.html(id=${exam.examId})}">Edit Exam</a>
Controller:
#RequestMapping(value = "/editExam.html{examId}", method =RequestMethod.GET)
public String editExam(#PathVariable String examId) {
return "editExam";
}