Tìm hiểu về Layout trong Thymeleaf
Đăng lúc: 10:58 AM - 25/03/2025 bởi Charles Chung - 230Trong bài này tôi sẽ hướng dẫn các bạn tạo tạo các fragment và layout với thymeleaf để xây dựng bố cục trang web

1. Giới thiệu về layout
Trong Thymeleaf, layout là một khái niệm được sử dụng để giúp tái sử dụng các phần giao diện web, như tiêu đề, chân trang, menu, v.v. Thay vì phải lặp lại mã HTML trong mỗi trang, chúng ta có thể tạo một layout chung và sử dụng nó cho nhiều trang khác nhau. Điều này giúp giảm thiểu mã lặp lại và quản lý giao diện dễ dàng hơn. Thymeleaf hỗ trợ việc xây dựng layout thông qua các layout fragments và thư viện layout.
2. Fragments
Fragment là các phần giao diện có thể tái sử dụng, ví dụ như phần tiêu đề, thanh điều hướng, hoặc chân trang. Các fragment này được định nghĩa trong một tệp HTML riêng biệt và có thể được sử dụng trong các tệp HTML khác.
3. Layout Dialect
Thymeleaf có một thư viện hỗ trợ layout gọi là Thymeleaf Layout Dialect. Thư viện này giúp bạn dễ dàng tạo ra layout và sử dụng chúng trong các trang khác nhau. Để cài đặt Thymeleaf Layout Dialect bạn cần thêm thư viện thymeleaf-layout-dialect vào tệp pom.xml nếu bạn đang sử dụng Maven:
1 2 3 4 |
<dependency> <groupId>nz.net.ultraq.thymeleaf</groupId> <artifactId>thymeleaf-layout-dialect</artifactId> </dependency> |
Khai báo các namespace layout trong các trang
1 |
<html xmlns:layout=http://www.ultrap.net.nz/thymeleaf/layout> |
Chỉ định đoạn sẽ được thay thế nội dung trong trang layout
1 |
<div layout:fragment="content"></div> |
Chỉ định tệp layout sẽ nhận nội dung từ trang con bất kỳ và phần sẽ thay vào trang layout, lưu ý tên phải trùng với tên định nghĩa ở trang layout.
1 2 3 4 5 |
<html xmlns:layout="http://www.ultrap.net.nz/thymeleaf/layout" layout:decorate="~{path_file_layout}"> <div layout:fragment="content"> <h1>Sản phẩm</h1> </div> |
4. Ví dụ 1
Trong ví dụ này chúng ta sẽ xây dựng layout có bố cục như sau
Bước 1: Tạo project springboot session7example1, lưu ý bổ sung thêm dependency dialect layout vào, project có cấu trúc như dưới đây
Bước 2: Code cho HomeController
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
package com.bkap.controller; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; @Controller public class HomeController { @GetMapping({"/","/home"}) public String index(Model model) { return "home/index"; } @GetMapping({"/about"}) public String about(Model model) { return "home/about"; } @GetMapping({"/contact"}) public String contact(Model model) { return "home/contact"; } @GetMapping({"/product"}) public String product(Model model) { return "home/product"; } } |
Bước 3: Code cho view home_layout.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
<!DOCTYPE html> <html xmlns:layout="http://www.ultrap.net.nz/thymeleaf/layout" xmlns:th="http://thymeleaf.org"> <head> <meta charset="UTF-8"> <title layout:title-pattern="$CONTENT_TITLE | $LAYOUT_TITLE">Bách Khoa Aptech</title> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/css/bootstrap.min.css"> <style> .slideshow { width: 100%; height: 120px; background: red; color: white; } </style> </head> <body> <div class="container mt-3"> <div th:replace="~{fragments/home_fragments::header}"> <h2>Phần menu</h2> </div> <hr /> <div th:replace="~{fragments/home_fragments::slideshow}"> <h2>Phần Album ảnh</h2> </div> <hr /> <div layout:fragment="content"></div> <hr /> <div th:replace="~{fragments/home_fragments::footer}"> <h2>Phần chân trang</h2> </div> </div> </body> </html> |
Bước 4: Code cho view index.html
1 2 3 4 5 6 7 8 9 10 11 12 |
<!DOCTYPE html> <html xmlns:layout="http://www.ultrap.net.nz/thymeleaf/layout" layout:decorate="~{home_layout}"> <head> <meta charset="UTF-8"> <title>Home</title> </head> <body> <div layout:fragment="content"> <h1>Trang chủ</h1> </div> </body> </html> |
Bước 5: Code cho view about.html
1 2 3 4 5 6 7 8 9 10 11 12 |
<!DOCTYPE html> <html xmlns:layout="http://www.ultrap.net.nz/thymeleaf/layout" layout:decorate="~{home_layout}"> <head> <meta charset="UTF-8"> <title>Giới thiệu</title> </head> <body> <div layout:fragment="content"> <h1>Giới thiệu</h1> </div> </body> </html> |
Bước 6: Code cho view contact.html
1 2 3 4 5 6 7 8 9 10 11 12 |
<!DOCTYPE html> <html xmlns:layout="http://www.ultrap.net.nz/thymeleaf/layout" layout:decorate="~{home_layout}"> <head> <meta charset="UTF-8"> <title>Liên hệ nha</title> </head> <body> <div layout:fragment="content"> <h1>Liên hệ</h1> </div> </body> </html> |
Bước 7: Code cho view product.html
1 2 3 4 5 6 7 8 9 10 11 12 |
<!DOCTYPE html> <html xmlns:layout="http://www.ultrap.net.nz/thymeleaf/layout" layout:decorate="~{home_layout}"> <head> <meta charset="UTF-8"> <title>Sản phẩm</title> </head> <body> <div layout:fragment="content"> <h1>Sản phẩm</h1> </div> </body> </html> |
Bước 8: Code cho view fragments/home_fragments.html
1 2 3 4 5 6 7 8 9 |
<div th:fragment="header"> <a href="/">Trang chủ</a> | <a href="/about">Giới thiệu</a> | <a href="/contact">Liên hệ</a> | <a href="/product">Sản phẩm</a> </div> <div th:fragment="slideshow" class="slideshow"> <h3>Album Ảnh</h3> </div> <div th:fragment="footer">Copyri |
Bước 9: Chạy để xem kết quả
Source code tải tại đây
5. Video demo (quay trong buổi dạy thực tế)
6. Ví dụ 2
Trong ví dụ này chúng ta sẽ xây dựng layout trang chủ dựa trên template có sẵn, các vùng được chia ra thành các fragment như ảnh bên dưới (template tải tại đây)
Ngoài ra các thành phần khác ẩn chưa xuất hiện cũng sẽ được phân tách thành các fragments, các trang con cũng phân tách riêng biệt thành các view (.html) xem cấu trúc hình dưới
Tiếp theo, mở tệp pom.xml bổ sung thêm dependency layout của thymeleaf
1 2 3 4 5 |
<!-- https://mvnrepository.com/artifact/nz.net.ultraq.thymeleaf/thymeleaf-layout-dialect --> <dependency> <groupId>nz.net.ultraq.thymeleaf</groupId> <artifactId>thymeleaf-layout-dialect</artifactId> </dependency> |
Tiếp theo, tạo lớp HomeController và code như sau
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
package com.bkap.controller; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; @Controller public class HomeController { @GetMapping({"/","/trang-chu"}) public String home(Model model) { return "home/index"; } @GetMapping("/about-us") public String about(Model model) { return "home/about"; } @GetMapping("/contact-us") public String contact(Model model) { return "home/contact"; } @GetMapping("/shop") public String shop(Model model) { return "home/shop"; } @GetMapping("/blog") public String blog(Model model) { return "home/blog"; } @GetMapping("/product") public String product(Model model) { return "home/product"; } } |
Tiếp theo, tạo các tệp view như cấu trúc trên
Tiếp theo, mở tệp home2-default.html của template ra rồi tìm các phần html tương ứng để đưa vào các trang như đã phân tác ở trên
Sau đây là một số đoạn code cần định nghĩa trong các fragment và layout
Code cho promotion_bar.html
1 2 3 4 |
<div th:fragment="promotion_bar" class="notification-bar mobilehide"> <a href="#" class="notification-bar__message">20% off your very first purchase, use promo code: belle fashion</a> <span class="close-announcement">×</span> </div> |
Code cho search_bar.html
1 2 3 4 5 6 7 8 9 |
<div th:fragment="search_bar" class="search"> <div class="search__form"> <form class="search-bar__form" action="#"> <button class="go-btn search__button" type="submit"><i class="icon anm anm-search-l"></i></button> <input class="search__input" type="search" name="q" value="" placeholder="Search entire store..." aria-label="Search" autocomplete="off"> </form> <button type="button" class="search-trigger close-btn"><i class="anm anm-times-l"></i></button> </div> </div> |
Code cho top_bar.html
1 2 3 |
<div th:fragment="top_bar" class="top-header"> code lấy từ template </div> |
Code cho menu_mobile.html
1 2 3 |
<div th:fragment="menu_mobile" class="mobile-nav-wrapper" role="navigation"> Code lấy từ template </div> |
Code cho nav_bar.html
1 2 3 |
<div th:fragment="nav_bar" class="header-wrap animated d-flex border-bottom"> code lấy từ template </div> |
Code cho slide_show.html
1 2 3 4 5 |
<div th:fragment="slide_show" class="slideshow slideshow-wrapper pb-section"> <div class="home-slideshow"> code lấy từ template </div> </div> |
Code cho weekly_bestseller.html
1 2 3 |
<div th:fragment="weekly_bestseller" class="section"> code lấy từ template </div> |
Code cho footer.html
1 2 3 |
<footer th:fragment="footer" id="footer" class="footer-2"> code lấy từ template </div> |
Code cho index.html
1 2 3 4 5 6 7 8 9 10 11 12 13 |
<!DOCTYPE html> <html xmlns:layout="http://www.ultrap.net.nz/thymeleaf/layout" layout:decorate="home_layout"> <head> <meta charset="UTF-8"> <title>Trang chủ</title> </head> <body> <div layout:fragment="page_content"> <div th:replace="~{home/fragments/slide_show::slide_show}"></div> <div th:replace="~{home/fragments/weekly_bestseller::weekly_bestseller}"></div> </div> </body> </html> |
Code cho about.html
1 2 3 4 5 6 7 8 9 10 11 12 |
<!DOCTYPE html> <html xmlns:layout="http://www.ultrap.net.nz/thymeleaf/layout" layout:decorate="home_layout"> <head> <meta charset="UTF-8"> <title>Giới thiệu về công ty</title> </head> <body> <div layout:fragment="page_content"> code lấy từ template vào </div> </body> </html> |
Code cho blog.html
1 2 3 4 5 6 7 8 9 10 11 12 |
<!DOCTYPE html> <html xmlns:layout="http://www.ultrap.net.nz/thymeleaf/layout" layout:decorate="home_layout"> <head> <meta charset="UTF-8"> <title>Blog</title> </head> <body> <div layout:fragment="page_content"> Code lấy từ template vào </div> </body> </html> |
Code cho contact.html
1 2 3 4 5 6 7 8 9 10 11 12 |
<!DOCTYPE html> <html xmlns:layout="http://www.ultrap.net.nz/thymeleaf/layout" layout:decorate="home_layout"> <head> <meta charset="UTF-8"> <title>Liên hệ</title> </head> <body> <div layout:fragment="page_content"> Code lấy từ template vào </div> </body> </html> |
Code cho product.html
1 2 3 4 5 6 7 8 9 10 11 12 |
<!DOCTYPE html> <html xmlns:layout="http://www.ultrap.net.nz/thymeleaf/layout" layout:decorate="home_layout"> <head> <meta charset="UTF-8"> <title>Trang sản phẩm</title> </head> <body> <div layout:fragment="page_content"> Code lấy từ template vào </div> </body> </html> |
Code cho shop.html
1 2 3 4 5 6 7 8 9 10 11 12 |
<!DOCTYPE html> <html xmlns:layout="http://www.ultrap.net.nz/thymeleaf/layout" layout:decorate="home_layout"> <head> <meta charset="UTF-8"> <title>Shop</title> </head> <body> <div layout:fragment="page_content"> Code lấy từ template vào </div> </body> </html> |
Code cho home_layout.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 |
<!DOCTYPE html> <html class="no-js" lang="en" xmlns:th="http://thymeleaf.org" xmlns:layout="http://www.ultrap.net.nz/thymeleaf/layout"> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <meta http-equiv="x-ua-compatible" content="ie=edge"> <title layout:title-pattern="$CONTENT_TITLE | $LAYOUT_TITLE">Bách Khoa Aptech</title> <meta name="description" content="description"> <meta name="viewport" content="width=device-width, initial-scale=1"> <!-- Favicon --> <link rel="shortcut icon" href="/assets/images/favicon.png" /> <!-- Plugins CSS --> <link rel="stylesheet" href="/assets/css/plugins.css"> <!-- Bootstap CSS --> <link rel="stylesheet" href="/assets/css/bootstrap.min.css"> <!-- Main Style CSS --> <link rel="stylesheet" href="/assets/css/style.css"> <link rel="stylesheet" href="/assets/css/responsive.css"> </head> <body class="template-index home2-default"> <div id="pre-loader"> <img src="/assets/images/loader.gif" alt="Loading..." /> </div> <div class="pageWrapper"> <!--Promotion Bar--> <div th:replace="~{home/fragments/promotion_bar::promotion_bar}"> Promotion Bar </div> <!--End Promotion Bar--> <!--Search Form Drawer--> <div th:replace="~{home/fragments/search_bar::search_bar}"> Search bar </div> <!--End Search Form Drawer--> <!--Top Header--> <div th:replace="~{home/fragments/top_bar::top_bar}"> Top bar </div> <!--End Top Header--> <!--Header--> <div th:replace="~{home/fragments/nav_bar::nav_bar}"> Menu logo </div> <!--End Header--> <!--Mobile Menu--> <div th:replace="~{home/fragments/menu_mobile::menu_mobile}"> Mobile </div> <!--End Mobile Menu--> <!--Body Content--> <div id="page-content" layout:fragment="page_content"> Nội dung </div> <!--End Body Content--> <!--Footer--> <div th:replace="~{home/fragments/footer::footer}"> Phần Footer </div> <!--End Footer--> <!--Scoll Top--> <span id="site-scroll"><i class="icon anm anm-angle-up-r"></i></span> <!--End Scoll Top--> <!--Quick View popup--> <div class="modal fade quick-view-popup" id="content_quickview"> code lấy từ template vào </div> <!--End Quick View popup--> <!-- Newsletter Popup --> <div class="newsletter-wrap" id="popup-container"> code lấy từ template vào </div> <!-- End Newsletter Popup --> <!-- Including Jquery --> <script src="/assets/js/vendor/jquery-3.3.1.min.js"></script> <script src="/assets/js/vendor/modernizr-3.6.0.min.js"></script> <script src="/assets/js/vendor/jquery.cookie.js"></script> <script src="/assets/js/vendor/wow.min.js"></script> <!-- Including Javascript --> <script src="/assets/js/bootstrap.min.js"></script> <script src="/assets/js/plugins.js"></script> <script src="/assets/js/popper.min.js"></script> <script src="/assets/js/lazysizes.js"></script> <script src="/assets/js/main.js"></script> <!--For Newsletter Popup--> <script> //code lấy từ template vào </script> <!--End For Newsletter Popup--> </div> </body> <!-- belle/home2-default.html --> </html> |
Source code tải tại đây
7. Video đọc dữ liệu lên trang chủ (quay trong buổi dạy thực tế)
8. Ví dụ 3
Trong ví dụ này chúng ta sẽ sử dụng source code ở ví dụ trên (dữ liệu mẫu tải tại đây) thực hiện các thao tác đọc dữ liệu lên trang index, shop, cấu trúc project như sau
Sau đây là một số đoạn code gợi ý
Code lớp Category (biểu diễn loại sản phẩm)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 |
package com.bkap.entities; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import jakarta.persistence.CascadeType; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; import jakarta.persistence.OneToMany; import jakarta.persistence.Table; @Table(name="categories") @Entity public class Category { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name="categoryid") private int categoryId; @Column(name="categoryname",unique = true, columnDefinition = "nvarchar(100)") private String categoryName; private boolean status; @ManyToOne @JoinColumn(name = "parentid") private Category parent; @OneToMany(mappedBy = "parent", cascade = CascadeType.ALL, orphanRemoval = true) private List<Category> subcategories = new ArrayList<>(); @OneToMany(mappedBy = "category") private Set<Product> products=new HashSet<Product>(); public Category getParent() { return parent; } public void setParent(Category parent) { this.parent = parent; } public List<Category> getSubcategories() { return subcategories; } public void setSubcategories(List<Category> subcategories) { this.subcategories = subcategories; } public Set<Product> getProducts() { return products; } public int getCategoryId() { return categoryId; } public void setCategoryId(int categoryId) { this.categoryId = categoryId; } public String getCategoryName() { return categoryName; } public void setCategoryName(String categoryName) { this.categoryName = categoryName; } public boolean isStatus() { return status; } public void setStatus(boolean status) { this.status = status; } } |
Code lớp Product (biểu diễn sản phẩm)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 |
package com.bkap.entities; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.Id; import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; import jakarta.persistence.Table; @Table(name="products") @Entity public class Product { @Id @Column(name="productid",length = 10) private String productId; @Column(name="productname",columnDefinition = "nvarchar(200)") private String productName; @Column(name="priceold") private int priceOld; private int price; @Column(name="colors",columnDefinition = "nvarchar(200)") private String colors; @Column(name="sizes",columnDefinition = "nvarchar(200)") private String sizes; @Column(name="pictures",columnDefinition = "nvarchar(500)") private String pictures; @Column(name="brief",columnDefinition = "nvarchar(1000)") private String brief; @Column(name="description",columnDefinition = "nvarchar(max)") private String description; private boolean status; @ManyToOne @JoinColumn(name = "categoryid",nullable = false) private Category category=new Category(); public String getProductId() { return productId; } public void setProductId(String productId) { this.productId = productId; } public String getProductName() { return productName; } public void setProductName(String productName) { this.productName = productName; } public int getPriceOld() { return priceOld; } public void setPriceOld(int priceOld) { this.priceOld = priceOld; } public int getPrice() { return price; } public void setPrice(int price) { this.price = price; } public String getColors() { return colors; } public void setColors(String colors) { this.colors = colors; } public String getSizes() { return sizes; } public void setSizes(String sizes) { this.sizes = sizes; } public String getPictures() { return pictures; } public void setPictures(String pictures) { this.pictures = pictures; } public String getBrief() { return brief; } public void setBrief(String brief) { this.brief = brief; } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } public boolean isStatus() { return status; } public void setStatus(boolean status) { this.status = status; } public Category getCategory() { return category; } public String[] getListPictures() { if (this.pictures!=null) return pictures.split(","); else return new String[0]; } } |
Code lớp SlideImage (biểu diễn hình anh slideshow)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 |
package com.bkap.entities; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import jakarta.persistence.Table; @Table(name="slideimages") @Entity public class SlideImage { @Id @GeneratedValue(strategy =GenerationType.IDENTITY) @Column(name="slideid") private int slideId; @Column(name="picture", columnDefinition = "nvarchar(500)") private String picture; @Column(name="title", columnDefinition = "nvarchar(500)") private String title; @Column(name="brief", columnDefinition = "nvarchar(1000)") private String brief; @Column(name="link", columnDefinition = "nvarchar(500)") private String link; private boolean status; public SlideImage() { // TODO Auto-generated constructor stub } public SlideImage(int slideId, String picture, String title, String brief, String link, boolean status) { super(); this.slideId = slideId; this.picture = picture; this.title = title; this.brief = brief; this.link = link; this.status = status; } public SlideImage(String picture, String title, String brief, String link, boolean status) { super(); this.picture = picture; this.title = title; this.brief = brief; this.link = link; this.status = status; } public int getSlideId() { return slideId; } public void setSlideId(int slideId) { this.slideId = slideId; } public String getPicture() { return picture; } public void setPicture(String picture) { this.picture = picture; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public String getBrief() { return brief; } public void setBrief(String brief) { this.brief = brief; } public String getLink() { return link; } public void setLink(String link) { this.link = link; } public boolean isStatus() { return status; } public void setStatus(boolean status) { this.status = status; } } |
Code interface CategoryRepository (xử lý các nghiệp vụ của Category kế thừa từ JPA)
1 2 3 4 5 6 7 8 9 10 |
package com.bkap.repositories; import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; import com.bkap.entities.Category; public interface CategoryRepository extends JpaRepository<Category, Integer> { public List<Category> findByParentNull(); } |
Code interface ProductRepository (xử lý các nghiệp vụ của Product kế thừa từ JPA)
1 2 3 4 5 6 7 8 9 10 11 12 |
package com.bkap.repositories; import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; import com.bkap.entities.Product; public interface ProductRepository extends JpaRepository<Product,String> { public List<Product> findTop5ByOrderByProductIdAsc(); public List<Product> findByProductNameContaining(String name); } |
Code interface SlideImageRepository (xử lý các nghiệp vụ của SlideImage kế thừa từ JPA)
1 2 3 4 5 6 7 8 9 |
package com.bkap.repositories; import org.springframework.data.jpa.repository.JpaRepository; import com.bkap.entities.SlideImage; public interface SlideImageRepository extends JpaRepository<SlideImage, Integer> { } |
Code lớp CategoryService (xử lý các nghiệp vụ gọi từ Repository)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
package com.bkap.services; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import com.bkap.entities.Category; import com.bkap.repositories.CategoryRepository; @Service public class CategoryService { @Autowired CategoryRepository repository; public List<Category> getAll(){ return repository.findByParentNull(); } } |
Code lớp ProductService (xử lý các nghiệp vụ gọi từ Repository)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
package com.bkap.services; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import com.bkap.entities.Product; import com.bkap.repositories.ProductRepository; @Service public class ProductService { @Autowired ProductRepository productRepository; public List<Product> getTop5(){ return productRepository.findTop5ByOrderByProductIdAsc(); } public List<Product> getAll(){ return productRepository.findAll(); } } |
Code lớp SlideImageService (xử lý các nghiệp vụ gọi từ Repository)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
package com.bkap.services; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import com.bkap.entities.SlideImage; import com.bkap.repositories.SlideImageRepository; @Service public class SlideImageService { @Autowired SlideImageRepository slideImageRepository; public List<SlideImage> getAll(){ return slideImageRepository.findAll(); } } |
Code lớp HomeController
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
package com.bkap.controller; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; import com.bkap.services.CategoryService; import com.bkap.services.ProductService; import com.bkap.services.SlideImageService; @Controller public class HomeController { @Autowired SlideImageService slideImageService; @Autowired ProductService productService; @Autowired CategoryService categoryService; @GetMapping({"/","/trang-chu"}) public String home(Model model) { model.addAttribute("slideshows", slideImageService.getAll()); model.addAttribute("top5", productService.getTop5()); return "home/index"; } @GetMapping("/about-us") public String about(Model model) { return "home/about"; } @GetMapping("/contact-us") public String contact(Model model) { return "home/contact"; } @GetMapping("/shop") public String shop(Model model) { model.addAttribute("products", productService.getAll()); model.addAttribute("categories", categoryService.getAll()); return "home/shop"; } @GetMapping("/blog") public String blog(Model model) { return "home/blog"; } @GetMapping("/product") public String product(Model model) { return "home/product"; } } |
Code lớp GlobalFragmentController (đọc dữ liệu toàn cục mỗi khi có request)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
package com.bkap.controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ModelAttribute; @ControllerAdvice public class GlobalFragmentController { /* * phương thức này sẽ được load mỗi khi có request bất kỳ, * và dữ liệu nằm trong biến contact có thể dùng bất cứ view nào */ @ModelAttribute("contact") public String loadInfo(Model model) { return "Charles Chung: 0968018161"; } } |
Code hiển thị dữ liệu lên các fragment và layout
Đọc dữ liệu toàn cục trong biến contact lên footer.html
1 2 3 4 5 6 7 8 |
<div class="col-12 col-sm-12 col-md-3 col-lg-3 contact-box"> <h4 class="h4">Contact Us</h4> <ul class="addressFooter"> <li><i class="icon anm anm-map-marker-al"></i><p>55 Gallaxy Enque,<br>2568 steet, 23568 NY</p></li> <li class="phone"><i class="icon anm anm-phone-s"></i><p>(440) 000 000 0000</p></li> <li class="email"><i class="icon anm anm-envelope-l"></i><p th:text=${contact}></p></li> </ul> </div> |
Truyền dữ liệu cho slide_show và weekly_bestsell qua index.html
1 2 3 4 5 6 7 8 9 10 11 12 13 |
<!DOCTYPE html> <html xmlns:layout="http://www.ultrap.net.nz/thymeleaf/layout" layout:decorate="home_layout"> <head> <meta charset="UTF-8"> <title>Trang chủ</title> </head> <body> <div layout:fragment="page_content"> <div th:replace="~{home/fragments/slide_show::slide_show(${slideshows})}"></div> <div th:replace="~{home/fragments/weekly_bestseller::weekly_bestseller(${top5})}"></div> </div> </body> </html> |
Đọc dữ liệu lên fragment slide_show.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
<div th:fragment="slide_show(slideshows)" class="slideshow slideshow-wrapper pb-section"> <div class="home-slideshow"> <th:block th:each="s:${slideshows}" th:object="${s}"> <div class="slide"> <div class="blur-up lazyload"> <img class="blur-up lazyload" th:data-src="*{picture}" th:src="*{picture}" alt="Shop Our New Collection" title="Shop Our New Collection" /> <div class="slideshow__text-wrap slideshow__overlay classic middle"> <div class="slideshow__text-content middle"> <div class="container"> <div class="wrap-caption center"> <h2 class="h1 mega-title slideshow__title" th:text="*{title}">Our New Collection</h2> <span class="mega-subtitle slideshow__subtitle" th:text="*{brief}">Save up to 50% Off</span> <span class="btn">Shop now</span> </div> </div> </div> </div> </div> </div> </th:block> </div> </div> |
Đọc dữ liệu lên fragment weekly_bestseller.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 |
<div th:fragment="weekly_bestseller(top5)" class="section"> <div class="container"> <div class="row"> <div class="col-12 col-sm-12 col-md-12 col-lg-12"> <div class="section-header text-center"> <h2 class="h2">Weekly Bestseller</h2> <p>Our most popular products based on sales</p> </div> <div class="productSlider grid-products"> <th:block th:each="p:${top5}" th:object="${p}"> <div class="col-12 item"> <!-- start product image --> <div class="product-image"> <!-- start product image --> <a href="product-layout-1.html" class="grid-view-item__link"> <!-- image --> <img class="primary blur-up lazyload" th:data-src="*{getListPictures().length>0?getListPictures()[0]:''}" th:src="*{getListPictures().length>0?getListPictures()[0]:''}" alt="image" title="product"> <!-- End image --> <!-- Hover image --> <img class="hover blur-up lazyload" th:data-src="*{getListPictures().length>1?getListPictures()[1]:''}" th:src="*{getListPictures().length>1?getListPictures()[1]:''}" alt="image" title="product"> <!-- End hover image --> <!-- Variant Image--> <img class="grid-view-item__image hover variantImg" th:src="*{getListPictures().length>0?getListPictures()[0]:''}" alt="image" title="product"> <!-- Variant Image--> <!-- product label --> <div class="product-labels rounded"> <span class="lbl on-sale">Sale</span> <span class="lbl pr-label1">new</span> </div> <!-- End product label --> </a> <!-- end product image --> <!-- countdown start --> <div class="saleTime desktop" data-countdown="2022/03/01"></div> <!-- countdown end --> <!-- Start product button --> <form class="variants add" action="#" onclick="window.location.href='cart.html'" method="post"> <button class="btn btn-addto-cart" type="button" tabindex="0">Add To Cart</button> </form> <div class="button-set"> <a href="javascript:void(0)" title="Quick View" class="quick-view-popup quick-view" data-toggle="modal" data-target="#content_quickview"> <i class="icon anm anm-search-plus-r"></i> </a> <div class="wishlist-btn"> <a class="wishlist add-to-wishlist" href="wishlist.html"> <i class="icon anm anm-heart-l"></i> </a> </div> <div class="compare-btn"> <a class="compare add-to-compare" href="compare.html" title="Add to Compare"> <i class="icon anm anm-random-r"></i> </a> </div> </div> <!-- end product button --> </div> <!-- end product image --> <!--start product details --> <div class="product-details text-center"> <!-- product name --> <div class="product-name"> <a href="product-layout-1.html" th:text="*{productName}"></a> </div> <!-- End product name --> <!-- product price --> <div class="product-price"> $<span class="old-price" th:text="*{priceOld}">$500.00</span> $<span th:text="*{price}" class="price">$600.00</span> </div> <!-- End product price --> <!-- Color Variant --> <ul class="swatches"> <li class="swatch small rounded navy" rel="assets/images/product-images/product-image-stw1.jpg"></li> <li class="swatch small rounded green" rel="assets/images/product-images/product-image-stw1-1.jpg"></li> <li class="swatch small rounded gray" rel="assets/images/product-images/product-image-stw1-2.jpg"></li> <li class="swatch small rounded aqua" rel="assets/images/product-images/product-image-stw1-3.jpg"></li> <li class="swatch small rounded orange" rel="assets/images/product-images/product-image-stw1-4.jpg"></li> </ul> <!-- End Variant --> </div> <!-- End product details --> </div> </th:block> </div> </div> </div> </div> </div> |
Đọc dữ liệu lên shop.html (gồm đoạn categories 2 cấp và đoạn sản phẩm)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 |
<!--Categories--> <div class="sidebar_widget categories filter-widget"> <div class="widget-title"> <h2>Categories</h2> </div> <div class="widget-content"> <ul class="sidebar_categories"> <th:block th:each="p:${categories}"> <th:block th:if="${p.subcategories.size()>0}"> <li class="level1 sub-level"><a href="#" class="site-nav" th:text="${p.categoryName}">Clothing</a> <ul class="sublinks"> <th:block th:each="c:${p.subcategories}"> <li class="level2"><a href="#;" class="site-nav" th:text="${c.categoryName}">child</a></li> </th:block> </ul> </li> </th:block> <th:block th:unless="${p.subcategories.size()>0}"> <li class="level1"> <a href="#" class="site-nav" th:text="${p.categoryName}">Clothing</a> </li> </th:block> </th:block> </ul> </div> </div> <!--Categories--> <!--Popular Products--> <div class="sidebar_widget"> <div class="widget-title"> <h2>Popular Products</h2> </div> <div class="widget-content"> <div class="list list-sidebar-products"> <div class="grid"> <div class="grid__item"> <div class="mini-list-item"> <div class="mini-view_image"> <a class="grid-view-item__link" href="#"> <img class="grid-view-item__image" src="assets/images/product-images/mini-product-img.jpg" alt="" /> </a> </div> <div class="details"> <a class="grid-view-item__title" href="#">Cena Skirt</a> <div class="grid-view-item__meta"> <span class="product-price__price"><span class="money">$173.60</span></span> </div> </div> </div> </div> <div class="grid__item"> <div class="mini-list-item"> <div class="mini-view_image"> <a class="grid-view-item__link" href="#"><img class="grid-view-item__image" src="assets/images/product-images/mini-product-img1.jpg" alt="" /></a> </div> <div class="details"> <a class="grid-view-item__title" href="#">Block Button Up</a> <div class="grid-view-item__meta"> <span class="product-price__price"><span class="money">$378.00</span></span> </div> </div> </div> </div> <div class="grid__item"> <div class="mini-list-item"> <div class="mini-view_image"> <a class="grid-view-item__link" href="#"><img class="grid-view-item__image" src="assets/images/product-images/mini-product-img2.jpg" alt="" /></a> </div> <div class="details"> <a class="grid-view-item__title" href="#">Balda Button Pant</a> <div class="grid-view-item__meta"> <span class="product-price__price"><span class="money">$278.60</span></span> </div> </div> </div> </div> <div class="grid__item"> <div class="mini-list-item"> <div class="mini-view_image"> <a class="grid-view-item__link" href="#"><img class="grid-view-item__image" src="assets/images/product-images/mini-product-img3.jpg" alt="" /></a> </div> <div class="details"> <a class="grid-view-item__title" href="#">Border Dress in Black/Silver</a> <div class="grid-view-item__meta"> <span class="product-price__price"><span class="money">$228.00</span></span> </div> </div> </div> </div> </div> </div> </div> </div> <!--End Popular Products--> |
Chạy và kiểm tra kết quả
Source code tải tại đây
Video quay trong buổi dạy
thay lời cảm ơn!
Các bài cũ hơn
- Truy xuất database sử dụng Spring Data JPA Phần 3 (03:29 PM - 24/03/2025)
- Truy xuất database sử dụng Spring Data JPA Phần 2 (02:14 PM - 23/03/2025)
- Truy xuất database sử dụng Spring Data JPA Phần 1 (02:14 PM - 20/03/2025)
- Validation Form-Upload File Image-Nhúng CKEditor trong SpringBoot (10:28 AM - 19/03/2025)
- Tìm hiểu về Form trong Thymeleaf (10:30 PM - 18/03/2025)