0

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("seller@fake.com") && 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";
    }
}
  • Have you tried changing the case to lower-case? Or better yet, instead of `Product` call it `oneProduct` or `item` so that the variable is unique and more easily read. – riddle_me_this Apr 15 '18 at 22:09
  • Also, any reason you're using `method="GET"` instead of `POST`? – riddle_me_this Apr 15 '18 at 22:12
  • Also, your UI is going to look strange - you'll be creating a new column for every product if you look closely. – riddle_me_this Apr 15 '18 at 22:17
  • Ok, I will change it to lower-case. No specific reason for using 'GET', but should that matter? And about the UI thing, I was just trying to print out the information of every product I have in the DB to a page. – Amritanshu Amrit Apr 16 '18 at 22:58
  • Yes, using GET vs POST matters. If you're submitting data, you'll want to use POST: https://stackoverflow.com/questions/504947/when-should-i-use-get-or-post-method-whats-the-difference-between-them – riddle_me_this Apr 17 '18 at 02:12
  • You can also use the shorthand `@GetMapping` and `@PostMapping` in your controller. And print the value of `productService.products();` to make sure you're adding products ok. – riddle_me_this Apr 17 '18 at 02:15
  • Does your `Product` class have setters for the values you're missing? If that's the issue, I'll post an answer – riddle_me_this Apr 17 '18 at 02:19

1 Answers1

0

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.

riddle_me_this
  • 8,575
  • 10
  • 55
  • 80
  • When I changed the method from GET to POST, it worked! Thanks! But according to the link you gave, GET is used for retrieving data,i.e. querying, isn't it? POST will be used when I would want to change some data. Here, I am just sending the product is and retrieving the product details. So why does using GET give the particular problem that I get? – Amritanshu Amrit Apr 17 '18 at 05:05
  • It would be too long to discuss this in a comment, but in your case, your View Product could be replaced with a simple anchor tag. There's no change in the system's state and I don't see the value of even making a form out of it. If you're adding to cart, there are lots of things that typically happen behind the scenes, you'll want it to be secure, etc. You've got a lot of other issues going on too, so not really worth examining it until you've got them sorted out. A main reason is what method you'd be actually calling depending on GET vs. POST. – riddle_me_this Apr 17 '18 at 18:02