th:each indexing throwing errors - java

I am having issues indexing my for each objects. I have a list that I pull from the repository, that is called MonthlyAcct. I want to iterate over the list in the thymeleaf html file and show each property of the MonthlyAcct object displayed as an editable input field inside a table. I keep getting errors that Indexing into type 'monthAcct' is not supported, or currently, the error is: "Neither BindingResult nor plain target object for bean name 'monthAcct[0]' available as request attribute."
This is definitely an issue with how I've set up th:field, as if I switch it out to th:name, it shows up and does not throw errors. Do I have to make this into a form to get the th:field to work?
I have used this same style/tactic in other areas of my project and it works, so I am not sure why this time this type of set up is not working. Any ideas? I also have a different form on this page, that updates details for a client class, can this be causing any issues?
Just for reference I have tried * and $ with the th:each statement, and have tried both symbols with the th:field as well. Both throw the above mentioned error.
<table class="table table-striped" data-toggle="table" data-show-toggle="true" data-classes="table-no-bordered" data-striped="true" data-search="true" data-show-columns="true" >
<thead>
<th>year</th>
<th>January</th>
</thead>
<tbody>
<tr th:each="acct, stat : ${monthAcct}">
<td th:text="${acct.year}"></td>
<td>
<input type="number" class="text-left form-control" th:field="${monthAcct[__${stat.index}__].janAmt}"/>
</td>
</tr>
</tbody>
</table>
In the controller:
#RequestMapping(value="/accounting/client/{id}")
public String accountingDetails(#PathVariable("id")Client client, MonthlyAccountingTracker monthlyAccountingTracker, Model model) {
List<MonthlyAccountingTracker> monthAcct = monthlyAccountingTrackerRepository.findByClient(client);
model.addAttribute("client",clientRepository.findById(client.getId()));
model.addAttribute("monthAcct",monthAcct);
return "accounting";
}
#DynamicUpdate
#Entity
#Table(name="MonthlyMinAcctTracker")
#EntityListeners(AuditingEntityListener.class)
public class MonthlyAccountingTracker {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#ManyToOne
#JoinColumn(name="client")
private Client client;
private BigDecimal year;
private BigDecimal janAmt;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Client getClient() {
return client;
}
public void setClient(Client client) {
this.client = client;
}
public BigDecimal getJanAmt() {
return janAmt;
}
public void setJanAmt(BigDecimal janAmt) {
this.janAmt = janAmt;
}
}
I get my monthAcct list from the repository:
public interface MonthlyAccountingTrackerRepository extends CrudRepository<MonthlyAccountingTracker,Long>, JpaSpecificationExecutor {
MonthlyAccountingTracker save(MonthlyAccountingTracker entity);
MonthlyAccountingTracker findById(Long id);
List<MonthlyAccountingTracker> findByClient(Client client);
void delete(MonthlyAccountingTracker entity);
List<MonthlyAccountingTracker> findAll();
}

*{monthAcct} should be ${monthAcct} as you are setting the value in modelAndView or in your case model. The monthAcct is not a field of the th:object.

You can't use a List as a form backing object. You need another object that wraps your array (and to use that object as your backing object). For example:
Java:
// Backing object
class BackingObject {
List<MonthlyAccountingTracker> accounts;
public BackingObject(List<MonthlyAccountingTracker> accounts) {
this.accounts = accounts;
}
// Put in getters/setters...
}
// Controller
#RequestMapping(value="/accounting/client/{id}")
public String accountingDetails(#PathVariable("id")Client client, MonthlyAccountingTracker monthlyAccountingTracker, Model model) {
model.addAttribute("client",clientRepository.findById(client.getId()));
model.addAttribute("form", new BackingObject(monthlyAccountingTrackerRepository.findByClient(client)));
return "accounting";
}
Form:
<form th:object="${form}">
.
.
.
<tr th:each="acct, stat : *{accounts}">
<td th:text="${acct.year}"></td>
<td>
<input type="number" class="text-left form-control" th:field="*{accounts[__${stat.index}__].janAmt}"/>
</td>
</tr>
.
.
.
</form>

