Khái niệm và vai trò của Fragment
Trong khuôn khổ phát triển giao diện web bằng Thymeleaf, Fragment đóng vai trò tương tự như các component khả dụng lại, cho phép bạn cô lập các khối HTML lặp lại thành các định nghĩa độc lập. Thay vì sao chép mã nguồn ở nhiều vị trí, bạn chỉ cần khai báo một lần và tham chiếu chúng khi cần, giúp cấu trúc template gọn gàng, giảm thiểu lỗi và dễ dàng bảo trì.
Khai báo Fragment
Để định nghĩa một fragment, bạn chỉ cần gán thuộc tính th:fragment cho bất kỳ phần tử HTML nào. Ví dụ, tạo file footer.html chứa thành phần chân trang:
<html xmlns:th="http://www.thymeleaf.org">
<footer th:fragment="footerComponent">
© 2024 Công ty Công nghệ. Bản quyền thuộc về chúng tôi.
</footer>
</html>
Các phương thức nhúng Fragment
Khi gọi fragment, Thymeleaf cung cấp ba thuộc tính với hành vi thay thế và giữ thẻ khác nhau:
th:insert: Giữ lại thẻ bao ngoài tại vị trí gọi, đồng thời giữ nguyên thẻ gốc của fragment.th:replace: Loại bỏ hoàn toàn thẻ bao ngoài tại vị trí gọi, thay thế bằng fragment (bao gồm cả thẻ gốc). Đây là cách được khuyến nghị sử dụng phổ biến nhất.th:include: Giữ lại thẻ bao ngoài tại vị trí gọi, nhưng chỉ lấy nội dung bên trong của fragment (loại bỏ thẻ gốc). Lưu ý: thuộc tính này đã bị deprecated từ phiên bản 3.0.
Ví dụ thực tế trong file index.html:
<html xmlns:th="http://www.thymeleaf.org">
<div th:insert="footer :: footerComponent"></div>
<div th:replace="footer :: footerComponent"></div>
<div th:include="footer :: footerComponent"></div>
</html>
Kết quả render tương ứng sẽ là:
<div><footer>© 2024 Công ty Công nghệ. Bản quyền thuộc về chúng tôi.</footer></div>
<footer>© 2024 Công ty Công nghệ. Bản quyền thuộc về chúng tôi.</footer>
<div>© 2024 Công ty Công nghệ. Bản quyền thuộc về chúng tôi.</div>
Truyền tham số động vào Fragment
Fragment không bị giới hạn ở nội dung tĩnh. Bạn có thể định nghĩa các tham số ngay trong dấu ngoặc đơn của th:fragment để nhận dữ liệu từ ngữ cảnh gọi:
<html xmlns:th="http://www.thymeleaf.org">
<div th:fragment="headerNotification(alertMessage)">
Thông báo hệ thống: [(${alertMessage})]
</div>
</html>
Khi sử dụng, bạn truyền giá trị tương ứng vào vị trí fragment:
<html xmlns:th="http://www.thymeleaf.org">
<div th:include="header :: headerNotification('Dữ liệu đã được đồng bộ thành công!')"></div>
</html>
Output sau khi biên dịch:
<div>
Thông báo hệ thống: Dữ liệu đã được đồng bộ thành công!
</div>
Nếu fragment yêu cầu nhiều tham số, bạn có thể truyền theo đúng thứ tự khai báo hoặc sử dụng cú pháp đặt tên tham số để linh hoạt hơn trong việc sắp xếp:
<div th:fragment="dataGrid(gridParamA, gridParamB)">
<p th:text="${gridParamA} + ' | ' + ${gridParamB}">Placeholder</p>
</div>
<!-- Truyền theo thứ tự khai báo -->
<div th:replace="::dataGrid(${dataX}, ${dataY})"></div>
<!-- Truyền theo tên tham số (không phụ thuộc thứ tự) -->
<div th:replace="::dataGrid(gridParamB=${dataY}, gridParamA=${dataX})"></div>
Xây dựng Layout linh hoạt với Fragment
Một trong những ứng dụng thực tế nhất của fragment là tạo template mẹ cho phép các trang con inject nội dung cục bộ (như tiêu đề, CSS, JS) vào cấu trúc chung.
Trước tiên, định nghĩa vùng đầu trang trong file layout.html:
<html xmlns:th="http://www.thymeleaf.org">
<head th:fragment="pageHeader(pageTitle, customStyles, customScripts)">
<title th:replace="${pageTitle}">Default Application Title</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<link href="/assets/css/core.css" rel="stylesheet">
<script src="/assets/js/core.js"></script>
<th:block th:replace="${customStyles}" />
<th:block th:replace="${customScripts}" />
</head>
</html>
Tại file trang con, bạn sử dụng cú pháp chọn tập hợp (~{::selector}) để ánh xạ nội dung hiện tại vào các tham số của layout:
<html xmlns:th="http://www.thymeleaf.org">
<head th:include="layout :: pageHeader(~{::title}, ~{::link}, ~{::script})">
<title>Trang Chi Tiết Sản Phẩm</title>
<link rel="stylesheet" th:href="@{/css/detail.css}">
<script th:src="@{/js/detail.js}"></script>
</head>
</html>
Thymeleaf sẽ hợp nhất và trả về kết quả cuối cùng:
<head>
<title>Trang Chi Tiết Sản Phẩm</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<link href="/assets/css/core.css" rel="stylesheet">
<script src="/assets/js/core.js"></script>
<link rel="stylesheet" href="/css/detail.css">
<script src="/js/detail.js"></script>
</head>
Cần lưu ý rằng cú pháp selector phải khớp chính xác với tên thẻ HTML (~{::link} thay vì ~{::links}). Nếu một trang cụ thể không cần inject style hay script bổ sung, bạn chỉ việc truyền một tập hợp rỗng ~{} cho tham số tương ứng:
<head th:include="layout :: pageHeader(~{::title}, ~{::link}, ~{})">
<title>Trang 404</title>
<link rel="stylesheet" th:href="@{/css/error.css}">
</head>