I have a headache because of Thymeleaf.
I want to handle complex object in Thymeleaf like loop in loop.
See this code:
thymeleaf code
<tr th:each="wrapper:${logWrappers}">
<td th:text="${wrapper.serverName}">
<tr th:each="log:${wrapper.logs}">
<tr th:text="${log.name}">
<tr th:each="debug:${log.debug}" th:text="${debug}"></tr>
</tr>
</tr>
</td>
</tr>
model
public class LogWrapper {
private String serverName;
private List logs;
// getters and setters
}
object in model
public class LogModule {
private String name;
private String logType;
private List<String> debugs = new ArrayList<>();
private List<String> infos = new ArrayList<>();
private List<String> errors = new ArrayList<>();
// getters and setters
}
send parameter
model.addAttribute("logWrappers", logWrappers);
problem
<tr th:each="log:${wrapper.logs}">
This code is not working. How can i solve it?
retry
<tr> <td> <tr th:each="wrapper:${logWrappers}">
<td th:text="${wrapper.serverName}">
<tr th:each="log:${wrapper.logs}"> <td th:text="log.name">
<tr th:each="debug:${log.debug}"> <td th:text="${debug}"></td>
</tr>
</td>
</tr>
</td>
</tr>
</td>
</tr>
Related
Can you help me about showing all the elements in "book-data".html file?
My controller class is
#Autowired BooksService booksService
#GetMapping("/books")
private List<Books> getAllBooks() throws IOException, InterruptedException {
return booksService.getAllBooks();
}
and my service class is
#Autowired BooksRepository booksRepository;
public List<Books> getAllBooks() throws IOException, InterruptedException {
return (List<Books>) booksRepository.findAll();
}
my book repository interface is
public interface BooksRepository extends CrudRepository<Books, Integer>
{
}
and my "book-data".html file is
<table class="table">
<thead class="thead-dark">
<tr>
<th scope="col">Book ID</th>
<th scope="col">Book Name</th>
<th scope="col">Book Author</th>
<th scope="col">Book Price</th>
</tr>
</thead>
<tbody>
<tr>
<td th:text="${book.bookid}"></td>
<td th:text="${book.bookname}"></td>
<td th:text="${book.author}"></td>
<td th:text="${book.price}"></td>
</tr>
</tbody>
In HTML page, it doesn't show any book data. For service class, I saw another solution like below, but it didn't solve the problem.
#RequestMapping("/books")
private String listBooks(Model model){
model.addAtrribute("books", booksService.getAllBooks());
return "books";
}
in html:
<tbody>
<tr th:each="book : ${books}">
<td th:text="${book.bookid}"></td>
<td th:text="${book.bookname}"></td>
<td th:text="${book.author}"></td>
<td th:text="${book.price}"></td>
</tr>
</tbody>
I think something like this is maybe a solution :
in your controller with adding #Controller in class statement :
#Autowired
BooksService booksService
#GetMapping("/books")
private String listBooks(Model model){
model.addAtrribute("books", booksService.getAllBooks());
return "views/book-data";
}
your service with adding #Service in class statement :
#Autowired
BooksRepository bookRepository
public List<Books> getAllBooks(){
return this.booksRepository.findAll();
}
your repository with adding #Repository in class statement :
maybe you could had #Query
#Repository
public interface BooksRepository extends CrudRepository<Books, Long>
{
public List<Books> findAll();
}
And your html :
<table class="table">
<thead class="thead-dark">
<tr>
<th scope="col">Book ID</th>
<th scope="col">Book Name</th>
<th scope="col">Book Author</th>
<th scope="col">Book Price</th>
</tr>
</thead>
<tbody>
<tr th:each="book : ${books}">
<td th:text="${book.bookid}"></td>
<td th:text="${book.bookname}"></td>
<td th:text="${book.author}"></td>
<td th:text="${book.price}"></td>
</tr>
</tbody>
</table>
First, I created another html file that shows "all books". This file is named allBooks.html. Then, the service method below solved the problem, for your information.
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<html xmlns:th="https://thymeleaf.org">
<style>
table, th, td {
border: 1px solid black;
text-align: center;
}
</style>
<table class="table">
<thead class="thead-dark">
<tr>
<th scope="col">Book ID</th>
<th scope="col">Book Name</th>
<th scope="col">Book Author</th>
<th scope="col">Book Price</th>
</tr>
</thead>
<tbody>
<tr th:each="book : ${books}">
<td th:text="${book.bookid}"></td>
<td th:text="${book.bookname}"></td>
<td th:text="${book.author}"></td>
<td th:text="${book.price}"></td>
</tr>
</tbody>
#GetMapping("/books")
private ModelAndView getAllBooks() throws IOException, InterruptedException {
ModelAndView mav = new ModelAndView();
mav.setViewName("allBooks");
mav.addObject("books", booksService.getAllBooks());
return mav;
}
When I run this it will create a new book if valid however upon error it will not post back to the page and I get a 400:
This application has no explicit mapping for /error, so you are seeing this as a fallback.
There was an unexpected error (type=Bad Request, status=400).
Validation failed for object='book'. Error count: 4
I tested with system outs, it never gets to the submit if it has errors, I think maybe #Valid is wrong or ???
This is my controller... please tell me what's missing ?
#Controller
#RequestMapping("/")
public class BookController{
#RequestMapping(method=RequestMethod.GET)
public String home(Model model) {
refData = new ReferenceData();
model.addAttribute("refData", refData);
model.addAttribute("book", new Book());
List<Book> books = BookRepo.findAll();
model.addAttribute("books", books);
return "home";
}
#RequestMapping(method=RequestMethod.POST)
public String submit(#Valid Book book,Model model, BindingResult bindingResult) {
refData = new ReferenceData();
model.addAttribute("refData", refData);
if (bindingResult.hasErrors()) {
return "home";
}
BookRepo.save(book);
return "redirect:/";
}
My form looks like this...
<form name="addForm" action="#" th:action="#{/}" th:object="${book}" method="post">
<table>
<tr>
<td><label for="*{title}">Book:</label></td>
<td><input type="text" th:field="*{title}" /></td>
<td th:if="${#fields.hasErrors('title')}" th:errors="*{title}">Name
Error</td>
</tr>
<tr>
<td><label for="*{author}">Author:</label></td>
<td><input type="text" th:field="*{author}" /></td>
<td th:if="${#fields.hasErrors('author')}" th:errors="*{author}">Author
Error</td>
</tr>
<tr>
<td><label for="*{genre}">Genre:</label></td>
<td><input type="text" th:field="*{genre}" /></td>
<td th:if="${#fields.hasErrors('genre')}" th:errors="*{genre}">genre
Error</td>
</tr>
<tr>
<td><label for="*{pages}">Pages:</label></td>
<td><input type="text" th:field="*{pages}" /></td>
<td th:if="${#fields.hasErrors('pages')}" th:errors="*{pages}">pages
Error</td>
</tr>
<tr>
<td><label th:for="*{year}">Year:</label></td>
<td>
<select th:field="*{year}" ng-model="bookData.year">
<option th:each="selectItem: ${refData.years}"
th:value="${selectItem.value}" th:text="${selectItem.label}">year</option>
</select>
</td>
<td th:if="${#fields.hasErrors('year')}" th:errors="*{year}">year
Error</td>
</tr>
<tr>
<td><label th:for="*{rating}">Rating:</label></td>
<td><select th:field="*{rating}" ng-model="bookData.rating">
<option th:each="selectItem: ${refData.rating}"
th:value="${selectItem.value}" th:text="${selectItem.label}">rating</option>
</select></td>
<td th:if="${#fields.hasErrors('rating')}" th:errors="*{rating}">rating
Error</td>
</tr>
<tr>
<td><button type="submit">Submit</button></td>
</tr>
</table>
</form>
my entity...
#Entity
public final class Book
{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
#NotNull
#Pattern(regexp="^[a-zA-Z0-9_]+$")
private String title;
#NotNull
#Pattern(regexp="^[a-zA-Z0-9_]+$")
private String author;
#NotNull
#Pattern(regexp="^[a-zA-Z0-9_]+$")
private String genre;
#NotNull
#Digits(message="message here", integer=32767, fraction = 0)
private Integer pages;
private String year;
private String rating;
I found I had to place the bindingResult directly after the #Valid Book entity thus...
#RequestMapping(method=RequestMethod.POST)
public String submit(#Valid Book book,**BindingResult bindingResult**, Model model ) {
refData = new ReferenceData();
model.addAttribute("refData", refData);
if (bindingResult.hasErrors()) {
return "home";
}
BookRepo.save(book);
return "redirect:/";
}
Wow what a minor=major mistake !!
Johnny O.
I was trying to iterate through functionList in thymleaf.This is what I done.
Controller :
#RequestMapping(value = "/list",method = RequestMethod.GET)
public ModelAndView getFunctionList(HttpServletRequest request){
ModelAndView mav = new ModelAndView("adminConfigurationFunction");
List<Function> functionList = functionService.getList();
mav.addObject("list", functionList);
return mav;
}
and in html :
<table class="table table-striped table-condensed margin-top-20">
<tr>
<th>Name</th>
<th>Description</th>
</tr>
<tr th:each="func : ${functionList}" >
<td th:text="${func.name}"></td>
<td th:text="${func.description}"></td>
</tr>
</table>
I am new here. What am I doing wrong?Or should I use th:for?
I found the answer.
<tr th:each="func : ${list}" >
<td th:text="${func.name}"></td>
<td th:text="${func.description}"></td>
</tr>
changed th:each="func : ${functionList}" th:each="func : ${list}"`
I have the following code below. Thymeleaf is unable to resolve "orderDetails" despite the field being existent/not null when i debug through the internals of Thymeleaf.
Exception= Field or property 'orderDetails' cannot be found on object of type
<div th:each="order : ${orders}">
<table>
<tr>
<th>CUSTOMER</th>
<th>PRICE</th>
<th>TIME ORDER PLACED</th>
<th>ITEMS</th>
</tr>
<tr>
<td th:text="${order.customerAccount.email}">email</td>
<td th:text="${order.baseCost}">2.50</td>
<td th:text="${order.tip}">2.00</td>
<td th:text="${order.orderDetails}">2.00</td>
<!-- <td th:text="${#lists.size(order.orderDetails)}">1</td> -->
</tr>
</table>
<table>
<tr>
<th>DRINK NAME</th>
<th>AMOUNT</th>
<th>QUANTITY</th>
<th>COST</th>
</tr>
<tr th:each="orderDetail : ${order.orderDetails}">
<td th:text="${orderDetail.barStock.drink.name}">Test Drink Name</td>
<td th:text="${orderDetail.barStock.amount}">10oz</td>
<td th:text="${orderDetail.quantity}">2</td>
<td th:text="${orderDetail.barStock.cost * orderDetail.quantity}">2.00</td>
</tr>
</table>
Here is the field in question of the "order" field/class.
#OneToMany(fetch = FetchType.EAGER, mappedBy = "barOrder")
#JsonProperty
private Set<OrderDetail> orderDetails;
A public getter method for orderDetails field is needed to allow Thymeleaf to access it.
public Set<OrderDetail> getOrderDetails() {
return orderDetails;
}
In MVC3, i created one register form. I created one model,,controller and view. This is my code:
[HttpGet]
public ActionResult Insert()
{
Models.Employees objEmpl = new Models.Employees();
return View(objEmpl);
}
[HttpPost]
[AcceptVerbs("Post")]
public ActionResult Insert(Models.Employees objs)
{
var v = new Models.test().InsertDl(objs);
return View();
}
The above is my controller
#model MvcVideo.Models.Employees
#{
ViewBag.Title = "Insert";
Layout = "~/Views/Shared/VideoLayout.cshtml";
}
<h2>Insert</h2>
#using(Html.BeginForm("Insert","Home",FormMethod.Post ))
{
<table>
<tr>
<td>
Employee Name
</td>
<td>
#Html.TextBox("Ename",#Model.Enames)
</td>
</tr>
<tr>
<td>
Department Id
</td>
<td>
#Html.TextBox("Departmentid",#Model.DepartId )
</td>
</tr>
<tr>
<td>
Email Id
</td>
<td>
#Html.TextBox("Emailid",#Model.EmailIds)
</td>
</tr>
<tr>
<td>
Address
</td>
<td>
#Html.TextBox("Address",#Model.Adress)
</td>
</tr>
<tr>
<td colspan="2" style="text-align:center;" >
<button title ="Ok" value="OK"></button>
</td>
</tr>
</table>
}
But the objs object at public actionresult Insert(models.Empoyees objs) action method parameter is showing null values. Imean Ename=Null, Department=0, Emailid=Null and Address=null
It isn't working because the names you've provided in your Html helpers don't match up with the property names on your model, so the default model binder can't resolve them when the values get posted back.
Using Html.TextBoxFor instead of Html.TextBox will provide you with strong typing against your model, and is the safer approach.
Replace this
#Html.TextBox("Ename",#Model.Enames)
With
#Html.TextBoxFor(model => model.Enames)
This will fix your issue.