Showing posts with label JCart. Show all posts
Showing posts with label JCart. Show all posts

Tuesday, February 2, 2016

JCart : Iteration-8 - Customer MyAccount Page

In this Iteration#8 we will implement showing the Customer Account and Order History functionality in our ShoppingCart application.
  • Customer MyAccount Page
    • Profile
    • Order History
Once the customer is logged in our system he can click on MyAccount link at the top of the header and view his profile details and order history.

First let us write the Controller handler method in our CustomerController to show myAccount details.

@Controller
public class CustomerController extends JCartSiteBaseController
{ 
 @Autowired private CustomerService customerService;
 ...
 ... 
 
 @RequestMapping(value="/myAccount", method=RequestMethod.GET)
 protected String myAccount(Model model)
 {
  String email = getCurrentUser().getCustomer().getEmail();
  Customer customer = customerService.getCustomerByEmail(email);
  model.addAttribute("customer", customer);
  List<Order> orders = customerService.getCustomerOrders(email);
  model.addAttribute("orders", orders);
  return "myAccount";
 }
}

Now create the myAccount.html view to render customer details and customer order history.

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
 xmlns:th="http://www.thymeleaf.org"
 xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3"
 layout:decorator="layout/mainLayout">
<head>
<title>My Account</title>
</head>
<body>
 <div layout:fragment="content">
  <div class="single-product-area">
   <div class="zigzag-bottom"></div>
   <div class="container">
    <div role="tabpanel">
     <ul class="customer-tab" role="tablist">
      <li role="presentation" class="active"><a href="#profile"
       aria-controls="profile" role="tab" data-toggle="tab">Customer
        Info</a></li>
      <li role="presentation"><a href="#orders"
       aria-controls="orders" role="tab" data-toggle="tab">Orders</a></li>
     </ul>
     <div class="tab-content">
      <div role="tabpanel" class="tab-pane fade in active" id="profile">
       <h2>Customer Info</h2>
       <form role="form" action="#" th:object="${customer}"
        method="post">        
        <div class="form-group">
         <label>FirstName</label> <input type="text"
          class="form-control" th:field="*{firstName}"
          readonly="readonly" />
        </div>
        <div class="form-group">
         <label>LastName</label> <input type="text" class="form-control"
          th:field="*{lastName}" readonly="readonly" />
        </div>
        <div class="form-group">
         <label>Email</label> <input type="email" class="form-control"
          th:field="*{email}" readonly="readonly" />
        </div>
        <div class="form-group">
         <label>Phone</label> <input type="text" class="form-control"
          th:field="*{phone}" readonly="readonly" />
        </div>
       </form>
      </div>
      <div role="tabpanel" class="tab-pane fade" id="orders">
       <h2>Orders</h2>
       <table cellspacing="0" class="shop_table cart">
        <thead>
         <tr>
          <th>#</th>
          <th>Order Number</th>
          <th>Status</th>
         </tr>
        </thead>
        <tbody>
         <tr th:each="order,iterStat : ${orders}">
          <td><span th:text="${iterStat.count}">1</span></td>
          <td><a href="#" th:text="${order.orderNumber}"
           th:href="@{/orders/{orderNumber}(orderNumber=${order.orderNumber})}">OrderNumber</a>
          </td>
          <td><span th:text="${order.status}">Status</span></td>

         </tr>
        </tbody>
       </table>
      </div>
     </div>
    </div>
   </div>
  </div>
 </div>
</body>
</html>

Now you can login as customer and click on MyAccount and see the profile. When you click on Orders tab you can see the list of orders that customer is placed. Also you can click on Order Number to see more details of the Order.

JCart : Manage Customers

For Managing Customers we need a provision to see all the list of customers and view any Customers details.

Let us start with implementing the back-end Customer service.

public interface CustomerRepository extends JpaRepository<Customer, Integer>
{

 Customer findByEmail(String email);

 @Query("select o from Order o where o.customer.email=?1")
 List<Order> getCustomerOrders(String email);

}

@Service
@Transactional
public class CustomerService {
 @Autowired CustomerRepository customerRepository;
 
 public Customer getCustomerByEmail(String email) {
  return customerRepository.findByEmail(email);
 }

 public Customer createCustomer(Customer customer) {
  return customerRepository.save(customer);
 }

 public List<Customer> getAllCustomers() {
  return customerRepository.findAll();
 }

 public Customer getCustomerById(Integer id) {
  return customerRepository.findOne(id);
 }

 public List<Order> getCustomerOrders(String email) {
  return customerRepository.getCustomerOrders(email);
 }
}

Now let us implement CustomerController to handle the requests to display list of customers and the selected customer details.

@Controller
@Secured(SecurityUtil.MANAGE_CUSTOMERS)
public class CustomerController extends JCartAdminBaseController
{
 private static final String viewPrefix = "customers/";
 
 @Autowired 
 private CustomerService customerService;
 
 @Override
 protected String getHeaderTitle()
 {
  return "Manage Customers";
 }
  
 @RequestMapping(value="/customers", method=RequestMethod.GET)
 public String listCustomers(Model model) {
  List<Customer> list = customerService.getAllCustomers();
  model.addAttribute("customers",list);
  return viewPrefix+"customers";
 }
 
 @RequestMapping(value="/customers/{id}", method=RequestMethod.GET)
 public String viewCustomerForm(@PathVariable Integer id, Model model) {
  Customer customer = customerService.getCustomerById(id);
  model.addAttribute("customer",customer);
  return viewPrefix+"view_customer";
 }  
}

Create the thymeleaf view template for showing list of customers customers.html as follows:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" 
  xmlns:th="http://www.thymeleaf.org"
   xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3"
      layout:decorator="layout/mainLayout">      
<head>
 <title>Customers</title>
</head>
<body>             
 <div layout:fragment="content">
  <div class="row">
  <div class="col-md-12">
    <div class="box">
   <div class="box-header">
     <h3 class="box-title">List of Customers</h3>
   </div>
   <div class="box-body table-responsive no-padding">
     <table class="table table-hover">
    <tr>
      <th style="width: 10px">#</th>
      <th>Customer Name</th>
      <th>Email</th>
      <th>View</th>                      
    </tr>
    <tr th:each="customer,iterStat : ${customers}">
      <td><span th:text="${iterStat.count}">1</span></td>
      <td th:text="${customer.firstName}">Customer Name</td>
      <td th:text="${customer.email}">Customer Email</td>
      <td><a th:href="@{/customers/{id}(id=${customer.id})}" 
      class="btn btn-sm btn-default"><i class="fa fa-search"></i> View</a></td>
    </tr>                    
     </table>
   </div>                
    </div>
  </div>
  </div>    
 </div>     
</body>    
</html>

Create the thymeleaf view template for showing customer details view_customer.html as follows:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
 xmlns:th="http://www.thymeleaf.org"
 xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3"
 layout:decorator="layout/mainLayout">

<head>
<title>Customer - View</title>
</head>
<body>
 <div layout:fragment="content">
  <div class="box box-warning">
   <div class="box-header with-border">
    <h3 class="box-title">View Customer</h3>
   </div>
   <div class="box-body">
    <form role="form" action="#" th:object="${customer}" method="post">
     <div class="form-group">
      <label>FirstName</label> <input type="text" class="form-control"
       th:field="*{firstName}" readonly="readonly" />
     </div>

     <div class="form-group">
      <label>LastName</label> <input type="text" class="form-control"
       th:field="*{lastName}" readonly="readonly" />
     </div>

     <div class="form-group">
      <label>Email</label> <input type="email" class="form-control"
       th:field="*{email}" readonly="readonly" />
     </div>

     <div class="form-group">
      <label>Phone</label> <input type="text" class="form-control"
       th:field="*{phone}" readonly="readonly" />
     </div>
    </form>
   </div>
  </div>
 </div>

</body>
</html>

Now you can run the application and click on Customers menu item in left navigation. You can see list of customers and click on View button to view customer details.

JCart : Manage Orders

For Managing Orders we need a provision to see all the list of orders and view an order details and updating the order status.

Let us start with implementing the back-end order service.

@Service
@Transactional
public class OrderService
{
 @Autowired EmailService emailService;
 @Autowired OrderRepository orderRepository;
 
 ...
 ...
 
 public Order getOrder(String orderNumber)
 {
  return orderRepository.findByOrderNumber(orderNumber);
 }

 public List<Order> getAllOrders()
 {
  Sort sort = new Sort(Direction.DESC, "createdOn");
  return orderRepository.findAll(sort);
 }

 public Order updateOrder(Order order) {
  Order o = getOrder(order.getOrderNumber());
  o.setStatus(order.getStatus());
  Order savedOrder = orderRepository.save(o);
  return savedOrder;
 }
}

Now let us implement OrderController to handle the requests to display list of orders, the selected order details and updating the Order status.

@Controller
@Secured(SecurityUtil.MANAGE_ORDERS)
public class OrderController extends JCartAdminBaseController
{
 private static final String viewPrefix = "orders/";

 @Autowired protected EmailService emailService;
 @Autowired protected OrderService orderService;
 @Autowired private TemplateEngine templateEngine;
 
 @Override
 protected String getHeaderTitle()
 {
  return "Manage Orders";
 }
 
 
 @RequestMapping(value="/orders", method=RequestMethod.GET)
 public String listOrders(Model model) {
  List<Order> list = orderService.getAllOrders();
  model.addAttribute("orders",list);
  return viewPrefix+"orders";
 }
 
 @RequestMapping(value="/orders/{orderNumber}", method=RequestMethod.GET)
 public String editOrderForm(@PathVariable String orderNumber, Model model) {
  Order order = orderService.getOrder(orderNumber);
  model.addAttribute("order",order);
  return viewPrefix+"edit_order";
 }
 
 @RequestMapping(value="/orders/{orderNumber}", method=RequestMethod.POST)
 public String updateOrder(@ModelAttribute("order") Order order, BindingResult result, 
   Model model, RedirectAttributes redirectAttributes) {  
  Order persistedOrder = orderService.updateOrder(order);
  this.sendOrderStatusUpdateEmail(persistedOrder);
  logger.debug("Updated order with orderNumber : {}", persistedOrder.getOrderNumber());
  redirectAttributes.addFlashAttribute("info", "Order updated successfully");
  return "redirect:/orders";
 }
 
