Phân trang cho danh sách bài viết
Trong phần này, chúng ta phân trang cho danh sách các bài viết theo ngày xuất bản.
Ý tưởng: trong phần giao diện, thiết kế một nút bấm (loadmore), khi nhấn vào nút này load thêm các bài viết (nếu còn).
Thiết lập cho phía client
Thiết kế một nút bấm và xử lý sự kiện cho nút bấm này:
Dưới đây là hàm fetchPostList() sử dụng trong đoạn mã trên:
Hàm fetchPostList() được sử dụng để gửi yêu cầu đến API: /api/v1/posts để lấy danh sách bài viết. Nếu lastPostPageToken.next tồn tại nó sẽ được thêm vào queryString để yêu cầu trang tiếp theo của bài viết. Nếu ezyweb.lang tồn tại nó cũng được thêm vào queryString. Sau đó, sử dụng ajax để gửi yêu cầu get với các tham số đã xây dựng. limit=3 xác định số lượng bài viết muốn lấy ra trong mỗi lần nhấn.
Nếu yêu cầu thành công ('.done()'), dữ liệu trả về data sẽ được sử dụng để cập nhật lastPostPageToken. Thêm các bài viết mới vào #postListBody và kiểm tra xem còn bài viết hay không, nếu đã hết bài viết thì xóa đi nút next.
Nếu yêu cầu thất bại ('.fail()'), hàm ezyweb.processGetApiErrors(e) sẽ được gọi để xử lý lỗi.
Hàm buildPostListBodyHtml(posts) được sử dụng để xây dựng HTML cho danh sách bài viết từ data.items.
Hàm buildPostListItemHtml(post) sử dụng để lấy ra bài viết với dữ liệu tương ứng. Dưới đây là đoạn mã của hàm này:
Thiết lập cho phần Backend
- RequestParam
- Được sử dụng để ràng buộc các tham số của yêu cầu HTTP đến các tham số của phương thức xử lý yêu cầu trong controller.
- Được sử dụng để trích xuất các tham số từ URL query string và ánh xạ chúng đến các tham số của phương thức để sử dụng trong logic xử lý yêu cầu.
- Cú pháp: @RequestParam(“parameterName“) DataType parameterVariable
- parameterName: là tên của tham số trong url query string.
- parameterVariable: biến tương ứng sẽ nhận giá trị của tham số.
Ví dụ vê một url query string: http://localhost:8080/api/v1/posts?limit=3&nextPageToken=eyJzb3J0T3JkZXIiOiJQUklPUklUWV9ERVNDX0lEX0RFU0MiLCJ2YWx1ZSI6eyJwcmlvcml0eSI6MCwiaWQiOjEyfX0=
package com.blog.essential.web.controller.view; import com.blog.essential.web.controller.service.WebEssentialPostControllerService;
import com.blog.essential.web.response.WebLatestPostResponse;
import com.blog.essential.web.response.WebMostViewPostResponse;
import com.blog.essential.web.response.WebMostVotePostResponse;
import com.blog.essential.web.response.WebPostItemResponse;
import com.tvd12.ezyhttp.server.core.annotation.Controller;
import com.tvd12.ezyhttp.server.core.annotation.DoGet;
import com.tvd12.ezyhttp.server.core.annotation.RequestParam;
import com.tvd12.ezyhttp.server.core.view.View;
import lombok.AllArgsConstructor;
import org.youngmonkeys.ezyarticle.web.validator.WebTermValidator;
import org.youngmonkeys.ezyplatform.model.PaginationModel;
import org.youngmonkeys.ezyplatform.web.validator.WebCommonValidator; import java.util.List; import static org.youngmonkeys.ezyplatform.constant.CommonConstants.VIEW_VARIABLE_PAGE_TITLE; @Controller
@AllArgsConstructor
public class HomeController { private final WebEssentialPostControllerService essentialPostControllerService; private final WebCommonValidator commonValidator; private final WebTermValidator termValidator; @DoGet("/") public View home( @RequestParam("author") String authorUuid, @RequestParam("term") String termSlug, @RequestParam("keyword") String keyword, @RequestParam("nextPageToken") String nextPageToken, @RequestParam("prevPageToken") String prevPageToken, @RequestParam("lastPage") boolean lastPage, @RequestParam(value = "limit", defaultValue = "1") int limit ) { this.commonValidator.validatePageSize(limit); this.commonValidator.validateSearchUuid(authorUuid); this.commonValidator.validateSearchKeyword(keyword); this.termValidator.validateSearchTerm(termSlug); WebPostItemResponse mainPost = essentialPostControllerService.getMainPostOrNull(); List<WebPostItemResponse> extraPosts = essentialPostControllerService.getExtraPosts(); List<WebMostViewPostResponse> mostViewPosts = essentialPostControllerService .getMostViewPosts(); List<WebMostVotePostResponse> mostVotePosts = essentialPostControllerService .getMostVotePosts(); PaginationModel<WebLatestPostResponse> latestPostPagination = essentialPostControllerService .getPostPagination( authorUuid, termSlug, keyword, nextPageToken, prevPageToken, lastPage, limit ); return View.builder() .template("home") //--> Hàm .addVariable("mainPost", mainPost) .addVariable("extraPosts", extraPosts) .addVariable("mostViewPosts", mostViewPosts) .addVariable("mostVotePosts", mostVotePosts) .addVariable("latestPostPagination", latestPostPagination) .addVariable(VIEW_VARIABLE_PAGE_TITLE, "home") .build(); }
}
Ý nghĩa của các param sử dụng trong bài:
- author: tên tác giả - được sử dụng để lấy ra các bài viết của tác giả được chỉ định.
- term: lấy ra các bài viết theo term.
- keyword: dùng để lọc bài viết theo một số điều kiện cụ thể (author, slug, status, type).
- nextPageToken: một token lưu trữ vị trí bắt đầu cho lần lấy tiếp theo.
- prevPageToken: một tonken lưu trữ vị trí bắt đầu của lần lấy trước đó.
- lastPage: dùng để xác định trang hiện tại có phải là trang cuối cùng hay không.
- limit: là số bài viết mặc định ban đầu khi load trang.
VD về nextPageToken: {"sortOrder":"PRIORITY_DESC_ID_DESC","value":{"priority":0,"id":12}}
Đoạn mã trên được sử dụng khi trang home được gọi đến và các bài viết mặc định được lấy ra. Còn trong đoạn mã dưới, nó được gọi khi nhấn vào nút loadmore để lấy dữ liệu cho các bài viết tiếp theo:
package com.blog.essential.web.controller.api; import com.blog.essential.web.controller.service.WebEssentialPostControllerService;
import com.blog.essential.web.response.WebLatestPostResponse;
import com.blog.essential.web.response.WebPostVoteResponse;
import com.tvd12.ezyhttp.server.core.annotation.*;
import lombok.AllArgsConstructor;
import org.youngmonkeys.ezyarticle.sdk.entity.PostType;
import org.youngmonkeys.ezyarticle.sdk.model.PostModel;
import org.youngmonkeys.ezyarticle.web.service.WebPostService;
import org.youngmonkeys.ezyarticle.web.validator.WebPostValidator;
import org.youngmonkeys.ezyarticle.web.validator.WebTermValidator;
import org.youngmonkeys.ezyplatform.model.PaginationModel;
import org.youngmonkeys.ezyplatform.web.validator.WebCommonValidator; import javax.servlet.http.HttpServletRequest;
import java.math.BigInteger; import static org.youngmonkeys.ezyplatform.util.HttpRequests.getLanguage; @Api
@Controller("/api/v1")
@AllArgsConstructor
public class WebEssentialApiPostController { private final WebPostService postService; private final WebEssentialPostControllerService essentialPostControllerService; private final WebCommonValidator commonValidator; private final WebPostValidator postValidator; private final WebTermValidator termValidator; @DoGet("/posts") public PaginationModel<WebLatestPostResponse> postsGet( HttpServletRequest request, @RequestParam("author") String authorUuid, @RequestParam("term") String termSlug, @RequestParam("keyword") String keyword, @RequestParam("nextPageToken") String nextPageToken, @RequestParam("prevPageToken") String prevPageToken, @RequestParam("lastPage") boolean lastPage, @RequestParam(value = "limit", defaultValue = "3") int limit ) { this.commonValidator.validatePageSize(limit); this.commonValidator.validateSearchUuid(authorUuid); this.commonValidator.validateSearchKeyword(keyword); this.termValidator.validateSearchTerm(termSlug); return this.essentialPostControllerService .getPostPagination( authorUuid, termSlug, keyword, nextPageToken, prevPageToken, lastPage, limit ); } @DoPost("/posts/{slug}/vote") public WebPostVoteResponse postsSlugVoteUpPost( HttpServletRequest request, @PathVariable String slug ) { PostModel post = postValidator .validatePublishedPostSlugWithTypeAndLanguage( slug, PostType.POST.toString(), getLanguage(request) ); BigInteger totalVote = postService.increaseVoteCount( post.getId() ); return new WebPostVoteResponse(totalVote); }
}
- Phân trang bài viết
Từ các param nhận được, truyền vào và sử dụng các param. Xác định các tiêu chí để lấy ra các bài viết.
Trong PostControllerService, viết một hàm để xử lý các param này:
Hàm trên sử dụng một bộ lọc - lọc ra các tiêu chí của bài viết:
Truyền vào hàm getPostPagination() thứ hai với các tham số tương ứng:
Lấy ra PaginationModel chứa các bài viết theo tiêu chí lọc ở trên (các bài viết được lưu trong items của PaginationModel).
Đưa các qua decorator để lấy hết các thông tin cần thiết cho bài viết (items).
Đưa qua Converter, lọc ra các thông tin cần thiết cho items.
- Dữ liệu được sử dụng
Sau khi lấy ra được dữ liệu, hàm done() trong ajax được gọi:
Data ở đây tương ứng với PaginationModel<WebLatestPostResponse>, data.items sẽ lấy ra dữ liệu của các bài viết, lấy dữ liệu đó xây dựng cho các bài viết tiếp theo.