You should use an * instead of $ sign for monthAcct field:
<input type="number" class="text-left form-control" th:field="*{monthAcct[__${stat.index}__].janAmt}"/>

Related

how to populate a response from java spring boot with thymleaf

I am trying to return the data as a response body in java spring boot when a button is click in an html page.I have a list of countries displayed in my page with an edit button assigned to each. I want to find the data that was clicked by id so I have defined the method in my controller class. That's when the edit button is click, it should take the id of the country in the clicked row and display the information based on that id. When I test the api in Postman, it returns the data correctly but when I called the same api in my html page, it's giving me this error.
org.thymeleaf.exceptions.TemplateProcessingException: Exception evaluating SpringEL expression: "/findById/{id=${country.id}}" (template: "country" - line 555, col 26)
at org.thymeleaf.spring5.expression.SPELVariableExpressionEvaluator.evaluate(SPELVariableExpressionEvaluator.java:292) ~[thymeleaf-spring5-3.0.14.RELEASE.jar:3.0.14.RELEASE]
Caused by: org.springframework.expression.spel.SpelParseException: Expression [/findById/{id=${country.id}}] #0: EL1070E: Problem parsing left operand
This is my data class
Entity
#Data
#NoArgsConstructor
#AllArgsConstructor
public class Country {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String code;
private String capital;
private String description;
private String nationality;
private String continent;
}
My Controller class
#Controller
public class CountryController {
#Autowired
private CountryService countryService;
#GetMapping("/countries")
public String getCountry(Model model){
List<Country> countryList = countryService.getAllCountry();
model.addAttribute("countries",countryList);
return "country";
}
#PostMapping("/countries/addNew")
public String saveInfo(Country country){
countryService.saveCountryInfo(country);
return "redirect:/countries";
}
#GetMapping("/findById/{id}")
#ResponseBody
public ResponseEntity<Country> getCountryById(#PathVariable("id") Long countryId){ //Bind PathVariable id to id
return ResponseEntity.ok(countryService.getCountryById(countryId)) ;
}
#GetMapping("/country/code/{code}")
public Country getCountryCode(#PathVariable("code") String code){
return countryService.getCountryByCode(code);
}
}
My Service class
#Service
public class CountryService {
#Autowired
private CountryRepository countryRepository;
public List<Country> getAllCountry() {
return countryRepository.findAll();
}
public void saveCountryInfo(Country country){
countryRepository.save(country);
}
public Country getCountryById(Long id){
return countryRepository.findById(id).get();
}
public Country getCountryByCode(String code){
return countryRepository.findByCode(code);
}
}
My Repository class
#Repository
public interface CountryRepository extends JpaRepository<Country,Long> {
public Country findByCode(String code);
}
Here is the html code
<section class="section dashboard">
<div class="row">
<!-- Left side columns -->
<div class="row">
<div class="col-lg-9 col-md-12">
<div class="panel panel-default">
<div class="panel-heading">
<!-- Image background -->
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addModal" data-whatever="#mdo">Add A Country</button>
<h1>List of Country</h1>
<table class="table">
<thead>
<tr>
<th>Id</th>
<th>Code</th>
<th>Capital</th>
<th>Description</th>
<th>Nationality</th>
<th>Continent</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<tr th:each="country:${countries}">
<td th:text="${country.id}"></td>
<td th:text="${country.code}">Code</td>
<td th:text="${country.capital}">Capital</td>
<td th:text="${country.description}">Description</td>
<td th:text="${country.nationality}">Nationality</td>
<td th:text="${country.continent}">Continent</td>
<td>
<div class="btn-group">
<a th:href="${/findById/{id=${country.id}}}" class="btn btn-primary" id="editButton" data-bs-toggle="modal" data-bs-target="#editModal">Edit</a>
</div>
</td>
</tr>
</tbody>
</table>
</div><!-- End of Image background -->
</div><!-- End Left side columns -->
</div>
</div>
</div>
</section>