 protected void sendOrderStatusUpdateEmail(Order order)
 {
  try {
   final Context ctx = new Context();
         ctx.setVariable("order", order);
   final String htmlContent = this.templateEngine.process("order-status-update-email", ctx);
         
   emailService.sendEmail(order.getCustomer().getEmail(), 
           "QuilCartCart - Order Status Update", 
           htmlContent);
  } catch (JCartException e) {
   logger.error(e);
  }
 }
}

We are using thymeleaf email template jcart-core/src/main/resources/email-templates/order-status-update-email.html for sending the Order Status Update email.

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
  <head>
    <title th:remove="all">Template for HTML email</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
  </head>
  <body>
    <p th:text="${'Hello '+order.customer.firstName}">
      Hello Customer
    </p>
    <p>
       Order Number : <span th:text="${order.orderNumber}">Number</span><br/>
       Status: <span th:text="${order.status}">Status</span>
    </p>
    <p>
      Regards, <br />
      <em>The QuilCart Team</em>
    </p>
  </body>
</html>
Create the thymeleaf view template for showing list of orders orders.html as follows:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" 
   xmlns:th="http://www.thymeleaf.org"
   xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3"
      layout:decorator="layout/mainLayout">      
 <head>
 <title>Orders</title>
</head>
<body>   
 <div layout:fragment="content">
  <div class="row">
  <div class="col-md-12">
    <div class="box">
   <div class="box-header">
     <h3 class="box-title">List of Orders</h3>
   </div>
   <div class="box-body table-responsive no-padding">
     <table class="table table-hover">
    <tr>
      <th style="width: 10px">#</th>
      <th>Order Number</th>
      <th>Customer Name</th>
      <th>Email</th>
      <th>Edit</th>      
    </tr>
    <tr th:each="order,iterStat : ${orders}">
      <td><span th:text="${iterStat.count}">1</span></td>
      <td th:text="${order.orderNumber}">Order Number</td>
      <td th:text="${order.customer.firstName}">Customer Name</td>
      <td th:text="${order.customer.email}">Customer Email</td>
      <td><a th:href="@{/orders/{orderNumber}(orderNumber=${order.orderNumber})}" 
       class="btn btn-sm btn-default"><i class="fa fa-edit"></i> Edit</a></td>
    </tr>                    
     </table>
   </div>   
    </div>
   </div>
 </div>    
 </div> 
</body>    
</html>

Create the thymeleaf view template for showing order details edit_order.html as follows:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
 xmlns:th="http://www.thymeleaf.org"
 xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3"
 layout:decorator="layout/mainLayout">
<head>
<title>Orders - Edit</title>
</head>
<body>
 <div layout:fragment="content">
  <div class="box box-warning">
   <div class="box-header with-border">
    <h3 class="box-title">Edit Order</h3>
   </div>
   <!-- /.box-header -->
   <div class="box-body">
    <form role="form"
     th:action="@{/orders/{orderNumber}(orderNumber=${order.orderNumber})}"
     th:object="${order}" method="post">
     <p th:if="${#fields.hasErrors('global')}" th:errors="*{global}"
      th:class="text-red">Incorrect data</p>
     <div>
      <div th:unless="${order}">
       <h2>No order found</h2>
      </div>
      <div th:if="${order}">
       <h3>
        Order Number : <span th:text="${order.orderNumber}">Number</span>
       </h3>
       <h3>Order Item Details</h3>
       <table class="table table-hover">
        <thead>
         <tr>
          <th>Name</th>
          <th>Quantity</th>
          <th>Cost</th>
         </tr>
        </thead>
        <tbody>
         <tr th:each="item : ${order.items}">
          <td th:text="${item.product.name}">product.name</td>
          <td th:text="${item.quantity}"></td>
          <td th:text="${item.price * item.quantity}">price</td>
         </tr>
        </tbody>
        <tfoot>

         <tr class="cart-subtotal">
          <th>Order Subtotal</th>
          <td><span class="amount" th:text="${order.totalAmount}">£15.00</span>
          </td>
         </tr>

         <tr class="shipping">
          <th>Shipping and Handling</th>
          <td>Free Shipping</td>
         </tr>

         <tr class="order-total">
          <th>Order Total</th>
          <td><strong><span class="amount"
            th:text="${order.totalAmount}">£15.00</span></strong></td>
         </tr>

        </tfoot>
       </table>
       <div>
        <label>Order Status</label> <select th:field="*{status}">
         <option
          th:each="status: ${T(com.sivalabs.jcart.entities.OrderStatus).values()}"
          th:value="${status}" th:text="${status}">Status</option>
        </select>
       </div>
      </div>
     </div>
     <div class="box-footer">
      <button type="submit" class="btn btn-primary">Submit</button>
     </div>
    </form>
   </div>
  </div>
 </div>
</body>
</html>

Now you can run the application and click on Order menu item in left navigation. You can see list of order and click on Edit button to view order details or edit the order status.

JCart : Iteration-7

In Iteration#6 we have implemented features in ShoppingCart application to enable Customers place orders.

In this Iteration#7 we will implement the features in Administration application to view and manage the Customers and Orders.

As part of Iteration#7 we will implement the following usecases:

JCart : Billing and Delivery Page

Once the customer reviewed his cart items details and clicks on Checkout we should display Billing & Delivery page where customer enters delivery address details, payment details etc and place the order.

Let us create a OrderDTO.java as follows:

public class OrderDTO implements Serializable
{
 private static final long serialVersionUID = 1L;

 @NotEmpty(message="FirstName is required")
 private String firstName;
 @NotEmpty(message="LastName is required")
 private String lastName;
 @NotEmpty(message="EmailId is required")
 @Email
 private String emailId;
 @NotEmpty(message="Phone is required")
 private String phone;
 
 @NotEmpty(message="Address Line1 is required")
 private String addressLine1;
 private String addressLine2;
 @NotEmpty(message="City is required")
 private String city;
 @NotEmpty(message="State is required")
 private String state;
 @NotEmpty(message="ZipCode is required")
 private String zipCode;
 @NotEmpty(message="Country is required")
 private String country;
 
 @NotEmpty(message="FirstName is required")
 private String billingFirstName;
 @NotEmpty(message="LastName is required")
 private String billingLastName;
 @NotEmpty(message="Address Line1 is required")
 private String billingAddressLine1;
 private String billingAddressLine2;
 @NotEmpty(message="City is required")
 private String billingCity;
 @NotEmpty(message="State is required")
 private String billingState;
 @NotEmpty(message="ZipCode is required")
 private String billingZipCode;
 @NotEmpty(message="Country is required")
 private String billingCountry;
 
 @NotEmpty(message="Credit Card Number is required")
 private String ccNumber;
 @NotEmpty(message="CVV is required")
 private String cvv;
 
 //setters & getters
}

Create CheckoutController to display the Billing & Delivery page as follows:

@Controller
public class CheckoutController extends JCartSiteBaseController
{

 @Override
 protected String getHeaderTitle()
 {
  return "Checkout";
 }

 @RequestMapping(value="/checkout", method=RequestMethod.GET)
 public String checkout(HttpServletRequest request, Model model)
 {
  OrderDTO order = new OrderDTO();
  model.addAttribute("order", order);
  Cart cart = getOrCreateCart(request);
  model.addAttribute("cart", cart);
  return "checkout";
 }
}

Finally create the checkout.html view as follows:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" 
   xmlns:th="http://www.thymeleaf.org"
   xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3"
      layout:decorator="layout/mainLayout">
      
