Spring Boot+Thymeleaf: th not able to resolve a Spring EL expression

Amritanshu Amrit

I am using spring boot+thymeleaf+neo4j. Everything is working fine except that thymeleaf is not able to resolve a few of the attributes of the 'product' variable used in the th:each block in the template product_grid.html, which includes th:src="${product.URL}", th:text="${Product.title}" and the th:action="@{/product/(${Product.getId()})}" expression in form tag. The th:text="${Product.Price}" is working. When I check the code produced in the browser the src tag is empty (src:""), the text attribute containing the title tag is not shown in the browser. The th:action works fine but when I click the button defined inside the form, the url changes to http://localhost:8080/product/?btn=View+Product instead of the following code which is shown in the browser console http://localhost:8080/product/?1

Note: I am trying to get the image url from a field which is stored in neo4j database. The project directory is: project directory image

Template:product_grid.html

<html xmlns:th="http://www.thymeleaf.org" >
<head>
    <meta charset="utf-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title>Products</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.bundle.min.js" integrity="sha384-feJI7QwhOS+hwpX2zkaeJQjeiwlhOP+SdQDqhgvvo1DsjtiSQByFdThsxO669S2D"
            crossorigin="anonymous"></script>
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js" integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl" crossorigin="anonymous"></script>
    <link href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm"
          crossorigin="anonymous">
</head>

<body>


<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
    <a class="navbar-brand" href="#">Grada</a>
    <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent"
            aria-expanded="false" aria-label="Toggle navigation">
        <span class="navbar-toggler-icon"></span>
    </button>

    <div class="collapse navbar-collapse" id="navbarSupportedContent">
        <ul class="navbar-nav mr-auto">
            <li class="nav-item ">
                <a class="nav-link" href="#">Home
                    <span class="sr-only">(current)</span>
                </a>
            </li>
            <li class="nav-item">
                <a class="nav-link">My Best Products</a>
            </li>

            <li class="nav-item">
                <a class="nav-link" th:href="@{/login}">Login</a>
            </li>
        </ul>
        <form class="form-inline my-2 my-lg-0">
            <input class="form-control mr-sm-2" type="search" placeholder="Search" aria-label="Search">
            <a class="btn btn-outline-success my-2 my-sm-0" href="file:///home/madhav/SPM/Grada/public_html/product.html">Search</a>
        </form>
    </div>
</nav>
<div class="container text-center">
    <div class="row">
        <div th:each="Product:${products}" class="col-lg-3 col-sm-12 col-md-6 my-2 p-auto">
            <div class="card">
                <div class="card-body">
                    <img src="http://via.placeholder.com/150x150/888/111" th:src="${Product.URL}" alt="img" class="card-img-top img-thumbnail img-fluid">
                    <div class="card-title lead" th:text="${Product.title}">Some product name</div>
                    <div class="card-text">Price: &#8377;<span th:text="${Product.Price}">400</span></div>
                </div>
                <form method="GET" action="/" th:action="@{/product/(${Product.getId()})}">
                    <input type="submit" name="btn" class="form-control btn btn-primary" value="View Product">
                    <input type="submit" name="btn" class="form-control btn btn-primary" value="Add to Cart">
                </form>
            </div>
        </div>
    </div>
</div>

</body>
</html>`

Product model:Product.html

package com.grada.ecommerce.Models;


import com.grada.ecommerce.Models.Seller;
import org.neo4j.ogm.annotation.*;


import java.util.HashSet;
import java.util.Set;


@NodeEntity(label = "Product")
public class Product
{
    public  Product()
    {
    }

    public Product(String title, Double price, int quantity, float rating, String description, String url, String company)
    {
        this.title  = title;
        this.Rating = rating;
        this.Description = description;
        this.Price = price;
        this.Quantity = quantity;
        this.URL = url;
        this.Company = company;
    }

    @Id
    @GeneratedValue
    private Long id;

    @Property(name = "title")
    public String title;
    
    @Property(name = "Rating")
    public float Rating;
    @Property(name = "Description")
    public String Description;
    @Property(name = "Price")
    public Double Price;
    @Property(name = "Quantity")
    public int Quantity;
    @Property(name = "Company")
    public String Company;
    @Property(name = "URL")
    public String URL;


    @Override
    public String toString()
    {
        return this.title;
    }

    public Long getId() {
        return id;
    }

    public String getTitle()
    {
        return title;
    }

   @Relationship(type = "Sells", direction = Relationship.INCOMING)
   public Seller Seller;

}
ProductController.java

package com.grada.ecommerce.Controllers;

import com.grada.ecommerce.Models.Product;
import com.grada.ecommerce.Models.Seller;
import com.grada.ecommerce.Services.ProductService;
import com.grada.ecommerce.Services.SellerService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@Controller
public class ProductController
{

    final ProductService productService;
    final SellerService sellerService;

    @Autowired
    public ProductController(ProductService productService, SellerService sellerService)
    {
        this.productService = productService;
        this.sellerService = sellerService;
    }

    @RequestMapping(value = "/", method = RequestMethod.GET)
    public String Index(Model model)
    {
        Iterable<Product> products  = productService.products();
        model.addAttribute("products", products);
        return "product_grid";
    }

    @RequestMapping(value = "/product", method = RequestMethod.GET)
    public String ShowProduct(@RequestParam(value = "id") Long id, Model model)
    {
        Product product = productService.findProductByID(id);
        if(product == null)
            return "redirect:/";
        model.addAttribute("product", product);
        return "productid";
    }

    @RequestMapping(value = "/add")
    public String AddProduct(Model model)
    {
        model.addAttribute("product", new Product());
        return "add";
    }

    @RequestMapping(value = "/add", method = RequestMethod.POST)
    public String AddProduct(@ModelAttribute Product product)
    {
        productService.addProduct(product);
        return "redirect:/";
    }

    @RequestMapping(value = "/delete", method = RequestMethod.GET)
    public String DeleteProduct(Model model)
    {
        model.addAttribute("product", new Product());
        return "delete";
    }

    @RequestMapping(value = "/delete", method = RequestMethod.POST)
    public String DeleteProduct(@ModelAttribute Product product)
    {
        productService.deleteProduct(product);
        return "redirect:/";
    }

    @RequestMapping(value = "/login", method = RequestMethod.GET)
    public String LoginPage(Model model)
    {
        return "login";
    }

    @RequestMapping(value = "/login", method = RequestMethod.POST)
    public String Authenticate(Model model, String username, String password)
    {
        if (username.equalsIgnoreCase("[email protected]") && password.equals("fakeseller"))
        {
            Iterable<Product> products  = productService.products();
            model.addAttribute("products", products);
            return "loggedin";
        }

        else
            return "redirect:/login";
    }

    @RequestMapping(value = "/policy", method = RequestMethod.GET)
    public String PolicyPage()
    {
        return "policies";
    }
}

vphilipnyc

Welcome to SO.

Include setX methods for your variables in the Product class. Thymeleaf needs these to bind.

IMO, a great way to do this is to use Project Lombok and simply annotate your class with @Data. Then you won't even need to specify getters or setters (or your toString()) at all.

Use lower-case for your variables since by convention variables with a capital first letter refers to the class, not an instance variable.

As mentioned, use POST instead of GET in your form since you are submitting data.

Use the shorthand @GetMapping and @PostMapping to make it easier to read.

Collected from the Internet

Please contact [email protected] to delete if infringement.

edited at
0

Comments

0 comments
Login to comment

Related