Binding With Multiple Tables Spring Thymeleaf

I know the title of this question is not clear, but before disliking let me explain my problem.
I have a social media with java spring using JPA and thymeleaf.
Users have ids, usernames, and creation dates.
In the posts table there is a field called "authorid", so when someone makes a new post the system only writes his user id, not his username.
Now, I have a page on the site, available at "localhost:8080/showAllPosts/" which shows ALL the application posts from the most recent to the least recent. The controller looks something like this:
#Controller
public class MyController {
#GetMapping("/showAllPosts")
public String showAllPosts(Model model) {
List<Post> post = new ArrayList<>();
model.addAttribute("post", post);
List<Post> posts = postRepository.findAll();
model.addAttibute("posts", posts);
return "showallposts.html";
}
}
The showallposts.html page looks like this:
<table>
<tbody>
<tr th:if="${posts.empty}">
<td colspan="2">No Posts Available</td>
</tr>
<tr th:each="post : ${posts}">
<td>
<span th:text="'posted by ' + ${post.authorid}>
<span th:text="${post.postcontent}"></span>
<br>
</td>
<td>
</td>
</tr>
</tbody>
</table>
As you can see, it shows the post content, and "posted by" plus the author. The only problem is that the post.authorid returns the numeric id of user who posted, and I wanted the username. Is there something we can do with the users model?
Notice that in the users repository I have this method:
#Query(value = "SELECT u.username FROM Users u WHERE u.userid = ?1")
public String findUsernameById(Long userid);
ALL help is appreciated! <3
Alternatively, you can use a DTO to move the post with the author's name into the Thymeleaf view.
Create a new class like PostDto:
public class PostDto {
private Long id;
private String postContent;
private Long authorId;
private String String authorName;
// other fields
// getters and setters
}
Convert your post entity class to PostDto using the Stream API. During the mapping, you can get the username by authorId via UserRespository and supplement the post dto object with the author's name.
#Controller
public class MyController {
#Autowired
private UserRepository userRepository;
#GetMapping("/showAllPosts")
public String showAllPosts(Model model) {
List<Post> post = new ArrayList<>();
model.addAttribute("post", post);
List<Post> posts = postRepository.findAll().stream()
.map(post -> {
PostDto dto = toDto(post);
dto.setAuthorName(userRepository.findUsernameById(post.getAuthorId()));
})
model.addAttibute("posts", posts);
return "showallposts.html";
}
private PostDto toDto(Post post) {
PostDto dto = new PostDto();
dto.setId(post.getId);
dto.setPostContent(post.getPostContent());
dto.setAuthorId(post.getAuthorId());
}
}
Use in the Thymeleaf view as ${post.authorName}.
<table>
<tbody>
<tr th:if="${posts.empty}">
<td colspan="2">No Posts Available</td>
</tr>
<tr th:each="post : ${posts}">
<td>
<span th:text="'posted by ' + ${post.authorName}>
<span th:text="${post.postcontent}"></span>
<br>
</td>
<td>
</td>
</tr>
</tbody>
</table>

Trying to create a composite jpa object in Spring + Thymeleaf