<head>
<title>Cart</title>
</head>
<body>
<div layout:fragment="content">
<div class="single-product-area">
<div class="zigzag-bottom"></div>
<div class="container">
<div class="row">
     
 <div class="woocommerce-info col-md-offset-2 col-md-8" th:if="${#lists.isEmpty(cart.items)}">
   <h2>Cart is Empty</h2>
 </div>
  <div class="col-md-offset-2 col-md-8" th:unless="${#lists.isEmpty(cart.items)}">
  <div class="product-content-right">
   <div class="woocommerce">
          
     <h3 id="order_review_heading">Your order</h3>

     <div id="order_review" style="position: relative;">
      <table class="shop_table">
       <thead>
        <tr>
         <th class="product-name">Product</th>
         <th class="product-total">Total</th>
        </tr>
       </thead>
       <tbody>
        <tr class="cart_item" th:each="item : ${cart.items}">
         <td class="product-name" >
          <span th:text="${item.product.name}" >Product Name </span> 
          <strong class="product-quantity" th:text="'× '+${item.quantity}">× 1</strong> </td>
         <td class="product-total">
          <span class="amount" th:text="${item.product.price * item.quantity}">£15.00</span> </td>
        </tr>
       </tbody>
       <tfoot>
        <tr class="cart-subtotal">
         <th>Cart Subtotal</th>
         <td><span class="amount" th:text="${cart.totalAmount}">£15.00</span>
         </td>
        </tr>
        <tr class="shipping">
         <th>Shipping and Handling</th>
         <td>

          Free Shipping
          <input type="hidden" class="shipping_method" value="free_shipping" id="shipping_method_0" data-index="0" name="shipping_method[0]"/>
         </td>
        </tr>

        <tr class="order-total">
         <th>Order Total</th>
         <td><strong><span class="amount" th:text="${cart.totalAmount}">£15.00</span></strong> </td>
        </tr>
       </tfoot>
      </table>
     </div>
     
    <form action="#" th:action="@{/orders}" class="checkout" method="post" th:object="${order}">

     <div id="customer_details" class="col2-set">
      <div class="col-1">
       <div class="woocommerce-billing-fields">
        <h3>Billing Details</h3>

        <p id="billing_first_name_field" class="form-row form-row-first validate-required">
         <label class="" for="billing_first_name">First Name <abbr title="required" class="required">*</abbr>
         </label>
         <input type="text" value="" placeholder="" th:field="*{billingFirstName}" class="input-text "/>
         <p th:if="${#fields.hasErrors('billingFirstName')}" th:errors="*{billingFirstName}" th:errorclass="text-danger">Incorrect firstName</p>
        </p>

        <p id="billing_last_name_field" class="form-row form-row-last validate-required">
         <label class="" for="billing_last_name">Last Name <abbr title="required" class="required">*</abbr>
         </label>
         <input type="text" value="" placeholder="" th:field="*{billingLastName}" class="input-text "/>
         <p th:if="${#fields.hasErrors('billingLastName')}" 
          th:errors="*{billingLastName}" th:errorclass="text-danger">Incorrect lastName</p>
        </p>
        <p id="billing_email_field" class="form-row form-row-first validate-required validate-email">
         <label class="" for="billing_email">Email Address <abbr title="required" class="required">*</abbr>
         </label>
         <input type="text" value="" placeholder="" th:field="*{emailId}" class="input-text "/>
         <p th:if="${#fields.hasErrors('emailId')}" 
          th:errors="*{emailId}" th:errorclass="text-danger">Incorrect emailId</p>
        </p>

        <p id="billing_phone_field" class="form-row form-row-last validate-required validate-phone">
         <label class="" for="billing_phone">Phone <abbr title="required" class="required">*</abbr>
         </label>
         <input type="text" value="" placeholder="" th:field="*{phone}"  class="input-text "/>
         <p th:if="${#fields.hasErrors('phone')}" 
          th:errors="*{phone}" th:errorclass="text-danger">Incorrect phone</p>
        </p>
        <div class="clear"></div>
        
        <p id="billing_address_1_field" class="form-row form-row-wide address-field validate-required">
         <label class="" for="billing_address_1">Address <abbr title="required" class="required">*</abbr>
         </label>
         <input type="text" value="" placeholder="Street address" th:field="*{billingAddressLine1}" class="input-text "/>
         <p th:if="${#fields.hasErrors('billingAddressLine1')}" th:errors="*{billingAddressLine1}" th:errorclass="text-danger">Incorrect addressLine1</p>
        </p>

        <p id="billing_address_2_field" class="form-row form-row-wide address-field">
         <input type="text" value="" placeholder="Apartment, suite, unit etc. (optional)" th:field="*{billingAddressLine2}"  class="input-text "/>
         <p th:if="${#fields.hasErrors('billingAddressLine2')}" th:errors="*{billingAddressLine2}" th:errorclass="text-danger">Incorrect addressLine2</p>
        </p>

        <p id="billing_city_field" class="form-row form-row-wide address-field validate-required" data-o_class="form-row form-row-wide address-field validate-required">
         <label class="" for="billing_city">Town / City <abbr title="required" class="required">*</abbr>
         </label>
         <input type="text" value="" placeholder="Town / City" th:field="*{billingCity}" class="input-text "/>
         <p th:if="${#fields.hasErrors('billingCity')}" th:errors="*{billingCity}" th:errorclass="text-danger">Incorrect city</p>
        </p>

        <p id="billing_state_field" class="form-row form-row-first address-field validate-state" data-o_class="form-row form-row-first address-field validate-state">
         <label class="" for="billing_state">State</label>
         <input type="text" th:field="*{billingState}" placeholder="State / County" value="" class="input-text "/>
         <p th:if="${#fields.hasErrors('billingState')}" th:errors="*{billingState}" th:errorclass="text-danger">Incorrect state</p>
        </p>
        <p id="billing_postcode_field" class="form-row form-row-last address-field validate-required validate-postcode" data-o_class="form-row form-row-last address-field validate-required validate-postcode">
         <label class="" for="billing_postcode">Zip Code <abbr title="required" class="required">*</abbr>
         </label>
         <input type="text" value="" placeholder="Postcode / Zip" th:field="*{billingZipCode}" class="input-text "/>
         <p th:if="${#fields.hasErrors('billingZipCode')}" th:errors="*{billingZipCode}" th:errorclass="text-danger">Incorrect zipCode</p>
        </p>
        <p id="billing_country_field" class="form-row form-row-wide address-field update_totals_on_change validate-required woocommerce-validated">
         <label class="" for="billing_country">Country <abbr title="required" class="required">*</abbr>
         </label>
         <select class="country_to_state country_select" th:field="*{billingCountry}">                                                    
          <option value="IN">India</option>
         </select>
        </p>
        <div class="clear"></div>                                            
       </div>
      </div>

      <div class="col-2">
       <div class="woocommerce-shipping-fields">
        <h3 id="ship-to-different-address">
         <label class="checkbox" for="ship-to-different-address-checkbox">Ship to same address?</label>
         <input type="checkbox" value="1" name="ship_to_different_address" checked="checked" 
           class="input-checkbox" id="ship-to-different-address-checkbox"/>
        </h3>
        <div class="shipping_address" style="display: block;">                                                

         <p id="shipping_first_name_field" class="form-row form-row-first validate-required">
          <label class="" for="shipping_first_name">First Name <abbr title="required" class="required">*</abbr>
          </label>
          <input type="text" value="" placeholder="" th:field="*{firstName}" class="input-text "/>
          <p th:if="${#fields.hasErrors('firstName')}" th:errors="*{firstName}" th:errorclass="text-danger">Incorrect firstName</p>
         </p>

         <p id="shipping_last_name_field" class="form-row form-row-last validate-required">
          <label class="" for="shipping_last_name">Last Name <abbr title="required" class="required">*</abbr>
          </label>
          <input type="text" value="" placeholder="" th:field="*{lastName}" class="input-text "/>
          <p th:if="${#fields.hasErrors('lastName')}" 
           th:errors="*{lastName}" th:errorclass="text-danger">Incorrect lastName</p>
         </p>
         <div class="clear"></div>

         <p id="shipping_address_1_field" class="form-row form-row-wide address-field validate-required">
          <label class="" for="shipping_address_1">Address <abbr title="required" class="required">*</abbr>
          </label>
          <input type="text" value="" placeholder="Street address" th:field="*{addressLine1}"  class="input-text "/>
          <p th:if="${#fields.hasErrors('addressLine1')}" th:errors="*{addressLine1}" th:errorclass="text-danger">Incorrect addressLine1</p>
         </p>

         <p id="shipping_address_2_field" class="form-row form-row-wide address-field">
          <input type="text" value="" placeholder="Apartment, suite, unit etc. (optional)" th:field="*{addressLine2}" class="input-text "/>
          <p th:if="${#fields.hasErrors('addressLine2')}" th:errors="*{addressLine2}" th:errorclass="text-danger">Incorrect addressLine2</p>
         </p>

         <p id="shipping_city_field" class="form-row form-row-wide address-field validate-required" data-o_class="form-row form-row-wide address-field validate-required">
          <label class="" for="shipping_city">City <abbr title="required" class="required">*</abbr>
          </label>
          <input type="text" value="" placeholder="Town / City" th:field="*{city}" class="input-text "/>
          <p th:if="${#fields.hasErrors('city')}" th:errors="*{city}" th:errorclass="text-danger">Incorrect city</p>
         </p>

         <p id="shipping_state_field" class="form-row form-row-first address-field validate-state" data-o_class="form-row form-row-first address-field validate-state">
          <label class="" for="shipping_state">State</label>
          <input type="text" th:field="*{state}" placeholder="State / County" value="" class="input-text "/>
          <p th:if="${#fields.hasErrors('state')}" th:errors="*{state}" th:errorclass="text-danger">Incorrect state</p>
         </p>
         <p id="shipping_postcode_field" class="form-row form-row-last address-field validate-required validate-postcode" data-o_class="form-row form-row-last address-field validate-required validate-postcode">
          <label class="" for="shipping_postcode">Zip Code <abbr title="required" class="required">*</abbr>
          </label>
          <input type="text" value="" placeholder="Postcode / Zip" th:field="*{zipCode}" class="input-text "/>
          <p th:if="${#fields.hasErrors('zipCode')}" th:errors="*{zipCode}" th:errorclass="text-danger">Incorrect zipCode</p>
         </p>
         <p id="shipping_country_field" class="form-row form-row-wide address-field update_totals_on_change validate-required woocommerce-validated">
          <label class="" for="shipping_country">Country <abbr title="required" class="required">*</abbr>
          </label>
          <select class="country_to_state country_select" th:field="*{country}" >
           <option value="IN">India</option>
          </select>
         </p>
         <div class="clear"></div>
        </div>
       </div>
      </div>
     </div>                                
     <div id="customer_details" class="col2-set">
      <div class="col-1">
       <div class="woocommerce-billing-fields">
        <h3>Payment Details</h3>

        <p id="cc_number" class="form-row form-row-first validate-required">
         <label class="" for="cc_number">Credit Card Number <abbr title="required" class="required">*</abbr>
         </label>
         <input type="text" th:field="*{ccNumber}" class="input-text "/>
         <p th:if="${#fields.hasErrors('ccNumber')}" th:errors="*{ccNumber}" th:errorclass="text-danger">Invalid Credit Card</p>
        </p>
        
        <p id="cc_cvv" class="form-row form-row-first validate-required">
         <label class="" for="cc_cvv">CCV <abbr title="required" class="required">*</abbr>
         </label>
         <input type="text" th:field="*{cvv}" class="input-text "/>
         <p th:if="${#fields.hasErrors('cvv')}" th:errors="*{cvv}" th:errorclass="text-danger">Invalid CVV</p>
        </p>
        <p id="payment_expiry_date" class="form-row form-row-wide validate-required woocommerce-validated">
         <label class="" for="shipping_country">Expiry Date <abbr title="required" class="required">*</abbr></label>
         <div style="display: inline;">
         <select style="width: 25%">
          <option value="2015">2015</option>
          <option value="2016">2016</option>
          <option value="2017">2017</option>
          <option value="2018">2018</option>
         </select>
         
         <select style="width: 25%">
          <option value="1">Jan</option>
          <option value="2">Feb</option>
          <option value="3">Mar</option>
          <option value="4">Apr</option>
         </select>
         </div>
        </p>
        
       </div>
      </div>
       </div>

     <div id="payment">
       
       <div class="form-row place-order">
        <input type="submit" data-value="Place order" value="Place order" id="place_order" name="woocommerce_checkout_place_order" class="button alt"/>
       </div>

       <div class="clear"></div>

      </div>
    </form>

   </div>                       
  </div>                    
 </div>
</div>
</div>
</div>
</div>
</body>
</html>

Next we need to implement the back-end services for Order related operations.

public interface OrderRepository extends JpaRepository<Order, Integer>
{
 Order findByOrderNumber(String orderNumber);
}

@Service
@Transactional
public class OrderService
{ 
 @Autowired OrderRepository orderRepository;
 
 public Order createOrder(Order order)
 {
  order.setOrderNumber(String.valueOf(System.currentTimeMillis()));
  Order savedOrder = orderRepository.save(order);
  return savedOrder;
 }
 
 public Order getOrder(String orderNumber)
 {
  return orderRepository.findByOrderNumber(orderNumber);
 }

}

@Controller
public class OrderController extends JCartSiteBaseController
{

 @Autowired private CustomerService customerService;
 @Autowired protected OrderService orderService;
 @Autowired protected EmailService emailService;
 
