Search code examples
spring-bootspring-mvcspring-securitythymeleaf

Thymeleaf - Constructing URL based off form data


I'm currently writing an E-Commerce web application and I am working on the add to cart feature.

When you add to cart, it creates a post request that will submit data to the user's cart. I want to know if there's a better way of writing this (how to put the form data in the URL).

When someone clicks add to cart, it should send a post request based off the currently logged in user (w/ spring security) in this type of url: /cart/{productId}/{quantity} - then it will be added to the user's cart.

Thymeleaf Code:

    <div th:each="product : ${popularProducts}">
<form action="#" th:action="@{/cart/} + ${product.getId() + @{/} + ${quantity}" th:object="${product}" method="POST">
  <div class="card">
    <img class="card-img" th:src="${product.getPictureUrl()}">
      <a href="#" class="card-link text-danger like">
        <i class="fas fa-heart"></i>
      </a>
    </div>
    <div class="card-body">
      <h4 class="card-title"  th:text="${product.getName()}"></h4>
      <h6 class="card-subtitle mb-2 text-muted"  th:text="${product.getCategory()}"></h6>
      <p class="card-text" th:text="${product.getDescription()}">
      <div class="buy d-flex justify-content-between align-items-center">
        <div class="price text-success"><h5 class="mt-4" th:text="'$' + ${product.getPrice()}"></h5></div>
        <div class="form-group blu-margin">
         <div class="form-label-group">
              <input th:field="*{quantity}" type="text" id="quantity" name="quantity" class="form-control" placeholder="1" required="required">
           </div>
        </div>
         <a href="#" class="btn btn-danger mt-3"><i class="fas fa-shopping-cart"></i> Add to Cart</a>
      </div>
    </div>
    </form>

CartController:

@PostMapping("/cart/{productId}/{quantity}")
public void addProduct(@PathVariable String productId, @PathVariable String quantity, Model model) {
    Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
    User user = ((User) principal);

    if (productService.findById(Long.parseLong(productId)) != null) {
        Optional<Product> product = productService.findById(Long.parseLong(productId));
        int quantityAmount = Integer.parseInt(quantity);
        user.getCart().getCartItems().add(new CartItems(product.get(), quantityAmount,
                product.get().getPrice() * quantityAmount, user.getCart()));
    }

    List<CartItems> itemsInCart = user.getCart().getCartItems();
    int numItems = itemsInCart.size() + 1;

    model.addAttribute("amountOfItems", numItems);

}

Product Class:

@Table(name = "products")
public class Product {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "product_id")
private Long id;

@Column(name = "name")
private String name;

@Column(name = "category")
private String category;

@Column(name = "description")
private String description;

@Column(name = "price")
private Double price;

@Column(name = "stock")
private int stock;

@Column(name = "picture_url")
private String pictureUrl;

public Product() {

}

public Product(String name, String category, String description, Double price, int stock, String pictureUrl) {
    this.name = name;
    this.category = category;
    this.description = description;
    this.price = price;
    this.stock = stock;
    this.pictureUrl = pictureUrl;
}
// and getters/setters...

CartItems Class:

@Table(name = "cartitems")
public class CartItems implements Serializable {

private static final long serialVersionUID = -3426230490106082532L;

@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private int id;

@Column(name = "quantity")
private int quantity;

@Column(name = "price")
private double price;

@OneToOne
@JoinColumn(name = "product_id")
private Product product;

@ManyToOne
@JoinColumn(name = "cart_id")
private Cart cart;

public CartItems() {

}

public CartItems(Product product, int quantity, double price, Cart cart) {
    this.quantity = quantity;
    this.price = price;
    this.product = product;
    this.cart = cart;
}
// all getters and setters..

Solution

  • Thymeleaf URLs using Form Data

    how to put the form data in the URL?

    You can't construct a Thymeleaf URL using data entered by a user in a web form. Thymeleaf is a server-side HTML pre-processor. It uses data already available on the server to "fill in the blanks" in a Thymeleaf template. The resulting HTML is then sent from the server to the browser. There is no Thymeleaf in the browser, at that point.

    Using POST Data

    You are right to use an http POST operation for your form data. This means all the user-provided data will be sent to the server in the body of the request, as I'm sure you already know.

    Generally, it's not a good idea to try to add such data to the URL. You can do this, using client-side JavaScript - but it's a bad idea for various reasons - not least being this: URLs can be bookmarked, saved, and easily re-played. Form data typically represents a transaction. You don't want to re-submit most transactions in this way (duplicate orders, etc.). Some background reading is here.

    So, use a more generic URL, and then extract the form data from the body of the request. The data in the body is, basically, the same as the data you are trying to add to the URL, anyway.

    Thymeleaf URLs - In General

    A final note: Just for future reference, and not directly relevant to this specific URL, for reasons already mentioned above...

    Instead of using this:

    "@{/cart/} + ${product.getId() + @{/} + ${quantity}"
    

    ...the link should be created like this:

    "@{/cart/{id}/{qty}(id=${product.id},qty=${quantity})}"
    

    In this example, {id} is a placeholder (call it whatever you want) which receives its value from the parameter in parentheses ():

    id=${product.id}
    

    And the same points apply to {qty}.

    Also, you shouldn't need to use the getters such as getId() directly. Use the field name id. Thymeleaf will handle calling the relevant getter (assuming it is defined correctly).

    See the documentation for Thymeleaf link urls.