This question already has an answer here:
Trailing slash is not inserted in Spring Boot application
(1 answer)
Closed 5 years ago.
I'm trying to call a java method with post mapping on a button click using form actions, I tried a lot, unfortunately, the method is not calling, shocking to me is that the nearly same code is working absolutely fine in some other project.
Here's the controller class:
#Controller
#RequestMapping("/question")
public class QuestionController {
#Autowired
GenericClient client;
#GetMapping("/")
public ModelAndView createDashboardView(){
ModelAndView modelAndView = new ModelAndView("views/question");
List<QuestionDTO> questions = client.genericClient(null, "question/fetchAllQuestions");
QuestionsList questionsList = new QuestionsList();
questionsList.setId1(questions.get(0).getId());
questionsList.setQuestion1(questions.get(0).getDescription());
questionsList.setId2(questions.get(1).getId());
questionsList.setQuestion2(questions.get(1).getDescription());
questionsList.setId3(questions.get(2).getId());
questionsList.setQuestion3(questions.get(2).getDescription());
modelAndView.addObject("questionsList", questionsList);
return modelAndView;
}
#PostMapping("/save")
public ModelAndView onSaveClick(QuestionsList questionsList, BindingResult result){
ModelAndView modelAndView = new ModelAndView("views/question");
System.out.println("Inside method");
System.out.println(questionsList.getQuestion2());
return modelAndView;
}
}
Here's the HTML file with Thymeleaf:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorator="views/master">
<body>
<div layout:fragment="page-content">
<div class="container-fluid">
<!-- BEGIN PAGE CONTENT-->
<div class="row-fluid errmsg" id="dvError"
visible="false">
<div style="width: 100%; text-align: center;">
<div class="message success">
<p></p>
</div>
</div>
</div>
<div class="row-fluid">
<div class="span12">
<!-- BEGIN SAMPLE FORM PORTLET-->
<div class="portlet box blue tabbable">
<div class="portlet-title">
<h4>
<i class="icon-reorder"></i><span class="hidden-480">Add/Edit
Questions</span>
</h4>
</div>
<div class="portlet-body form">
<div class="tabbable portlet-tabs">
<ul class="nav nav-tabs">
</ul>
<div class="tab-content">
<div class="tab-pane active" id="portlet_tab1">
<!-- BEGIN FORM-->
<div class="control-group"></div>
</div>
<div class="mytable" style="overflow: auto;">
<div class="control-group">
<div class="controls questionMar01 questionColor ">
<!-- Note:- Please enter ## in your question where you want company name -->
</div>
</div>
<form class="form-horizontal" action="#" th:action="#{/question/save}" th:object="${questionsList}" method="POST">
<div>
<input th:field="*{question1}" style="height:30px" id="txtQuestion1"
placeholder="Enter Question 1 Here" class="m-wrap large"
type="text" />
<br/><br/>
<input th:field="*{question2}" style="height:30px" id="txtQuestion2"
placeholder="Enter Question 2 Here" class="m-wrap large"
type="text"/>
<br/><br/>
<input th:field="*{question3}" style="height:30px" id="txtQuestion3"
placeholder="Enter Question 3 Here" class="m-wrap large"
type="text" />
</div>
<div> </div>
<div class="form-actions">
<button type="submit" class="btn blue okMark">Save</button>
<!-- <button id="btncancel" class="btn cancel"
OnClick="btncancel_Click">Cancel</button> -->
</div>
</form>
<!-- END FORM-->
</div>
</div>
</div>
</div>
<!-- END SAMPLE FORM PORTLET-->
</div>
</div>
<!-- END PAGE CONTENT-->
</div>
</div>
</div>
</body>
</html>
Here I'm trying to call /question/save/ post mapping on Save button click, unfortunately, onSaveClick method is not calling. Please help me out. Thanks
Project Structure:
Network tab after Save button click:
More information:
2018-01-18 17:40:15.741 INFO 9264 --- [ restartedMain] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/question/],methods=[GET]}" onto public org.springframework.web.servlet.ModelAndView com.beezyadmin.controller.QuestionController.createDashboardView()
2018-01-18 17:40:15.742 INFO 9264 --- [ restartedMain] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/question/save],methods=[POST]}" onto public org.springframework.web.servlet.ModelAndView com.beezyadmin.controller.QuestionController.onSaveClick(com.beezyadmin.dto.QuestionsList,org.springframework.validation.BindingResult)
/question/save is getting mapped as shown in the logs and also I'm able to call the method directly using PostMan, however, I'm unable to do so from Thymeleaf html page on Save button click.
Source code from view page source:
Finally, there is something wrong the layout file, which causes clicking button perform an GET instead POST automatically for some reason.
After removing layout file every thing works.
Your controller has mapping "/question/save"
BUT
Your th:action has a url of "/question/save/"
Watch out the trailing slash.
Related
When I use create action is make me a problem for update action th:value=*{name} How to set th:field values to not conflict each other.
Here is the error:
There was an unexpected error (type=Internal Server Error, status=500).
Error during execution of processor 'org.thymeleaf.spring5.processor.SpringInputGeneralFieldTagProcessor' (template: "designations" - line 243, col 18)
Here is html
<div class="modal-body">
<form th:action="#{/designations/create}" th:object="${newDesignation}" th:method="post">
<div class="form-group">
<label>Designation Name <span class="text-danger">*</span></label>
<input th:field="*{name}" class="form-control" type="text">
</div>
<div class="form-group">
<label>Department <span class="text-danger">*</span></label>
<select th:field="*{departmentName}" class="select">
<option th:each="i : ${departmentsList}" th:value="${i.name}" ></option>
</select>
</div>
<div class="submit-section">
<button class="btn btn-primary submit-btn">Submit</button>
</div>
</form>
</div>
<div class="modal-body">
<form th:action="#{/designations/update/{id}(id=${i.id})}" th:object="${designationToUpdate}" th:method="post">
<div class="form-group">
<label>Designation Name <span class="text-danger">*</span></label>
<input th:field="*{name}" class="form-control" type="text">
</div>
<div class="form-group">
<label>Department <span class="text-danger">*</span></label>
<select th:field="*{departmentName}" class="select">
<option th:each="i : ${departmentsList}" th:value="${i.name}" th:text="${i.name}"></option>
</select>
</div>
<div class="submit-section">
<button class="btn btn-primary submit-btn">Save</button>
</div>
</form>
</div>
With simple spring-boot-starter(web,thymeleaf,lombok), this simple app:
#Controller
#SpringBootApplication
public class ThymeleafTestApplication {
public static void main(String[] args) {
SpringApplication.run(ThymeleafTestApplication.class, args);
}
#GetMapping
public String test() {
return "test";
}
#ModelAttribute("newDesignation")
public MyDto newDesignation() {
return new MyDto("foo");
}
#ModelAttribute("designationToUpdate")
public MyDto designationToUpdate() {
return new MyDto("bar");
}
}
#Data
#AllArgsConstructor
class MyDto {
String name;
}
and a simplified html:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org" lang="en">
<head>
<title>Test same input name</title>
</head>
<body>
<h2>Test same input name</h2>
<form th:action="#{/foo}" th:object="${newDesignation}" th:method="post">
<div>
<label>Designation Name <span>*</span></label>
<!-- recommended to use an alternative/identifiyng th:id here:-->
<input th:field="*{name}" type="text" />
</div>
<div>
<button>Submit</button>
</div>
</form>
<form th:action="#{/bar}" th:object="${designationToUpdate}" th:method="post">
<div>
<label>Designation Name <span>*</span></label>
<!-- ... and here: -->
<input th:field="*{name}" type="text" />
</div>
<div>
<button>Save</button>
</div>
</form>
</body>
</html>
Thymeleaf makes no problems:
So (for me) it is proven, that thymeleaf makes "no conflicts" rendering two <input/> fields with identical names in different or even same (->list attribute) <form/>.
The duplicate id on the other hand is not nice/html conform! (...not for thymeleaf, but for client side usage.) Please resolve it, e.g. like:
<input th:id="newName" ...
<!-- ... and -->
<input th:id="updateName" ...
With devtools, please ensure:
spring.thymeleaf.cache=false
for development environment (ONLY!;), this will increase the reliability of observations.
...and please follow the stack trace to the end! (I find them quite speaking in opposite to other templating frameworks:).
We can quickly reproduce some similar error message by (artificially introducing a typo):
#Data
#AllArgsConstructor
class MyDto {
String namy; //!
}
-> rebuild, reload ->
2021-12-30 02:32:53.104 ERROR 20564 --- [nio-8080-exec-1] org.thymeleaf.TemplateEngine:
[THYMELEAF][http-nio-8080-exec-1] Exception processing template "test":
An error happened during template parsing (template: "class path resource [templates/test.html]")
#... like 1000 lines of stacktrace, referring to (first occurence of) *{name}, but then:
Caused by:
org.springframework.beans.NotReadablePropertyException:
Invalid property 'name' of bean class [com.example.thymeleaf.test.MyDto]:
Bean property 'name' is not readable or has an invalid getter method:
Does the return type of the getter match the parameter type of the setter?
#... few (hundred) more
I'm working on a Spring Boot application. I use main.html as a frame around all the other .htmls embedded. For this I use layout:fragment.
I also have a "DATABASE" menu in my navigaton bar, which points to the H2 database console.
Is it possible to embed this console into the main.html as fragment?
main.html's body:
<body>
<div class="container">
<nav class="navbar navbar-default">
<ul class="nav navbar-nav">
<li>HOME</li>
<li>BLOGS</li>
<li>BLOGGERS</li>
<li sec:authorize="hasRole('ROLE_ADMIN')">ADMIN</li>
<li sec:authorize="hasRole('ROLE_ADMIN')">DATABASE</li>
<li><button id="signup-small-button" class="btn btn-success">SIGN UP</button></li>
</ul>
<div class="navbar-right">
<form sec:authorize="!isAuthenticated()" th:action="#{/loginfailed}" method="post">
<input th:value="${username}" type="text" name="username" placeholder="username" required class="login-input"/>
<input th:value="${password}" type="password" name="password" placeholder="password" required class="login-input"/>
<button type="submit" id="login-button" class="btn btn-success btn-xs">Log in</button>
</form>
<div sec:authorize="isAuthenticated()">
<span th:text="${#authentication.name + ' | '}" class="black"></span>
<form th:action="#{/logout}" method="post" id="logout-button-form">
<button type="submit" id="logout-button" class="btn btn-primary btn-xs">Log out</button>
</form>
</div>
</div>
</nav>
<div layout:fragment="content"></div>
<footer>
<hr/>
<p id="footer">Made by hazazs (©Copyrighted by hazazs™)</p>
</footer>
</div>
</body>
The solution was a database.html file:
<html layout:decorate="~{layout/main}">
<head>
<title>DATABASE</title>
</head>
<body>
<div layout:fragment="content" class="center">
<iframe src="http://IP:PORT/h2" id="h2-console"></iframe>
</div>
</body>
</html>
And of course a #RequestMapping for "/database" which returns this html.
#RequestMapping(value = "/database", method = RequestMethod.GET)
public String database() {
return "database";
}
application.yaml:
spring:
h2:
console:
enabled: true
path: /h2
So I have the following written in Thymeleaf:
<form th:action="#{|/recipes/${recipe.id}/delete|}" method="post" id="recipeDeleteForm">
</form>
And the following in Java:
#RequestMapping(value = "/recipes/{recipeId}/delete", method = RequestMethod.POST)
public String deleteRecipe(#PathVariable Long recipeId, RedirectAttributes redirectAttributes){
Recipe recipe = recipeService.findOne(recipeId);
recipeService.delete(recipe);
return "redirect:/";
}
The problem is that when I press the delete button on the browser, the request will only send the number 1 to the controller, instead of sending the specific object id. So it will always delete the first element from the list, not the one that I chose.
EDIT: Actually, this is incremental. If I delete a random object from the list first time, it will delete the first item from the list, if I delete another random element from the list, it will delete the second one, and so on...
Something else to add would be that this doesn't happen if I try to go to a detail page, or I try to edit the object. It will send the correct id to the controller. This happens only for the delete request.
An important difference here would be that the edit and detail actions are links, and delete is a form action.
This is how the entire markup file looks like:
<!DOCTYPE html>
<html lang="en">
<head th:replace="layout :: head"></head>
<body>
<nav th:replace="layout :: nav"></nav>
<div class="grid-container">
<div th:replace="layout :: logo"></div>
<div class="grid-100">
<div class="recipes">
<div class="grid-100 row controls">
<div class="grid-30">
<select>
<option value="">All Categories</option>
<option value="breakfast">Breakfast</option>
<option value="lunch">Lunch</option>
<option value="dinner">Dinner</option>
<option value="dessert">Dessert</option>
</select>
</div>
<div class="grid-40">
<input placeholder="Search"></input>
</div>
<div class="grid-30">
<div class="flush-right">
<a th:href="#{/recipes/add-new-recipe}"><button>+ Add Recipe</button></a>
</div>
</div>
</div> <div class="clear"></div>
<div class="grid-100 row addHover" th:each="recipe : ${recipes}">
<a th:href="#{|/recipes/${recipe.id}/detail|}">
<div class="grid-70">
<p>
<span><img th:src="#{/images/favorite.svg}" height="12px"/> </span>
<span th:text="${recipe.name}"></span>
</p>
</div>
</a>
<div class="hoverBlock">
<div class="grid-30">
<div class="flush-right">
<p>
<a th:href="#{|/recipes/${recipe.id}/edit|}"> <img th:src="#{/images/edit.svg}" height="12px"/> Edit </a>
<img th:src="#{/images/delete.svg}" height="12px"/> Delete
<form th:action="#{|/recipes/${recipe.id}/delete|}" method="post" id="recipeDeleteForm">
</form>
</p>
</div>
</div>
</div>
</div><div class="clear"></div>
<div class="row"> </div>
</div>
</div>
</div>
</body>
</html>
The problem was that I was using an <a> tag to submit the delete form:
<img th:src="#{/images/delete.svg}" height="12px"/> Delete
<form th:action="#{|/recipes/${recipe.id}/delete|}" method="post" id="recipeDeleteForm">
</form>
The solution is to use a button instead, inside the form tags, and eliminate the anchor:
<form th:action="#{|/recipes/${recipe.id}/delete|}" method="post" id="recipeDeleteForm">
<button type="submit">Delete</button>
</form>
Here's my form.
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head th:replace="header :: head"></head>
<body>
<div th:replace="header :: head-nav"></div>
<div id="wrapper">
<div class="container-fluid">
<div th:each="catFeatGroup,status : ${catFeatGroupList}" class="form-group">
<label>Position</label><input th:field="$ {catFeatGroupList[__${status.index}__].position}" th:value="${catFeatGroup.position}" />
<label>Name</label> <input name="${catFeatGroupList[__${status.index}__].name}" th:value="${catFeatGroup.name}" />
</div>
<button type="submit" class="btn btn-default">Submit</button>
</div>
</div>
<div th:replace="footer :: foot"></div>
</body>
</html>
And here's the controller that gives it values
#RequestMapping("/category/edit/{id}")
#ModelAttribute("catFeatGroupList")
#org.springframework.transaction.annotation.Transactional
public ModelAndView displayCategoryList(#PathVariable("id")Integer id){
ModelAndView mav = new ModelAndView("category-form");
List<CatFeatGroup> catFeatGroupList = catFeatGroupService.findGroupsForCategory(id);
mav.addObject("catFeatGroupList",catFeatGroupList);
return mav;
}
}
However the page returns an error
java.lang.IllegalStateException: Neither BindingResult nor plain target object for bean name 'catFeatGroupList[0]' available as request attribute
What am I missing
The expression th:field="${catFeatGroupList[__${status.index}__].position}" is creating the error.
The th:field attribute can only be used within a html form tag, to access the fields of the defined form.
th:field attribute generates 3 html attributes, id, name and value.
So your code can be modified as :
<div th:each="catFeatGroup,status : ${catFeatGroupList}" class="form-group">
<label>Position</label>
<input th:id="${'catFeatGroupList'+__${status.index}__+'.position'}" th:name="${'catFeatGroupList['+__${status.index}__+'].position'}" th:value="${catFeatGroup.position}" />
<label>Name</label>
<input th:id="${'catFeatGroupList'+__${status.index}__+'.name'}" th:name="${'catFeatGroupList['+__${status.index}__+'].name'}" th:value="${catFeatGroup.name}" />
</div>
I have used th:id, th:name and th:value instead of th:field.
If a html form tag available, you can use th:field, instead of using th:id, th:name and th:value like this
<form th:object="${modelObject}">
<div th:each="catFeatGroup,status : *{catFeatGroupList}" class="form-group">
<label>Position</label>
<input th:field="*{catFeatGroupList[__${status.index}__].position}" />
<label>Name</label>
<input th:field="*{catFeatGroupList[__${status.index}__].name}" />
</div>
</form>
This page displays several records when i select the Crime Number form the href tag i only want to post the data in that row only. Can someone tell me where i am going wrong here:
I did a different form for each record that returns from the server but only the first record gets returned all the time.
EDIT
The page is POST using java script and the Id its receiving is NULL
The crimeRecNo is posted accurately however the victim and criminal list only the first row is posted and not the selected one.Why????
function submitPage(crimeRecNo) {
document.getElementById("crimeList"+$('#crimeRecNo').val()).action = "getCrime/"+ crimeRecNo + ".htm";
document.getElementById("crimeList"+$('#crimeRecNo').val()).method = "POST";
document.getElementById("crimeList"+$('#crimeRecNo').val()).submit();
}
Html
</head>
<body>
<c:forEach items="${crimes}" var="crime">
<form:form id="crimeList${crime.crimeRecNo}" name="crimeList" commandName="crime">
<div id="content">
<div class="row-${crime.crimeRecNo}">
<h2>
<a class="crimeRecNo" href="${crime.crimeRecNo}">Crime Record
Number : ${crime.crimeRecNo}</a><form:input path="crimeRecNo" id="crimeRecNo"
value="${crime.crimeRecNo}" />
</h2>
<div class="crimeReport">
<label>${crime.crimeDetails}</label>
</div>
<div id="container">
<div id="crimePhotoz">
<div id="victimLabel">
<label class="heading">Victims/Witness In Crime</label>
</div>
<div class="grid_row">
<c:forEach items="${crime.victims}" var="victim">
<input type="hidden" value="${victim.photo}" class="foto" />
<canvas class="canvas" height="200" width="200"></canvas>
</c:forEach>
<c:forEach items="${crime.victims}" var="victim">
<div class="names">
<a class="crimeNames" href="${victim.socialSecurityNumber}">${victim.name}</a>
<form:input path="victims" id="victims" value="${victim.socialSecurityNumber}" />
</div>
</c:forEach>
</div>
<div id="criminalLabel">
<label class="heading">Criminals In Crime</label>
</div>
<div class="grid_row2">
<c:forEach items="${crime.criminals}" var="criminal">
<input type="hidden" value="${criminal.photo}" class="foto" />
<canvas class="canvas" height="200" width="200"></canvas>
</c:forEach>
<c:forEach items="${crime.criminals}" var="criminal">
<div class="names">
<a class="crimeNames" href="${criminal.socialSecurityNumber}">${criminal.name}</a>
<form:input path="criminals" id="criminals" value="${criminal.socialSecurityNumber}" />
</div>
</c:forEach>
</div>
</div>
</div>
</div>
<hr>
</div>
</form:form>
</c:forEach>
</body>
</html>
Maybe you are missing some quotes?
document.getElementById(${crime.crimeRecNo})
If ${crime.crimeRecNo} is a string this becomes
document.getElementById(myRecNo)
which is invalid javascript. In this case you should add quotes:
document.getElementById("${crime.crimeRecNo}")
If ${crime.crimeRecNo} is a number you don't need quotes but then you might have invalid ids. According to this html ids must at least start with a character. Iam not sure if this can cause problems but maybe you should try <form id="record-${crime.crimeRecNo}" .. >