 @Override
 protected String getHeaderTitle()
 {
  return "Order";
 }

 @RequestMapping(value="/orders", method=RequestMethod.POST)
 public String placeOrder(@Valid @ModelAttribute("order") OrderDTO order, 
   BindingResult result, Model model, HttpServletRequest request)
 {
  Cart cart = getOrCreateCart(request);
  if (result.hasErrors()) {
   model.addAttribute("cart", cart);
   return "checkout";
        }
  
  Order newOrder = new Order();
  
  String email = getCurrentUser().getCustomer().getEmail();
  Customer customer = customerService.getCustomerByEmail(email);
  newOrder.setCustomer(customer);
  Address address = new Address();
  address.setAddressLine1(order.getAddressLine1());
  address.setAddressLine2(order.getAddressLine2());
  address.setCity(order.getCity());
  address.setState(order.getState());
  address.setZipCode(order.getZipCode());
  address.setCountry(order.getCountry());
  
  newOrder.setDeliveryAddress(address);
  
  Address billingAddress = new Address();
  billingAddress.setAddressLine1(order.getAddressLine1());
  billingAddress.setAddressLine2(order.getAddressLine2());
  billingAddress.setCity(order.getCity());
  billingAddress.setState(order.getState());
  billingAddress.setZipCode(order.getZipCode());
  billingAddress.setCountry(order.getCountry());
  
  newOrder.setBillingAddress(billingAddress);
  
  Set<OrderItem> orderItems = new HashSet<OrderItem>();
  List<LineItem> lineItems = cart.getItems();
  for (LineItem lineItem : lineItems)
  {
   OrderItem item = new OrderItem();
   item.setProduct(lineItem.getProduct());
   item.setQuantity(lineItem.getQuantity());
   item.setPrice(lineItem.getProduct().getPrice());
   item.setOrder(newOrder);
   orderItems.add(item);
  }
  
  newOrder.setItems(orderItems);
  
  Payment payment = new Payment();
  payment.setCcNumber(order.getCcNumber());
  payment.setCvv(order.getCvv());
  
  newOrder.setPayment(payment);
  Order savedOrder = orderService.createOrder(newOrder);
  
  this.sendOrderConfirmationEmail(savedOrder);
  
  request.getSession().removeAttribute("CART_KEY");
  return "redirect:orderconfirmation?orderNumber="+savedOrder.getOrderNumber();
 }
 
 protected void sendOrderConfirmationEmail(Order order)
 {
  try {
   emailService.sendEmail(order.getCustomer().getEmail(), 
     "QuilCartCart - Order Confirmation", 
     "Your order has been placed successfully.\n"
     + "Order Number : "+order.getOrderNumber());
  } catch (JCartException e) {
   logger.error(e);
  }
 }
 
 @RequestMapping(value="/orderconfirmation", method=RequestMethod.GET)
 public String showOrderConfirmation(@RequestParam(value="orderNumber")String orderNumber, Model model)
 {
  Order order = orderService.getOrder(orderNumber);
  model.addAttribute("order", order);
  return "orderconfirmation";
 }

}

Create the template orderconfirmation.html for showing order confirmation as follows:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
 xmlns:th="http://www.thymeleaf.org"
 xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3"
 layout:decorator="layout/mainLayout">
<head>
<title>Order Confirmation</title>
</head>
<body>
 <div layout:fragment="content">
  <div class="single-product-area">
   <div class="zigzag-bottom"></div>
   <div class="container">
    <div class="row">

     <div class="woocommerce-info col-md-offset-2 col-md-8">
      <div th:unless="${order}" >
       <h2>No order found</h2>
      </div>
      <div th:if="${order}" >
       <h2>Your order has been placed successfully.</h2>
       <h2>
        Order Number : <span th:text="${order.orderNumber}">Number</span>
       </h2>
       <table class="table">
        <thead>
         <tr>
          <th>Name</th>
          <th>Quantity</th>
          <th>Cost</th>
         </tr>
        </thead>
        <tbody>
         <tr th:each="item : ${order.items}">
          <td th:text="${item.product.name}">product.name</td>
          <td th:text="${item.quantity}"></td>
          <td th:text="${item.price * item.quantity}">price</td>
         </tr>
        </tbody>
        <tfoot>
         <tr class="cart-subtotal">
          <th>Order Subtotal</th>
          <td><span class="amount" th:text="${order.totalAmount}">£15.00</span>
          </td>
         </tr>

         <tr class="shipping">
          <th>Shipping and Handling</th>
          <td>Free Shipping</td>
         </tr>

         <tr class="order-total">
          <th>Order Total</th>
          <td><strong><span class="amount" th:text="${order.totalAmount}">£15.00</span></strong> </td>
         </tr>

        </tfoot>
       </table>
      </div>
     </div>
    </div>
   </div>
  </div>
 </div>
</body>
</html>

Now you can add items to cart, view cart item details, and checkout by providing delivery and billing info and finally place order. Once the order is successfully placed it will display the order confirmation page.

JCart : Customer Registration

To facilitate new customer registration we will provide a new Registration form where customer provide his details and register with our system.

Let us implement the back-end customer service operations.

public interface CustomerRepository extends JpaRepository<Customer, Integer>{
 Customer findByEmail(String email);
}

@Service
@Transactional
public class CustomerService 
{
 @Autowired CustomerRepository customerRepository;
 
 public Customer getCustomerByEmail(String email) {
  return customerRepository.findByEmail(email);
 }

 public Customer createCustomer(Customer customer) {
  return customerRepository.save(customer);
 }
}

@Component
public class CustomerValidator implements Validator
{
 @Autowired private CustomerService custmoerService;

 @Override
 public boolean supports(Class<?> clazz) {
  return Customer.class.isAssignableFrom(clazz);
 }

 @Override
 public void validate(Object target, Errors errors) {
  Customer customer = (Customer) target;
  Customer customerByEmail = custmoerService.getCustomerByEmail(customer.getEmail());
  if(customerByEmail != null){
   errors.rejectValue("email", "error.exists", 
   new Object[]{customer.getEmail()}, 
   "Email "+customer.getEmail()+" already in use");
  }
 }
 
}

Let us implement the CustomerController registration handler methods as follows:

@Controller
public class CustomerController extends JCartSiteBaseController
{ 
 @Autowired private CustomerService customerService;
 @Autowired private CustomerValidator customerValidator;
 @Autowired protected PasswordEncoder passwordEncoder;
 
 @Override
 protected String getHeaderTitle()
 {
  return "Login/Register";
 }

 @RequestMapping(value="/register", method=RequestMethod.GET)
 protected String registerForm(Model model)
 {
  model.addAttribute("customer", new Customer());
  return "register";
 }
 
 @RequestMapping(value="/register", method=RequestMethod.POST)
 protected String register(@Valid @ModelAttribute("customer") Customer customer, 
  BindingResult result, Model model, RedirectAttributes redirectAttributes)
 {
  customerValidator.validate(customer, result);
  if(result.hasErrors()){
   return "register";
  }
  String password = customer.getPassword();
  String encodedPwd = passwordEncoder.encode(password);
  customer.setPassword(encodedPwd);
  
  Customer persistedCustomer = customerService.createCustomer(customer);
  logger.debug("Created new Customer with id : {} and email : {}", persistedCustomer.getId(), persistedCustomer.getEmail());
  redirectAttributes.addFlashAttribute("info", "Customer created successfully");
  return "redirect:/login";
 }
 
}

Finally let us create the register.html thymeleaf view as follows:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" 
   xmlns:th="http://www.thymeleaf.org"
   xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3"
      layout:decorator="layout/mainLayout">
      
<head>
 <title>Register</title>
</head>
<body>
 <div layout:fragment="content">
  <div class="single-product-area">
   <div class="zigzag-bottom"></div>
   <div class="container">
    
    <div class="row">
     
     <div class="col-md-offset-3 col-md-6" >
      <form id="login-form-wrap" th:action="@{/register}" th:object="${customer}" method="post">

       <p class="form-row form-row-first">
        <label for="firstName">FirstName <span class="required">*</span>
        </label>
        <input type="text" th:field="*{firstName}" class="input-text"/>
        <p th:if="${#fields.hasErrors('firstName')}" th:errors="*{firstName}" th:errorclass="text-danger">Incorrect data</p>
       </p>
       
       <p class="form-row form-row-first">
        <label for="lastName">LastName <span class="required">*</span>
        </label>
        <input type="text" th:field="*{lastName}" class="input-text"/>
        <p th:if="${#fields.hasErrors('lastName')}" th:errors="*{lastName}" th:errorclass="text-danger">Incorrect data</p>
        
       </p>
       
       <p class="form-row form-row-first">
        <label for="email">Email <span class="required">*</span>
        </label>
        <input type="email" th:field="*{email}" class="input-text" placeholder="Email"/>
        <p th:if="${#fields.hasErrors('email')}" th:errors="*{email}" th:errorclass="text-danger">Incorrect data</p>
       </p>
       <p class="form-row form-row-last">
        <label for="password">Password <span class="required">*</span>
        </label>
        <input type="password" th:field="*{password}" class="input-text" placeholder="Password"/>
        <p th:if="${#fields.hasErrors('password')}" th:errors="*{password}" th:errorclass="text-danger">Incorrect data</p>
       </p>
       
       <p class="form-row form-row-first">
        <label for="phone">Phone <span class="required">*</span>
        </label>
        <input type="text" th:field="*{phone}" class="input-text"/>
        <p th:if="${#fields.hasErrors('phone')}" th:errors="*{phone}" th:errorclass="text-danger">Incorrect data</p>
       </p>
       <div class="clear"></div>


       <p class="form-row">
        <input type="submit" value="Login" class="button"/>
       </p>
       
       <p>
        <div th:if="${info!=null}" class="alert alert-warning alert-dismissable" >
         <p><i class="icon fa fa-warning"></i> <span th:text="${info}"></span></p>
        </div>   
       </p>
       <p class="lost_password">
        Existing Customer? <a href="#" th:href="@{/login}" th:text="#{label.login}">Login</a>
       </p>
       
       <div class="clear"></div>
      </form>
      
     </div>
    </div>
    
   </div>
  </div>
 </div>
 
</body>
    
</html>

Now new customers can click on Register link and register themselves. Once the registration is successful he can login and proceed to checkout the cart.

JCart : Customer Login