I've tried to create an object with another object inside, with a form but the Object picked from a dropdown list gets converted into a String when returned from the Thymeleaf form.
Those are the entities in my project, with an 1:n relatioship between them:
Entity User
//imports
#Entity
#Table(name = "USERS")
public class User {
#Id
#GeneratedValue
#Column( name ="USER_ID")
private int id;
#Column( name ="username")
private String username;
#Column( name ="password")
private String password;
#Column( name ="email")
private String email;
#OneToMany(fetch= FetchType.LAZY, mappedBy="user", cascade = CascadeType.ALL)
private List<Post> posts;
//setter & getters & toString
}
Entity Post
//imports
#Entity
#Table(name="POSTS")
public class Post {
#Id
#GeneratedValue
#Column(name="POST_ID")
private int id;
#Column(name="tittle")
private String tittle;
#Column(name="text")
private String text;
#ManyToOne
#JoinColumn(name="USER_ID",referencedColumnName="USER_ID")
private User user;
//getters & setters & toString
To create a new Post:
//In Controller
#RequestMapping(value = "/posts/new")
public String newPost(Model model) {
model.addAttribute("post", new Post());
model.addAttribute("users", userService.list());
return "addPost";
}
Which returns the template that has this form:
<form th:action="#{/savePost}" th:object="${post}" method="post">
<tr>
<td><input type="hidden" th:field="${post.id}" /></td>
</tr>
<tr>
<td>Titulo</td>
<td>Texto</td>
<td>Usuario</td>
</tr>
<tr>
<td><input type="text" th:field="${post.tittle}"
th:value="${post.tittle}" /></td>
<td><input type="text" th:field="${post.text}"
th:value="${post.text}" /></td>
<td><select th:field="${post.user}">
<option th:each="user : ${users}" th:text="${user.username}"
th:value="${user.id}"></option>
</select></td>
</tr>
<tr>
<td colspan="3"><input class="btn btn-primary" type="submit"
value="GUARDAR"></td>
</tr>
</form>
The action attribute in the form calls:
#PostMapping("/savePost")
//#RequestMapping(value = "users/save",method = RequestMethod.POST)
public String savePost(#ModelAttribute Post post) {
postService.add(post);
return "redirect:/posts";
}
At this point, I try to create a Post and select a User from the dropdown but when attempting to save it gives me this error:
Field error in object 'post' on field 'user': rejected value [16]; codes [typeMismatch.post.user,typeMismatch.user,typeMismatch.com.julian.bootmvchibernate.model.User,typeMismatch]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [post.user,user]; arguments []; default message [user]]; default message [Failed to convert property value of type 'java.lang.String' to required type 'com.julian.bootmvchibernate.model.User' for property 'user'; nested exception is org.springframework.core.convert.ConversionFailedException: Failed to convert from type [java.lang.String] to type [#javax.persistence.ManyToOne #javax.persistence.JoinColumn com.julian.bootmvchibernate.model.User] for value '16'; nested exception is java.lang.NullPointerException]
So I tried to implement a Formatter like so:
public class UserFormatter implements Formatter<User> {
#Autowired
#Qualifier("userService")
public GeneralService<User> userService;
#Override
public String print(User object, Locale locale) {
return (object != null ? object.getUsername() : "");
}
#Override
public User parse(String text, Locale locale) throws ParseException {
final Integer userId = Integer.parseInt(text);
return userService.get(userId);
}
}
Registering it:
#SpringBootApplication
public class BootmvchibernateApplication implements WebMvcConfigurer{
#SuppressWarnings("unchecked")
#Override
public void addFormatters(FormatterRegistry registry) {
registry.addFormatter(new UserFormatter());
}
public static void main(String[] args) {
SpringApplication.run(BootmvchibernateApplication.class, args);
}
}
But when this approach is tried the next error is found, this just uppon accessing the /post/new direcction (the template addPost doesn't work):
An error happened during template parsing (template: "class path resource [templates/addPost2.html]")
org.thymeleaf.exceptions.TemplateInputException: An error happened during template parsing (template: "class path resource [templates/addPost2.html]")
.....
Caused by: org.attoparser.ParseException: Error during execution of processor 'org.thymeleaf.spring5.processor.SpringOptionFieldTagProcessor' (template: "addPost2" - line 43, col 8)
.....
Caused by: org.springframework.core.convert.ConversionFailedException: Failed to convert from type [java.lang.String] to type [#javax.persistence.ManyToOne #javax.persistence.JoinColumn com.julian.bootmvchibernate.model.User] for value '2'; nested exception is java.lang.NullPointerException
at org.springframework.core.convert.support.ConversionUtils.invokeConverter(ConversionUtils.java:47)
If any more information is needed, tell me and I post it.
EDIT: this is the repository if someone is interested.
github.com/JulianBautistaVelez/JPA_Spring_Excercise
The problem is in your addPost.html except that everything is ok now.
<table>
<form th:action="#{/posts/new/mod}" th:object="${post}"
method="POST">
<tr>
<td><input type="hidden" th:field="${post.id}" /></td>
</tr>
<tr>
<td>Titulo</td>
<td>Texto</td>
<td>Usuario</td>
</tr>
<tr>
<td><input type="text" th:field="${post.tittle}"
th:value="${post.tittle}" /></td>
<td><input type="text" th:field="${post.text}"
th:value="${newPost.text}" /></td>
<!-- <td><select th:field="${newPost.user}">
<option th:each="user : ${users}" th:text="${user.username}"
th:value="${user.id}"></option>
</select></td> -->
<td><select th:field="*{user}" class="form-control">
<option th:each="user: ${users}"
th:value="${user.id}" th:text="${user.username}"></option>
</select></td>
</tr>
<tr>
<td colspan="3"><input class="btn btn-primary" type="submit"
value="GUARDAR"></td>
</tr>
</form>
</table>
I changed newPost as post , because you are adding in here as post model.
#RequestMapping(value = "/posts/new")
public String newPost(Model model) {
logger.info("-- en NEW Usuario");
model.addAttribute("post", new Post());
model.addAttribute("users", userService.list());
logger.info("-- -- -- LISTA DE USUARIOS -- -- --");
System.out.println(userService.list());
return "addPost";
}

MVC: Value in dropdown menu doesn't set to selected value - remains 0

This is a spring Boot application that uses thymeleaf template manager. It has a simple form with dropdown menu. Options are populated from database, both their names (or ids) can be displayed correctly on form but after selecting option and submiting form value of given selected variable remains 0.
While I get the correct value of variable content, categoryId always has value 0 after submit (or null if I change it's type from int to Integer).
I'm guessing that model isn't correctly "linked" to jokeForm but I don't know how to link it correctly. I was following example 1. I hope someone can eassily spot the problem just by quickly looking at my code. Code breakes in method submitForm().
HTML form:
<html>
<body>
<form action="#" th:action="#{/new}" th:object="${jokeForm}" method="post">
<table>
<tr>
<td>Content:</td>
<td><input type="text" th:field="*{content}" /></td>
<td th:if="${#fields.hasErrors('content')}" th:errors="*{content}">Content Error</td>
</tr>
<tr>
<td>Category:</td>
<td>
<select name="categoryId" th:field="*{categoryId}">
<option value="0" th:each="category : ${categories}"
th:value="${category.id}"
th:utext="${category.name}"/>
<!-- <option th:each="category : *{categories}"
th:value="*{category.id}"
th:utext="*{category.name}"/> -->
</select>
</td>
<td th:if="${#fields.hasErrors('categoryId')}" th:errors="*{categoryId}">category Error</td>
</tr>
<tr>
<td><button type="submit">Submit</button></td>
</tr>
</table>
</form>
</body>
</html>
Controller
#GetMapping("/new")
public String showForm( Model model) {
DEBUG("showForm");
JokeForm form = new JokeForm();
categories = categoryRepository.findAll();
DEBUG(categories.get(0).toString());
DEBUG(categories.get(1).toString());
//form.setCategories(categories); //not working
model.addAttribute("jokeForm", form);
model.addAttribute("categories",categories);
return "form";
}
#PostMapping("/new")
#ResponseBody
public String submitForm(#ModelAttribute JokeForm jokeForm) {
DEBUG("submitForm");
//String content = jokeForm.getContent();
DEBUG(jokeForm.getContent());
DEBUG(jokeForm.getCategoryId().toString());
Joke j = new Joke();
j.setContent(jokeForm.getContent());
//j.setCategoryId(jokeForm.getCategoryId());
//DEBUG(Integer.toString(jokeForm.getCategoryId()));
//CAUSES ERROR value of CategoryId is Integer -> null System.out.println(Integer.toString(jokeForm.getCategoryId()));
//PRODUCES ERROR value of CategorId is int (because no category matches) j.setCategory(categoryRepository.findById(jokeForm.getCategoryId().intValue()).get(0));
jokeRepository.save(j); //save
return "Saved";
}
JokeForm
public class JokeForm {
#NotEmpty(message = "content may not be empty")
private String content;
#NotEmpty(message = "category may not be empty")
private int categoryId; //int-> 0, Integer -> null
/*
#NotEmpty(message = "category may not be empty")
private Category category;
public Category getCategory() {
return category;
}
public void setCategory(Category category) {
this.category = category;
}
private List<Category> categories;
public List<Category> getCategories() {
return categories;
}
public void setCategories(List<Category> categories) {
this.categories = categories;
} */
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public Integer getCategoryId() {
return categoryId;
}
public void setCategory(Integer categoryId) {
this.categoryId = categoryId;
}
}
You set value="0" for all options.
<select> should be:
<select th:field="*{categoryId}">
<option th:each="category : ${categories}"
th:value="${category.id}"
th:utext="${category.name}"/>
<!-- <option th:each="category : *{categories}"
th:value="*{category.id}"
th:utext="*{category.name}"/> -->
</select>
Edit:
and add (setter) setCategoryId() in JokeForm class

Two classes in one Java Spring Form

is it possible, and if yes, how is it possible to create one object and put it inside other object using Java Spring Form? Because I need to create "Engine" object and put it inside "Car" object. Here are my codes of "Engine" and "Car":
public class Engine {
private float volume;
private int id;
public float getVolume() {
return volume;
}
public void setVolume(float volume) {
this.volume = volume;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
}
public class Car {
private int id;
private Engine engine;
private String model;
public Engine getEngine() {
return engine;
}
public void setEngine(Engine engine) {
this.engine = engine;
}
public String getModel() {
return model;
}
public void setModel(String model) {
this.model = model;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
}
I was using this tutorial: http://www.codejava.net/frameworks/spring/spring-mvc-form-handling-tutorial-and-example
to learn how to create form.
I created this form:
<form:form action="register" method="post" commandName="carForm">
<table border="0">
<tr>
<td>Model:</td>
<td><form:input path="model" /></td>
</tr>
<tr>
<td>Volume:</td>
<td><form:password path="volume" /></td>
</tr>
<tr>
<td colspan="2" align="center"><input type="submit" value="Register" /></td>
</tr>
</table>
</form:form>
So is it any way to create Engine object with "volume" from form and later input this "Engine" object into "Car" object? Because each example of forms which I find in Google just creates one object.
I found solution of my problem, here is my form:
<form action="/Lab05/submitAdmissionForm.html" method="post">
<p>
Pojemnosc : <input type="number" step="0.1" name="volume" />
</p>
<p>
Model : <input type="text" name="model" />
</p>
<input type="submit" value="Submit" />
</form>
And here is my Controller:
#RequestMapping(value = "/submitAdmissionForm.html", method = RequestMethod.POST)
public ModelAndView submitAdmissionForm(#RequestParam("volume") float volume,
#RequestParam("model") String model) {
ModelAndView modelView = new ModelAndView("AdmissionSuccess");
Engine engine = new Engine();
engine.setVolume(volume);
Car car = new Car();
car.setEngine(engine);
car.setModel(model);
modelView.addObject("msg", "Details submited by you: Volume: " + car.engine.getVolume() + " Model: " + car.getModel());
return modelView;
}
Based on the commentary what i understand is you want the Engine object within a Car object so you can get specifics of Engine object.
You have two options:
1) Declare a Engine object within Car object as you did:
public class Car {
private Engine engine;
// getters and setters
}
2) Use the powerful inheritance capabilities.
public class Car extends Engine {
private int id;
private String model;
// extending Engine object gives you direct access to Engine objects variables
}
Use the inheritance model that way when you create the form for Car you call the Engine variables without the use of "engine.".

Categories

Resources