Phân trang bằng Ajax Get URL là phương pháp tạo thanh số trang (1, 2, 3, …) cho Blogger mà không cần dùng nguồn cấp dữ liệu feeds. Cực kỳ hữu ích với blog đã chặn feeds, đặt blog riêng tư, hoặc cần tốc độ tải nhanh hơn so với phương pháp đọc JSON feeds.
Bài viết này hướng dẫn từ A đến Z: từ nguyên lý, ưu nhược điểm, đến full code Blogger dán được ngay và cách cấu hình số bài mỗi trang, số trang hiển thị.
1. Phân trang Ajax Get URL là gì?
Thay vì đọc dữ liệu từ /feeds/posts/default, script sẽ dùng jQuery $.get() tải HTML của trang tìm kiếm Blogger tại URL:
/search?max-results=200
Sau đó script phân tích HTML trả về, đếm số post bằng class .post, đọc ngày đăng qua class .post_date (định dạng ISO8601), rồi sinh ra các liên kết phân trang có dạng:
/search?updated-max=YYYY-MM-DDTHH:MM:SS%2B07:00&start=N&max-results=10&page=P
2. Ưu điểm và nhược điểm
Ưu điểm
- ✅ Không cần feeds — chạy được cả khi Blogger đã chặn feeds hoặc blog riêng tư.
- ✅ Tốc độ nhanh —
$.getURL nhanh hơn đọc JSON feeds. - ✅ Hiển thị số trang trực quan — không chỉ hiện "Trang trước / Trang sau".
- ✅ Hoạt động với label search và query search — tự phát hiện view hiện tại.
Nhược điểm
- ⚠️ Phụ thuộc vào cấu trúc HTML của theme: cần class
.postvà class ngày.post_datecódata-dateISO8601. - ⚠️ Bắt buộc bài viết có thẻ
<!--more-->để đếm tổng số post chính xác. - ⚠️ Nếu theme đổi cấu trúc (class cha khác
.post), script cần cập nhật selector.
3. Điều kiện bắt buộc trước khi cài
- Theme có jQuery (nếu chưa có, thêm trước
</body>). - Theme có element
.postbao mỗi bài đăng. - Theme có element
.post_datevới thuộc tínhdata-date(ISO8601). - Mỗi bài đăng đều có thẻ
<!--more-->.
3.1. Thêm thẻ <!--more--> cho bài cũ
Vào trình soạn thảo HTML của bài viết, chèn sau vài dòng mở đầu:
<!--more-->
Nếu blog có nhiều bài, dùng chức năng Sửa bài viết hàng loạt hoặc chèn thủ công từng bài.
4. Cài đặt phân trang Ajax Get URL cho Blogger
Bước 1 — Mở tiện ích Blog1
Vào Blogger → Chủ đề → Chỉnh sửa HTML → Ctrl+F tìm <b:widget id='Blog1'.
Bước 2 — Chèn thẻ <nav class='page-numbers'>
Tìm thẻ <b:includable id='main'> (hoặc <b:includable id='main' var='top'>), rồi chèn ngay trước </b:includable>:
<b:if cond='data:view.isMultipleItems and !data:view.isArchive'>
<nav class='page-numbers nav-pagination' id='blog-pager'/>
</b:if>
Bước 3 — Thêm data-date cho ngày đăng
Tìm element hiển thị ngày đăng của mỗi bài, thường có dạng:
<span class='post_date'><data:post.date/></span>
Thêm thuộc tính expr:data-date='data:post.date.iso8601':
<span class='post_date' expr:data-date='data:post.date.iso8601'><data:post.date/></span>
Nếu theme không có element ngày đăng, thêm dòng ẩn dưới đây:
<span class='post_date' expr:data-date='data:post.date.iso8601' style='display:none'>
<data:post.date/>
</span>
Bước 4 — Dán CSS
<b:if cond='data:view.isMultipleItems and !data:view.isArchive'>
<b:tag name='style'>
/* ==================== VEOFOOD PAGINATION CSS ==================== */
.nav-pagination{
display:flex;flex-wrap:wrap;justify-content:center;align-items:center;
gap:8px;margin:24px auto;padding:0;list-style:none;
max-width:100%;font-family:'Segoe UI',Roboto,Arial,sans-serif;
}
.nav-pagination .page-number{
display:inline-flex;align-items:center;justify-content:center;
min-width:38px;height:38px;padding:0 12px;
border-radius:10px;
background:#fff;color:#5A0FC8;border:1px solid #e5d9ff;
font-size:14px;font-weight:700;text-decoration:none;
transition:all .2s cubic-bezier(.4,0,.2,1);
cursor:pointer;
}
.nav-pagination .page-number:hover{
background:#f3ecff;border-color:#c8b3ff;transform:translateY(-1px);
box-shadow:0 4px 12px rgba(90,15,200,.15);
}
.nav-pagination .page-number.current{
background:linear-gradient(135deg,#5A0FC8,#7c3aed);
color:#fff;border-color:transparent;
box-shadow:0 4px 14px rgba(90,15,200,.35);
pointer-events:none;
}
.nav-pagination .page-number[rel="prev"],
.nav-pagination .page-number[rel="next"]{
padding:0;width:38px;
}
.nav-pagination .page-number svg{display:block;fill:currentColor}
@media (max-width:480px){
.nav-pagination{gap:6px}
.nav-pagination .page-number{min-width:34px;height:34px;font-size:13px}
}
</b:tag>
</b:if>
Bước 5 — Dán JS
Dán trước thẻ </body>:
<script>
// ============================================================
// VEOFOOD PAGINATION — AJAX GET URL
// ============================================================
var data = {
view: {
isHomepage: "<b:if cond='data:view.isHomepage'>true</b:if>",
isSearch: "<b:if cond='data:view.isSearch and !data:view.search.label and !data:view.search.query'>true</b:if>",
isLabelSearch: "<b:if cond='data:view.search.label'>true</b:if>",
isSearchQuery: "<b:if cond='data:view.search.query'>true</b:if>"
},
blog: {
pageName: "<b:if cond='data:blog.pageName'><data:blog.pageName/></b:if>",
homepageUrl: "<data:blog.homepageUrl/>",
searchqueryescaped: "<b:if cond='data:blog.searchQuery'><data:blog.searchQuery/></b:if>"
},
messages: {
home: "<data:messages.home/>"
}
};
//<![CDATA[
var pages = {
num: {
posts: 10, // ← Số bài mỗi trang (khớp Settings → Posts → Posts per page)
pages: 5, // ← Số trang hiển thị (số lẻ để căn giữa đẹp)
max: 500 // ← Số bài tối đa (lớn hơn tổng số bài blog)
},
element: {
id: '#blog-pager',
parent: '.Blog .post',
child: '.post_date'
},
messages: {
next: '<svg height="16px" viewBox="0 0 608 1280" width="16px" xmlns="http://www.w3.org/2000/svg"><path d="M595 288q0 13-10 23L192 704l393 393q10 10 10 23t-10 23l-50 50q-10 10-23 10t-23-10L23 727q-10-10-10-23t10-23l466-466q10-10 23-10t23 10l50 50q10 10 10 23z"/></svg>',
prev: '<svg height="16px" viewBox="0 0 608 1280" width="16px" xmlns="http://www.w3.org/2000/svg"><g transform="translate(608 0) scale(-1 1)"><path d="M595 288q0 13-10 23L192 704l393 393q10 10 10 23t-10 23l-50 50q-10 10-23 10t-23-10L23 727q-10-10-10-23t10-23l466-466q10-10 23-10t23 10l50 50q10 10 10 23z"/></g></svg>',
page: 'Trang',
nextpage: 'Trang kế',
prevpage: 'Trang trước'
}
};
//]]>
</script>
<b:if cond='data:view.isMultipleItems and !data:view.isArchive'>
<script>
//<![CDATA[
(function(){
var serviceUrl = '';
if (data.view.isHomepage === 'true' || data.view.isSearch === 'true') {
serviceUrl = '/search?max-results=' + pages.num.max;
}
if (data.view.isLabelSearch === 'true') {
serviceUrl = '/search/label/' + data.blog.pageName + '?max-results=' + pages.num.max;
}
if (data.view.isSearchQuery === 'true') {
serviceUrl = '/search?q=' + data.blog.searchqueryescaped + '&max-results=' + pages.num.max;
}
$.get(serviceUrl, function(resp) {
var posts = $(resp).find(pages.element.parent);
if (!posts.length) return;
var url = window.location.href;
var totalPages = Math.ceil(posts.length / pages.num.posts);
var currentPage = 1;
var html = '';
// Xác định trang hiện tại từ URL
if (url.indexOf('&page=') !== -1 || url.indexOf('?page=') !== -1) {
var m = url.match(/[?&]page=(\d+)/);
if (m) currentPage = Number(m[1]) || 1;
}
var half = Math.floor(pages.num.pages / 2);
if (half === pages.num.pages - half) pages.num.pages = half * 2 + 1;
var startPage = currentPage - half;
if (startPage < 1) startPage = 1;
var endPage = startPage + pages.num.pages - 1;
if (endPage > totalPages) endPage = totalPages;
function getDate(idx) {
var el = posts.eq(idx).find(pages.element.child);
var d = el.attr('data-date') || '';
return d.replace('+', '%2B');
}
function buildUrl(pageNum, rel) {
var start = (pageNum - 1) * pages.num.posts;
if (pageNum === 1) {
if (data.view.isHomepage === 'true' || data.view.isSearch === 'true') return data.blog.homepageUrl;
if (data.view.isLabelSearch === 'true') return '/search/label/' + data.blog.pageName + '?max-results=' + pages.num.posts;
if (data.view.isSearchQuery === 'true') return '/search?q=' + data.blog.searchqueryescaped + '&max-results=' + pages.num.posts;
}
var t = (pageNum - 1) * pages.num.posts - 1;
var d = getDate(t);
if (data.view.isLabelSearch === 'true') {
return '/search/label/' + data.blog.pageName + '?updated-max=' + d + '&max-results=' + pages.num.posts + '&start=' + start + '&page=' + pageNum;
}
if (data.view.isSearchQuery === 'true') {
return '/search?q=' + data.blog.searchqueryescaped + '&updated-max=' + d + '&max-results=' + pages.num.posts + '&start=' + start + '&page=' + pageNum;
}
return '/search?updated-max=' + d + '&max-results=' + pages.num.posts + '&start=' + start + '&page=' + pageNum;
}
// Nút Prev
if (currentPage > 1) {
html += '<a class="page-number" rel="prev" title="' + pages.messages.prevpage + '" href="' + buildUrl(currentPage - 1, 'prev') + '">' + pages.messages.prev + '</a>';
}
// Số trang
for (var i = startPage; i <= endPage; i++) {
var cls = (i === currentPage) ? 'page-number current' : 'page-number';
var title = pages.messages.page + ' ' + i;
html += '<a class="' + cls + '" title="' + title + '" href="' + buildUrl(i) + '">' + i + '</a>';
}
// Nút Next
if (currentPage < totalPages) {
html += '<a class="page-number" rel="next" title="' + pages.messages.nextpage + '" href="' + buildUrl(currentPage + 1, 'next') + '">' + pages.messages.next + '</a>';
}
var pager = document.querySelector(pages.element.id);
if (pager) pager.innerHTML = html;
});
})();
//]]>
</script>
</b:if>
5. Cấu hình số bài và số trang hiển thị
Trong đoạn JS ở Bước 5, tìm object pages.num:
num: {
posts: 10, // ← Số bài mỗi trang
pages: 5, // ← Số trang hiển thị
max: 500 // ← Tổng số bài tối đa (phải lớn hơn tổng bài blog)
}
| Tham số | Ý nghĩa | Khuyến nghị |
|---|---|---|
posts | Số bài hiển thị mỗi trang | 6 – 12 (mobile dùng 6, desktop 10) |
pages | Số trang hiển thị quanh trang hiện tại | 5 hoặc 7 (số lẻ để căn giữa) |
max | Số bài tối đa script sẽ tải về để đếm | ≥ tổng số bài blog (500 – 1000) |
Quan trọng: Phải đồng bộ pages.num.posts với Blogger → Settings → Posts → Posts per page. Nếu Blogger đang set 7 bài/trang mà script để 10 → số trang sẽ sai.
6. Troubleshooting — Lỗi thường gặp
6.1. Phân trang không hiện
- Kiểm tra URL hiện tại: script chỉ chạy khi view là multiple items và không phải archive.
- Mở DevTools Console xem có lỗi
$.get is not a function→ thiếu jQuery. - Kiểm tra selector
.Blog .postcó khớp với theme không (F12 → Elements).
6.2. Số trang bị sai (nhiều/ít hơn thực tế)
- Do bài viết thiếu thẻ
<!--more-->. - Do
pages.num.postskhác cấu hình Blogger. - Do
pages.num.maxnhỏ hơn tổng số bài blog.
6.3. Bấm số trang bị lỗi 404
- Do class
.post_datethiếu thuộc tínhdata-date→ kiểm tra Bước 3. - Do định dạng ngày không đúng ISO8601 → phải là
data:post.date.iso8601.
6.4. Phân trang hiện nhưng CSS không đẹp
- Do theme có CSS khác ghi đè lên
.nav-pagination. Thêm!importanthoặc tăng specificity. - Do file CSS dán sai vị trí — phải nằm trong
<b:if>của Bước 4.
7. Checklist triển khai
- ☐ Đã backup theme trước khi sửa.
- ☐ Theme có jQuery.
- ☐ Mỗi bài viết có
<!--more-->. - ☐ Đã chèn
<nav id='blog-pager'>trong<b:includable id='main'>. - ☐ Đã thêm
expr:data-date='data:post.date.iso8601'cho.post_date. - ☐ Đã dán CSS ở Bước 4.
- ☐ Đã dán JS ở Bước 5.
- ☐
pages.num.postskhớp với cấu hình Blogger. - ☐ Test chuyển trang 1 → 2 → 3 → quay lại.
- ☐ Test trên mobile và desktop.
- ☐ Test với label search (
/search/label/xxx). - ☐ Test với query search (
/search?q=xxx).
8. Kết luận
Phân trang Ajax Get URL là giải pháp mạnh, nhẹ, không cần feeds, phù hợp cho mọi blog Blogger — đặc biệt khi blog đã chặn feeds hoặc để riêng tư. Chỉ cần đảm bảo 3 điều kiện: có jQuery, có class .post, và mọi bài có thẻ <!--more--> là có thể triển khai ngay trong 10 phút.
Nếu bạn cần bản phân trang kết hợp Load More tự động (infinite scroll) hoặc sticky sidebar, đọc thêm các bài viết liên quan trên veospot.net.


Đăng nhận xét