So far we have implemented the functionality where customers can browse the categories, add products to cart, view Cart and update/remove items. But to checkout the cart the customer should login into the system. So if the customer is not yet loggedin we should redirect customer to login page. If customer is already registered with our system he can login or he should be able to register.

So, we will start implementing Customer Login/Registration usecases.

Let us create login form thymeleaf view jcart-site/src/main/resources/templates/login.html as follows:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" 
   xmlns:th="http://www.thymeleaf.org"
   xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3"
      layout:decorator="layout/mainLayout">
      
<head>
 <title>Login</title>
</head>
<body>
 <div layout:fragment="content">
  <div class="single-product-area">
   <div class="zigzag-bottom"></div>
   <div class="container">
    
    <div class="row">
     
     <div class="col-md-offset-4 col-md-4" >
      <form id="login-form-wrap" th:action="@{/login}" method="post">


       <p class="form-row form-row-first">
        <label for="email">Email <span class="required">*</span>
        </label>
        <input type="text" id="username" name="username" class="input-text" placeholder="Email"/>
       </p>
       <p class="form-row form-row-last">
        <label for="password">Password <span class="required">*</span>
        </label>
        <input type="password" id="password" name="password" class="input-text" placeholder="Password"/>
       </p>
       <div class="clear"></div>


       <p class="form-row">
        <input type="submit" value="Login" class="button"/>
       </p>
       
       <p>
        <div th:if="${param.error}" class="alert alert-danger alert-dismissable" >
         <p><i class="icon fa fa-ban"></i> <span th:text="#{error.login_failed}">Invalid Email and Password.</span></p>
        </div>
        <div th:if="${param.logout}" class="alert alert-info alert-dismissable" >
         <p><i class="icon fa fa-info"></i> <span th:text="#{info.logout_success}">You have been logged out.</span></p>
        </div>            
        <div th:if="${info!=null}" class="alert alert-warning alert-dismissable" >
         <p><i class="icon fa fa-warning"></i> <span th:text="${info}"></span></p>
        </div>   
       </p>
       <p class="lost_password">
        New Customer? <a href="#" th:href="@{/register}" th:text="#{label.register}">Register</a>
       </p>
       
       <div class="clear"></div>
      </form>
      
     </div>
    </div>
    
   </div>
  </div>
 </div>
 
</body>
    
</html>

We are using SpringSecurity for Customer Authentication and we have already configured SpringSecurity in our previous post JCart : Initial code setup for ShoppingCart We have already created a couple of sample customer records using the seed data sql script jcart-core/src/main/resources/data.sql.

You can try to login using sivaprasadreddy.k@gmail.com/siva credentials. If login is successful it will redirect to /checkout url which we have not yet implemented, otherwise it will show login error.

JCart : Iteration -6

In this Iteration-6 we will be implementing the Customer Login/Register and placing the orders.
As part of this we will implement the following usecases:

JCart : View Cart

In our earlier post we have implemented Add To Cart functionality. In this post we will implement showing the Cart Item details.

In out mainLayout.html header we have ShoppingCart icon showing the cart item count as follows:

<div class="shopping-item">
 <a href="#" th:href="@{/cart}">Cart <i class="fa fa-shopping-cart"></i> <span id="cart-item-count" class="product-count">(0)</span></a>
</div>

When customer clicks on Cart icon we will show the Cart details. Let us implement the "/cart" url handler method in CartController as follows:

@Controller
public class CartController extends JCartSiteBaseController
{
 ....
 
 @RequestMapping(value="/cart", method=RequestMethod.GET)
 public String showCart(HttpServletRequest request, Model model)
 {
  Cart cart = getOrCreateCart(request);
  model.addAttribute("cart", cart);
  return "cart";
 }
}

Now let us create thymeleaf view template cart.html as follows:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
   xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3"
      layout:decorator="layout/mainLayout">
      
      <head>
        <title>Cart</title>
    </head>
    <body>
     <div layout:fragment="content">
    
      <div class="single-product-area">
          <div class="zigzag-bottom"></div>
          <div class="container">
              <div class="row">
                  <div class="woocommerce-info col-md-offset-2 col-md-8" th:if="${#lists.isEmpty(cart.items)}">
       <h2>Cart is Empty</h2>
      </div>
                  <div class="col-md-offset-2 col-md-8" th:unless="${#lists.isEmpty(cart.items)}">
                      <div class="product-content-right">
                          <div class="woocommerce">
                              <form method="post" action="#">
                                  <table cellspacing="0" class="shop_table cart">
                                      <thead>
                                          <tr>
                                              <th class="product-remove">&nbsp;</th>
                                              <th class="product-thumbnail">&nbsp;</th>
                                              <th class="product-name">Product</th>
                                              <th class="product-price">Price</th>
                                              <th class="product-quantity">Quantity</th>
                                              <th class="product-subtotal">Total</th>
                                          </tr>
                                      </thead>
                                      <tbody>
                                          <tr class="cart_item" th:each="item : ${cart.items}">
                                              <td class="product-remove">
                                                  <a title="Remove this item" class="remove" href="#" 
                                                   th:onclick="'javascript:removeItemFromCart( \''+${item.product.sku}+'\');'">×</a> 
                                              </td>
  
                                              <td class="product-thumbnail">
                                                  <a href="#" th:href="@{/products/{sku}(sku=${item.product.sku})}">
                                                   <img width="145" height="145" alt="poster_1_up" 
                                                   class="shop_thumbnail" src="assets/img/products/2.jpg"
                                                   th:src="@{'/products/images/{id}.jpg'(id=${item.product.id})}"/>
                                                  </a>
                                              </td>
  
                                              <td class="product-name">
                                                  <a href="#" th:href="@{/products/{sku}(sku=${item.product.sku})}"
                                                   th:text="${item.product.name}">Product name</a> 
                                              </td>
  
                                              <td class="product-price">
                                                  <span class="amount" th:text="${item.product.price}">$15.00</span> 
                                              </td>
  
                                              <td class="product-quantity">
                                                  <div class="quantity buttons_added">
                                                   <input type="text" size="5" value="1" th:value="${item.quantity}" 
                                                     th:onchange="'javascript:updateCartItemQuantity( \''+${item.product.sku}+'\' , '+this.value+');'"/>                                                   
                                                  </div>
                                              </td>
  
                                              <td class="product-subtotal">
                                                  <span class="amount" th:text="${item.product.price * item.quantity}">$150.00</span> 
                                              </td>
                                          </tr>
                                          <tr>
                                              <td class="actions" colspan="6">
                                                  <a class="add_to_cart_button" href="#" th:href="@{/checkout}">CHECKOUT</a>
                                              </td>
                                          </tr>
                                      </tbody>
                                  </table>
                              </form>
  
                              <div class="cart-collaterals">
                          <div class="cart_totals ">
                                  <h2>Cart Totals</h2>
  
                                  <table cellspacing="0">
                                      <tbody>
                                          <tr class="cart-subtotal">
                                              <th>Cart Subtotal</th>
                                              <td><span class="amount" th:text="${cart.totalAmount}">$15.00</span></td>
                                          </tr>
  
                                          <tr class="shipping">
                                              <th>Shipping and Handling</th>
                                              <td>Free Shipping</td>
                                          </tr>
  
                                          <tr class="order-total">
                                              <th>Order Total</th>
                                              <td><strong><span class="amount" th:text="${cart.totalAmount}">$15.00</span></strong> </td>
                                          </tr>
                                      </tbody>
                                  </table>
                              </div>
  
                              </div>
                          </div>                        
                      </div>                    
                  </div>
              </div>
          </div>
      </div>
  
  </div>
  </body>
</html>

Now run the application and add items to cart and click on Cart Icon which should display Cart page with all the Cart item details.

Observe that we have already added HTML markup and JavaScript function calls to update the Item quantity and removing an item.

We will implement these functionalities in a moment. Let us add the following two JavaScript functions to update item count and remove items.

function updateCartItemQuantity(sku, quantity)
{
 $.ajax ({ 
  url: '/cart/items', 
  type: "PUT", 
  dataType: "json",
  contentType: "application/json",
  data : '{ "product" :{ "sku":"'+ sku +'"},"quantity":"'+quantity+'"}',
  complete: function(responseData, status, xhttp){ 
   updateCartItemCount();         
   location.href = '/cart' 
  }
 });
}

function removeItemFromCart(sku)
{
 $.ajax ({ 
  url: '/cart/items/'+sku, 
  type: "DELETE", 
  dataType: "json",
  contentType: "application/json",
  complete: function(responseData, status, xhttp){ 
   updateCartItemCount();
   location.href = '/cart' 
  }
 });
}

Next we will implement the CartController handler methods as follows:

@Controller
public class CartController extends JCartSiteBaseController
{
 ...
 ...
 
 @RequestMapping(value="/cart/items", method=RequestMethod.PUT)
 @ResponseBody
 public void updateCartItem(@RequestBody LineItem item, HttpServletRequest request, HttpServletResponse response)
 {
  Cart cart = getOrCreateCart(request);
  if(item.getQuantity() <= 0){
   String sku = item.getProduct().getSku();
   cart.removeItem(sku);
  } else {
   cart.updateItemQuantity(item.getProduct(), item.getQuantity());
  }
 }
 
 @RequestMapping(value="/cart/items/{sku}", method=RequestMethod.DELETE)
 @ResponseBody
 public void removeCartItem(@PathVariable("sku") String sku, HttpServletRequest request)
 {
  Cart cart = getOrCreateCart(request);
  cart.removeItem(sku);
 }

}

Now that we have completed all the Cart related usecases. In our next post we will see how to implement Checkout functionality.

JCart : ShoppingCart Add Item To Cart

In our HomePage/CategoryPage/ProductPage we have a button Add To Cart as follows:

<a class="add_to_cart_button" data-quantity="1" data-product_sku="" data-product_id="70" 
 rel="nofollow" href="#"
 th:onclick="'javascript:addItemToCart(\'' + ${product.sku} + '\');'">Add to cart</a>

When customer clicks on Add To Cart button it will trigger addItemToCart(sku) JavaScript function passing the product SKU value.

Now create jcart-site/src/main/resources/static/assets/js/app.js and implement addItemToCart(sku) function as follows:

function addItemToCart(sku)
{
 $.ajax ({ 
  url: '/cart/items', 
  type: "POST", 
  dataType: "json",
  contentType: "application/json",
  data : '{"sku":"'+ sku +'"}"',
  complete: function(responseData, status, xhttp){
   updateCartItemCount();   
  }
 }); 
}

This function triggers an Ajax call to url '/cart/items' using jQuery and if it is successful we are calling another JavaScript function updateCartItemCount(). The updateCartItemCount() function updates the current Cart Items count in the page header section.

<div class="shopping-item">
 <a href="#" th:href="@{/cart}">Cart <i class="fa fa-shopping-cart"></i> <span id="cart-item-count" class="product-count">(0)</span></a>
</div>
function updateCartItemCount()
{
 $.ajax ({ 
  url: '/cart/items/count', 
  type: "GET", 
  dataType: "json",
  contentType: "application/json",
  complete: function(responseData, status, xhttp){ 
   $('#cart-item-count').text('('+responseData.responseJSON.count+')');
  }
 });
}

The updateCartItemCount() function triggers an Ajax call to url: '/cart/items/count' to get the current Cart Item count. Once the response is received we are setting the count value.

We need to display the current Cart Item Count on all pages, so let us invoke updateCartItemCount() function in app.js for all the page load as follows:

jQuery(document).ready(function($){
 updateCartItemCount();
});

function updateCartItemCount()
{
 ...
}

function updateCartItemCount()
{
 ...
}

Now let us implement the back-end functionality to handle Cart related operations. First let us create the model objects to hold Cart and LineItem data.

public class Cart
{
 private List<LineItem> items;
 private Customer customer;
 private Address deliveryAddress;
 private Payment payment;
 
 public Cart()
 {
  items = new ArrayList<LineItem>();
  customer = new Customer();
  deliveryAddress = new Address();
  payment = new Payment();
 }
 

 public void addItem(Product product)
 {
  for (LineItem lineItem : items)
  {
   if(lineItem.getProduct().getSku().equals(product.getSku())){
    lineItem.setQuantity(lineItem.getQuantity()+1);
    return;
   }
  }
  LineItem item = new LineItem(product, 1);
  this.items.add(item);  
 }
 
 public void updateItemQuantity(Product product, int quantity)
 {
  for (LineItem lineItem : items)
  {
   if(lineItem.getProduct().getSku().equals(product.getSku())){
    lineItem.setQuantity(quantity);
   }
  }
 }
 
 public void removeItem(String sku)
 {
  LineItem  item = null;
  for (LineItem lineItem : items)
  {
   if(lineItem.getProduct().getSku().equals(sku)){
    item = lineItem;
    break;
   }
  }
  if(item != null){
   items.remove(item);
  }
 }
 
 public void clearItems()
 {
  items = new ArrayList<LineItem>();
 }
 
 public int getItemCount()
 {
  int count = 0;
  for (LineItem lineItem : items) {
   count +=  lineItem.getQuantity();
  }
  return count;
 }
  
 public BigDecimal getTotalAmount()
 {
  BigDecimal amount = new BigDecimal("0.0");
  for (LineItem lineItem : items)
  {
   amount = amount.add(lineItem.getSubTotal());
  }
  return amount;
 }
 
 //setters & getters
 
}
public class LineItem
{
 private Product product;
 private int quantity;
 
 public LineItem()
 {
 }
 
 public LineItem(Product product, int quantity)
 {
  this.product = product;
  this.quantity = quantity;
 }

 public BigDecimal getSubTotal()
 {
  return product.getPrice().multiply(new BigDecimal(quantity));
 }
 //setters & getters
}

We may need to get the current Cart object in one or more Controllers. So let us create a method getOrCreateCart(HttpServletRequest) in JCartSiteBaseController so that it will be available in all controllers.

public abstract class JCartSiteBaseController
{
 ....
 ....
 
 protected Cart getOrCreateCart(HttpServletRequest request)
 {
  Cart cart = null;
  cart = (Cart) request.getSession().getAttribute("CART_KEY");
  if(cart == null){
   cart = new Cart();
   request.getSession().setAttribute("CART_KEY", cart);
  }
  return cart;
 }
}

Now let us implement the CartController as follows:

@Controller
public class CartController extends JCartSiteBaseController
{
 @Autowired
 private CatalogService catalogService;
 
 @Override
 protected String getHeaderTitle()
 {
  return "Cart";
 }
  
 @RequestMapping(value="/cart/items/count", method=RequestMethod.GET)
 @ResponseBody
 public Map<String, Object> getCartItemCount(HttpServletRequest request, Model model)
 {
  Cart cart = getOrCreateCart(request);
  int itemCount = cart.getItemCount();
  Map<String, Object> map = new HashMap<String, Object>();
  map.put("count", itemCount);
  return map;
 }
  
 @RequestMapping(value="/cart/items", method=RequestMethod.POST)
 @ResponseBody
 public void addToCart(@RequestBody Product product, HttpServletRequest request)
 {
  Cart cart = getOrCreateCart(request);
  Product p = catalogService.getProductBySku(product.getSku());
  cart.addItem(p);
 }
 
}

Now run the application and click on Add To Cart in HomePage/CategoryPage/ProductPage, the product should be added to Cart and the Cart Item Count in header should be updated accordingly.

JCart : Iteration-5

In Iteration-5 we will be primarily working on Cart related functionality. As part of Iteration-4 we have implemented showing Home page, Category Page, Product Page and Product Search features.

In this Iteration we will implement the following usecases:

JCart : ShoppingCart Product Search Results

In our main template we have a Search box to search for products. In this post we will implement the Product Search functionality. When customer search for a product we will search products based on name or SKU or description.

Let us implement the search handler method in ProductController as follows:

@Controller
public class ProductController extends JCartSiteBaseController
{ 
 @Autowired protected CatalogService catalogService;
 
 ...
 ...
 
 @RequestMapping("/products")
 public String searchProducts(@RequestParam(name="q", defaultValue="") String query, Model model)
 {
  List<Product> products = catalogService.searchProducts(query);
  model.addAttribute("products", products);
  return "products";
 }
 
}

Create the Search Results view products.html as follows:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" 
   xmlns:th="http://www.thymeleaf.org"
   xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3"
      layout:decorator="layout/mainLayout">
      
<head>
 <title>Product Search Results</title>
</head>
<body>
 <div layout:fragment="content">
  <div class="single-product-area">
   <div class="zigzag-bottom"></div>
   <div class="container">
    
    <div class="row">
     <div class="woocommerce-info"> 
      <span class="">Product Search Results</span>
      </div>
     <div class="col-md-3 col-sm-6" th:each="product : ${products}">
      <div class="single-shop-product">
       <div class="product-upper">
        <img src="assets/img/products/2.jpg" alt="" 
          th:src="@{'/products/images/{id}.jpg'(id=${product.id})}"/>
       </div>
       <h2><a href="#" th:href="@{/products/{sku}(sku=${product.sku})}" 
         th:text="${product.name}">Product Name</a></h2>
       <div class="product-carousel-price">
        <ins th:text="${product.price}">$9.00</ins>
       </div>  
       
       <div class="product-option-shop">
        <a class="add_to_cart_button" data-quantity="1" data-product_sku="" data-product_id="70" 
         rel="nofollow" href="#"
         th:onclick="'javascript:addItemToCart(\'' + ${product.sku} + '\');'">Add to cart</a>
       </div>
      </div>
     </div>
     
    </div>
    
   </div>
  </div>
 </div>
 
</body>    
</html>

Now try to search by any product name or sku or description and you should be able to see the matching product results.

JCart : ShoppingCart Product Page

Customers can click on a product to view more details about the product either in Home Page or in Category Page.

Let us implement Controller method to show Product details as follows:

@Controller
public class ProductController extends JCartSiteBaseController
{ 
 @Autowired protected CatalogService catalogService;
 
 ....
 ....
 
 @RequestMapping("/products/{sku}")
 public String product(@PathVariable String sku, Model model)
 {
  Product product = catalogService.getProductBySku(sku);
  model.addAttribute("product", product);
  return "product";
 }
 
}
@Service
@Transactional
public class CatalogService 
{
 @Autowired ProductRepository productRepository;
 
 ....
 ....
 
 public Product getProductBySku(String sku) {
  return productRepository.findBySku(sku);
 }
 
}
public interface ProductRepository extends JpaRepository<Product, Integer> {

 Product findByName(String name);
 Product findBySku(String sku);
}

Now we will create the product.html thymeleaf template as follows:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
   xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3"
      layout:decorator="layout/mainLayout">
      
<head>
 <title>Product</title>
</head>
<body>
<div layout:fragment="content">

<div class="single-product-area">
 <div class="zigzag-bottom"></div>
 <div class="container">
  <div class="row">
   
   <div class="col-md-offset-2 col-md-8">
    <div class="product-content-right">
     <div class="product-breadcroumb">
      <a href="" th:href="@{/}">Home</a>
      <a href="" th:href="@{/categories/{name}(name=${product.category.name})}">
      <span th:text="${product.category.name}">Category Name</span></a>
      <a href="" th:href="@{/products/{sku}(sku=${product.sku})}">
      <span th:text="${product.name}">ProductName</span></a>
     </div>
     
     <div class="row">
      <div class="col-sm-6">
       <div class="product-images">
        <div class="product-main-img">
         <img src="assets/img/product-2.jpg" alt="" 
         th:src="@{'/products/images/{id}.jpg'(id=${product.id})}"/>
        </div>
                 
       </div>
      </div>
      
      <div class="col-sm-6">
       <div class="product-inner">
        <h2 class="product-name" th:text="${product.name}">ProductName</h2>
        <div class="product-inner-price">
         <ins th:text="${product.price}">$9.00</ins>
        </div>    
        
        <div>
         <button class="add_to_cart_button" type="submit" 
           th:onclick="'javascript:addItemToCart(\'' + ${product.sku} + '\');'">Add to cart</button>
        </div>   
        
        <div class="product-inner-category">
         <h2>Product Description</h2>  
         <p th:text="${product.description}">
          Lorem ipsum dolor sit amet, consectetur adipiscing elit.<br/>
         </p>
        </div> 
        
       </div>
      </div>
     </div>
     
    </div>                    
   </div>
  </div>
 </div>
</div>

</div>
</body>
</html>

Now we can see the product details when clicked on Product from Home Page or Category Page.

JCart : ShoppingCart Category Page

In our Home Page we displayed all the Categories along with few products per each category. When customer clicks on any Category Name we should display Category Page which shows all the products in that Category.

We already have HomeController.category() method to handle the URL /categories/{name}.
So let us create category.html thymeleaf template as follows:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" 
   xmlns:th="http://www.thymeleaf.org"
   xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3"
      layout:decorator="layout/mainLayout">
      
      <head>
        <title>Category</title>
    </head>
    <body>
     <div layout:fragment="content">
      <div class="single-product-area">
          <div class="zigzag-bottom"></div>
          <div class="container">
           
              <div class="row">
               <div class="woocommerce-info"> 
                <a href="" th:href="@{/}">Home</a> / 
                      <a href="" th:href="@{/categories/{name}(name=${category.name})}" 
                       th:text="${category.name}">Category Name</a>
                   </div>
                  <div class="col-md-3 col-sm-6" th:each="product : ${category.products}">
                      <div class="single-shop-product">
                          <div class="product-upper">
                              <img src="assets/img/products/2.jpg" alt="" 
                                th:src="@{'/products/images/{id}.jpg'(id=${product.id})}"/>
                          </div>
                          <h2><a href="#" th:href="@{/products/{sku}(sku=${product.sku})}" 
                            th:text="${product.name}">Product name</a></h2>
                          <div class="product-carousel-price">
                              <ins th:text="${product.price}">$9.00</ins>
                          </div>
                          
                          <div class="product-option-shop">
                              <a class="add_to_cart_button" data-quantity="1" data-product_sku="" 
                               data-product_id="70" rel="nofollow" href="#"
                               th:onclick="'javascript:addItemToCart(\'' + ${product.sku} + '\');'">Add to cart</a>
                          </div>                       
                      </div>
                  </div>
                  
              </div>
              
          </div>
      </div>
     </div>
     
    </body>
    
</html>

Now when you click on Category Name you should be able to see all the products in that category.

JCart : ShoppingCart Home Page

In our Home page we will show all the categories along with few of the products in each Category.

Let us update HomeController with two methods to show all the categories and the selected category products.

@Controller
public class HomeController extends JCartSiteBaseController
{ 
 
 @Autowired 
 protected CatalogService catalogService;
 
 @RequestMapping("/home")
 public String home(Model model)
 {
  List<Category> previewCategories = new ArrayList<>();
  List<Category> categories = catalogService.getAllCategories();
  for (Category category : categories)
  {
   Set<Product> products = category.getProducts();
   Set<Product> previewProducts = new HashSet<>();
   int noOfProductsToDisplay = 4;
   if(products.size() > noOfProductsToDisplay){
    Iterator<Product> iterator = products.iterator();
    for (int i = 0; i < noOfProductsToDisplay; i++)
    {
     previewProducts.add(iterator.next());
    }
   } else {
    previewProducts.addAll(products);
   } 
   category.setProducts(previewProducts);
   previewCategories.add(category);
  }
  model.addAttribute("categories", previewCategories);
  return "home";
 }
 
 @RequestMapping("/categories/{name}")
 public String category(@PathVariable String name, Model model)
 {
  Category category = catalogService.getCategoryByName(name);
  model.addAttribute("category", category);
  return "category";
 }
 
}

Now let us update the home page template home.html to render category details.

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
   xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3"
      layout:decorator="layout/mainLayout">      
      <head>
        <title>Home</title>
    </head>
    <body>
     <div layout:fragment="content">
      <div class="single-product-area">
          <div class="zigzag-bottom"></div>
          <div class="container">
           
              <div class="row" th:each="cat : ${categories}">
               <div class="woocommerce-info"> 
                <a class="" th:href="@{/categories/{name}(name=${cat.name})}" 
                 th:text="${'Category: '+cat.name}">Category Name</a>
                   </div>
                  <div class="col-md-3 col-sm-6" th:each="product : ${cat.products}">
                      <div class="single-shop-product">
                          <div class="product-upper">
                              <img src="assets/img/products/2.jpg" alt="" 
                                th:src="@{'/products/images/{id}.jpg'(id=${product.id})}"/>
                          </div>
                          <h2><a href="#" th:href="@{/products/{sku}(sku=${product.sku})}" 
                            th:text="${product.name}">Product Name</a></h2>
                          <div class="product-carousel-price">
                              <ins th:text="${product.price}">$9.00</ins>
                          </div>  
                          
                          <div class="product-option-shop">
                              <a class="add_to_cart_button" data-quantity="1" data-product_sku="" data-product_id="70" 
                               rel="nofollow" href="#"
                               th:onclick="'javascript:addItemToCart(\'' + ${product.sku} + '\');'">Add to cart</a>
                          </div>
                      </div>
                  </div>
                  
              </div>
              
          </div>
      </div>
     </div>
     
    </body>
    
</html>

In the above home.html template we are using some of the URLs for which we haven't implemented the handlers.

For example,
To display the product image : th:src="@{'/products/images/{id}.jpg'(id=${product.id})}"
To show product details : th:href="@{/products/{sku}(sku=${product.sku})}"
To add the product to Cart : th:onclick="'javascript:addItemToCart(\'' + ${product.sku} + '\');'"

Let us implement the handler for displaying product image.

Create ProductController.java as follows:

@Controller
public class ProductController extends JCartSiteBaseController
{ 
 @Override
 protected String getHeaderTitle()
 {
  return "Product";
 } 
 
 @RequestMapping(value="/products/images/{productId}", method=RequestMethod.GET)
 public void showProductImage(@PathVariable String productId, HttpServletRequest request, HttpServletResponse response) {
  try {
   //WebUtils.IMAGES_DIR = "D:/jcart/products/";
   FileSystemResource file = new FileSystemResource(WebUtils.IMAGES_DIR +productId+".jpg");     
   response.setContentType("image/jpg");
   org.apache.commons.io.IOUtils.copy(file.getInputStream(), response.getOutputStream());
   response.flushBuffer();
  } catch (IOException e) {
   e.printStackTrace();
  }
 }
}
Copy the sample product images from jcart-site/src/main/resources/static/assets/img/products folder into the path WebUtils.IMAGES_DIR(D:/jcart/products/).
Now we should be able to see the Home page with all the categories and 4 products for each category.

In our next post we will implement the Category Page which shows all the products in the selected Category.

JCart : ShoppingCart UI Layout Setup

In this post we will setup the layout for our ShoppingCart UI using Thymeleaf templates.

Download the ustore theme zip file from https://www.freshdesignweb.com/ustora/  and copy the following directories/files into jcart-site/src/main/resources/static/assets folder.
  • css
  • fonts
  • img
  • js
  • style.css
Create Site layout thymeleaf template jcart-site/src/main/resources/templates/layout/mainLayout.html as follows:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" 
   xmlns:th="http://www.thymeleaf.org"
   xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
   
<head>
    <meta charset="utf-8"/>
    <meta http-equiv="X-UA-Compatible" content="IE=edge"/>
    <meta name="viewport" content="width=device-width, initial-scale=1"/>
    <title layout:title-pattern="$DECORATOR_TITLE - $CONTENT_TITLE">QuilCart</title>
    
    <!-- Google Fonts -->
    <link href='https://fonts.googleapis.com/css?family=Titillium+Web:400,200,300,700,600' rel='stylesheet' type='text/css'/>
    <link href='https://fonts.googleapis.com/css?family=Roboto+Condensed:400,700,300' rel='stylesheet' type='text/css'/>
    <link href='https://fonts.googleapis.com/css?family=Raleway:400,100' rel='stylesheet' type='text/css'/>
    
    <link rel="stylesheet" th:href="@{/assets/css/bootstrap.min.css}"/>
    <link rel="stylesheet" th:href="@{/assets/css/font-awesome.min.css}"/>    
    <link rel="stylesheet" th:href="@{/assets/css/owl.carousel.css}"/>
    <link rel="stylesheet" th:href="@{/assets/style.css}"/>
    <link rel="stylesheet" th:href="@{/assets/css/responsive.css}"/>    
  </head>
  <body>
   
    <div class="header-area">
        <div class="container">
            <div class="row">
                <div class="col-md-offset-8 col-md-4">
                    <div class="header-right">
                        <ul class="list-unstyled list-inline">
                         
                            <li sec:authorize="${!isAuthenticated()}"><a href="#" th:href="@{/login}"><i class="fa fa-user"></i> Login</a></li>
                            <li sec:authorize="${!isAuthenticated()}"><a href="#" th:href="@{/register}"><i class="fa fa-user"></i> Register</a></li>
                            <li sec:authorize="${isAuthenticated()}"><a href="#" th:href="@{/myAccount}"><i class="fa fa-user"></i> My Account</a></li>
                            <li sec:authorize="${isAuthenticated()}"><a href="#" th:href="@{/logout}"><i class="fa fa-user"></i> Logout</a></li>
                        </ul>
                    </div>
                </div>
            </div>
        </div>
    </div>
    
    <div class="site-branding-area">
        <div class="container">
            <div class="row">
                <div class="col-sm-6">
                    <div class="logo">
                        <h1><a href="#"><img src="assets/img/quilcart.png" 
            th:src="@{/assets/img/quilcart.png}" /></a></h1>
                    </div>
                </div>
                
                <div class="col-sm-6">
                 
                    <div class="shopping-item">
                        <a href="#" th:href="@{/cart}">Cart <i class="fa fa-shopping-cart"></i> 
      <span id="cart-item-count" class="product-count">(0)</span></a>
                    </div>
                </div>
            </div>
        </div>
    </div>
    
    <div class="mainmenu-area">
        <div class="container">
            <div class="row">
                <div class="navbar-header">
                    <button type="button" class="navbar-toggle" 
       data-toggle="collapse" data-target=".navbar-collapse">
                        <span class="sr-only">Toggle navigation</span>
                        <span class="icon-bar"></span>
                        <span class="icon-bar"></span>
                        <span class="icon-bar"></span>
                    </button>
                </div> 
                <div class="navbar-collapse collapse">
                    <ul class="nav navbar-nav">
                        <li class="active"><a href="#" th:href="@{/}">Home</a></li>
                       <!--  <li><a th:href="@{/}">New Arrivals</a></li>
                        <li><a th:href="@{/}">Best Sellers</a></li> -->
                        
                    </ul>
                    <form class="navbar-form navbar-right" action="#" th:action="@{/products}">
               <input type="text" name="q" placeholder="Search products..."/>
                        <input type="submit" value="Search"/>
             </form>
                </div>  
            </div>
        </div>
    </div>
    
    <div class="product-big-title-area">
        <div class="container">
            <div class="row">
                <div class="col-md-12">
                    <div class="product-bit-title text-center">
                        <h2>Shop</h2>
                    </div>
                </div>
            </div>
        </div>
    </div>
 
    <div layout:fragment="content">
     <p>Main Content here....</p>
    </div>
       
   
    <script src="https://code.jquery.com/jquery.min.js"></script>
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/js/bootstrap.min.js"></script>
    <script th:src="@{'/assets/js/owl.carousel.min.js'}"></script>
    <script th:src="@{'/assets/js/jquery.sticky.js'}"></script>
    <script th:src="@{'/assets/js/jquery.easing.1.3.min.js'}"></script>
    <script th:src="@{'/assets/js/main.js'}"></script>   
    <script th:src="@{'/assets/js/app.js'}"></script>   
    
    </body>
</html>

Now update our home.html template to use the layout as follows:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" 
   xmlns:th="http://www.thymeleaf.org"
   xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3"
      layout:decorator="layout/mainLayout">
      
      <head>
        <title>Home</title>
    </head>
    <body>
     <div layout:fragment="content">
      <h3>Welcome to QuilCart</h3>
     </div>
     
    </body>
    
</html>

Now run the application and point your browser to https://localhost:8443/home and you should be able to see the home page with all the layout content as well.

JCart : Initial code setup for ShoppingCart

First we will start with setting up the initial code using SpringBoot. We have already discussed in JCart: Initial Code SetUp  article about creating a maven module jcart-site which will be our ShoppingCart application.

In that article we have shown what springboot dependencies to add as well. Just to recap we will be using SpringBoot, SpringMVC, Thymeleaf, JPA for our ShoppingCart application. jcart-site/pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" 
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
 http://maven.apache.org/xsd/maven-4.0.0.xsd">
 <modelVersion>4.0.0</modelVersion>
 <parent>
  <groupId>com.sivalabs</groupId>
  <artifactId>jcart</artifactId>
  <version>1.0</version>
 </parent>
 <artifactId>jcart-site</artifactId>

 <build>
  <plugins>
   <plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
   </plugin>
  </plugins>
 </build>

 <dependencies>
  <dependency>
   <groupId>com.sivalabs</groupId>
   <artifactId>jcart-core</artifactId>
   <version>${project.version}</version>
  </dependency>
  <dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-web</artifactId>
  </dependency>
  <dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-thymeleaf</artifactId>
  </dependency>
  <dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-security</artifactId>
  </dependency>
  <dependency>
        <groupId>org.thymeleaf.extras</groupId>
        <artifactId>thymeleaf-extras-springsecurity4</artifactId>
     </dependency>
  <dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-test</artifactId>
   <scope>test</scope>
  </dependency>  
  <dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-devtools</artifactId>
   <optional>true</optional>
  </dependency>
  <dependency>
   <groupId>commons-io</groupId>
   <artifactId>commons-io</artifactId>
   <version>2.3</version>
  </dependency>
 </dependencies>
</project>

For our JCart - Admin application we have used AdminLTE (https://almsaeedstudio.com/preview) theme which is based on Bootstrap with nice coloring scheme. It looks good for Administration kind of applications but not so good for a public facing e-commerce application, IMHO.

As I said earlier I am not a UI designer, so again I have googled for free e-commerce templates and found Ustora HTML5 ECommerce Template (https://www.freshdesignweb.com/ustora/) which looks good to me. So we will be using this template for our ShoppingCart screens.

Configuring HTTPS/SSL

As we discussed in JCart-Admin Configuring HTTPS/SSL post we will generate the keystore file and copy it to jcart-site/src/main/resources/ directory.

Configure the SSL related configuration properties in jcart-site/src/main/resources/application-default.properties

server.port=8443
server.ssl.key-store=classpath:jcartsitekeystore.p12
server.ssl.key-store-password=jcartsite
server.ssl.keyStoreType=PKCS12
server.ssl.keyAlias=jcartsitetomcat

WebMVC Configuration

We will create com.sivalabs.jcart.site.config.WebConfig.java for configuring SpringMVC components like ViewControllers, Interceptors, TemplateResolvers, SpringSecurityDialect and EmbeddedTomcatConnector as follows:

@Configuration
public class WebConfig extends WebMvcConfigurerAdapter
{

 @Value("${server.port:8443}") private int serverPort;
 
 @Autowired
    private MessageSource messageSource;

    @Override
    public Validator getValidator() {
        LocalValidatorFactoryBean factory = new LocalValidatorFactoryBean();
        factory.setValidationMessageSource(messageSource);
        return factory;
    }
    
 @Override
 public void addViewControllers(ViewControllerRegistry registry)
 {
  super.addViewControllers(registry);
        registry.addViewController("/login").setViewName("login");
        registry.addViewController("/register").setViewName("register");
  registry.addRedirectViewController("/", "/home");
  
 }
 
 @Override
 public void addInterceptors(InterceptorRegistry registry)
 {
  super.addInterceptors(registry);
 }

 @Bean 
    public ClassLoaderTemplateResolver emailTemplateResolver(){ 
  ClassLoaderTemplateResolver emailTemplateResolver = new ClassLoaderTemplateResolver(); 
  emailTemplateResolver.setPrefix("email-templates/"); 
  emailTemplateResolver.setSuffix(".html"); 
  emailTemplateResolver.setTemplateMode("HTML5"); 
  emailTemplateResolver.setCharacterEncoding("UTF-8"); 
  emailTemplateResolver.setOrder(2);
  
  return emailTemplateResolver; 
    }
 
 @Bean
 public SpringSecurityDialect securityDialect() {
     return new SpringSecurityDialect();
 }

 @Bean
 public EmbeddedServletContainerFactory servletContainer() {
  TomcatEmbeddedServletContainerFactory tomcat = new TomcatEmbeddedServletContainerFactory() {
   @Override
   protected void postProcessContext(Context context) {
    SecurityConstraint securityConstraint = new SecurityConstraint();
    securityConstraint.setUserConstraint("CONFIDENTIAL");
    SecurityCollection collection = new SecurityCollection();
    collection.addPattern("/*");
    securityConstraint.addCollection(collection);
    context.addConstraint(securityConstraint);
   }
  };

  tomcat.addAdditionalTomcatConnectors(initiateHttpConnector());
  return tomcat;
 }

 private Connector initiateHttpConnector() {
  Connector connector = new Connector("org.apache.coyote.http11.Http11NioProtocol");
  connector.setScheme("http");
  connector.setPort(8080);
  connector.setSecure(false);
  connector.setRedirectPort(serverPort);

  return connector;
 }
}

Note that for ShoppingCart application we will be running embedded tomcat server on https://host:8443 and call to http://host:8080 port will be redirected to https://host:8443.

Configuring SpringSecurity

As it is a public facing e-commerce site customers can browse through catalog products and add products to cart without requiring to login. But in order to checkout we will redirect the customer to login if he/she is not already loggedin. Also there are some URL that we would like to protect like customer's MyAccount page, Order History page etc.

So, for ShoppingCart application, Customer would become the user.

Create SpringSecurity User Wrapper AuthenticatedUser

package com.sivalabs.jcart.admin.security;

import java.util.Collection;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import com.sivalabs.jcart.entities.Customer;

public class AuthenticatedUser extends org.springframework.security.core.userdetails.User
{

 private static final long serialVersionUID = 1L;
 private Customer customer;
 
 public AuthenticatedUser(Customer customer)
 {
  super(customer.getEmail(), customer.getPassword(), getAuthorities(customer));
  this.customer = customer;
 }
 public Customer getCustomer()
 {
  return customer;
 }
 private static Collection<? extends GrantedAuthority> getAuthorities(Customer customer)
 {
  Collection<GrantedAuthority> authorities = AuthorityUtils.createAuthorityList("ROLE_USER");
  return authorities;
 }
}

Create Spring Data JPA Repository for Customer Entity 
jcart-core/src/main/java/com/sivalabs/jcart/customers/CustomerRepository.java as follows:

public interface CustomerRepository extends JpaRepository<Customer, Integer> {
 Customer findByEmail(String email);
}

Create jcart-core/src/main/java/com/sivalabs/jcart/customers/CustomerService.java to implement all Customer related operations.

@Service
@Transactional
public class CustomerService 
{
 @Autowired 
 private CustomerRepository customerRepository;
 
 public Customer getCustomerByEmail(String email) {
  return customerRepository.findByEmail(email);
 }
 
}

Create com.sivalabs.jcart.admin.security.CustomUserDetailsService.java which implements SpringSecurity's UserDetailsService.

@Service
@Transactional
public class CustomUserDetailsService implements UserDetailsService
{
 @Autowired CustomerService customerService;
 
 @Override
 public UserDetails loadUserByUsername(String email)
   throws UsernameNotFoundException {
  Customer customer = customerService.getCustomerByEmail(email);
  if(customer == null){
   throw new UsernameNotFoundException("Email "+email+" not found");
  }
  return new AuthenticatedUser(customer);
 }

}

Create com.sivalabs.jcart.site.security.WebSecurityConfig.java to configure SpringSecurity configuration.

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled = true, proxyTargetClass = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
 
 @Autowired
 private UserDetailsService customUserDetailsService;
 
 @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
 
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
         .csrf().disable()
            .authorizeRequests()
             .antMatchers("/resources/**", "/webjars/**","/assets/**").permitAll()
                .antMatchers("/", "/register", "/forgotPwd","/resetPwd").permitAll()
                .antMatchers("/myAccount","/checkout","/orders").authenticated()
                .and()
            .formLogin()
                .loginPage("/login")
                .defaultSuccessUrl("/home")
                .failureUrl("/login?error")
                .permitAll()
                .and()
            .logout()
             .logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
             .permitAll()
                .and()
            .exceptionHandling().accessDeniedPage("/403");
    }

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        auth
         .userDetailsService(customUserDetailsService)
         .passwordEncoder(passwordEncoder());
    }
}

Finally, let us create a simple HomeController to handle /home request and render home.html view.

@Controller
public class HomeController
{  
 @RequestMapping("/home")
 public String home(Model model)
 {
  return "home";
 } 
}

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" 
   xmlns:th="http://www.thymeleaf.org">
   
<head>
    <meta charset="utf-8"/>
    <meta http-equiv="X-UA-Compatible" content="IE=edge"/>
    <meta name="viewport" content="width=device-width, initial-scale=1"/>
    <title>QuilCart</title>
    
</head>
<body>
   <h3>Welcome to QuilCart</h3>     
</body>
</html>

Now run the application and point your browser to http://localhost:8080. It should automatically redirect you to https://localhost:8443/home and show home.html view.

In our next post we will setup the UI Layout using Thymeleaf templates and start developing screens.