Tạo bảng xếp hạng bài viết có tab giao diện, dùng REST API từ Init View Count

Bài viết này hướng dẫn bạn cách tạo một bảng xếp hạng bài viết theo lượt xem, có giao diện dạng tab để chuyển đổi giữa các mốc thời gian như Hôm nay, Tuần này, Tháng nàyTất cả. Giao diện sử dụng UIkit và dữ liệu được lấy trực tiếp từ REST API của Init View Count.

Tạo bảng xếp hạng bài viết có tab giao diện, dùng REST API từ Init View Count

Demo

Cập nhật: Từ phiên bản 1.3, Init View Count đã hỗ trợ sẵn chức năng bảng xếp hạng theo tab thông qua shortcode. Nếu bạn chỉ cần giao diện có sẵn, tối ưu hiệu năng và không phụ thuộc framework ngoài, bạn có thể dùng trực tiếp shortcode này. Bài viết bên dưới chỉ cần thiết khi bạn muốn tự tuỳ biến hoàn toàn UI, sử dụng UIkit hoặc bất kỳ CSS framework nào khác.

Chuẩn bị

  • Đã cài plugin Init View Count và có dữ liệu lượt xem thực tế.
  • Theme đang sử dụng UIkit (hoặc tích hợp riêng các class như uk-tab, uk-switcher, uk-grid).
  • Biết cách thêm JavaScript vào theme hoặc plugin, ví dụ qua wp_enqueue_script hoặc inline trong template.

1. Tạo HTML giao diện bảng xếp hạng

Bắt đầu bằng việc xây dựng giao diện HTML đơn giản, sử dụng uk-tab để tạo các tab thời gian (Tất cả, Hôm nay, Tuần này, Tháng này), và uk-switcher để chuyển nội dung tương ứng.

<div id="view-ranking" class="uk-margin">
  <ul class="uk-tab" data-uk-tab>
    <li class="uk-active"><a href="#" data-range="total">Tất cả</a></li>
    <li><a href="#" data-range="day">Hôm nay</a></li>
    <li><a href="#" data-range="week">Tuần này</a></li>
    <li><a href="#" data-range="month">Tháng này</a></li>
  </ul>

  <ul class="uk-switcher uk-margin">
    <li>
      <div class="ranking-content" data-range="total" data-loaded="false">
        <!-- Skeleton chỉ cho tab đầu -->
        <div class="uk-grid-small uk-flex-middle uk-margin-small" uk-grid>
          <div class="uk-width-auto">
            <div class="uk-background-muted uk-border-rounded" style="width:60px; height:60px;"></div>
          </div>
          <div class="uk-width-expand">
            <div class="uk-margin-small">
              <div class="uk-background-muted uk-border-rounded" style="height: 16px; width: 80%;"></div>
            </div>
            <div class="uk-margin-small-top">
              <div class="uk-background-muted uk-border-rounded" style="height: 12px; width: 60%;"></div>
            </div>
          </div>
        </div>

        <div class="uk-grid-small uk-flex-middle uk-margin-small" uk-grid>
          <div class="uk-width-auto">
            <div class="uk-background-muted uk-border-rounded" style="width:60px; height:60px;"></div>
          </div>
          <div class="uk-width-expand">
            <div class="uk-margin-small">
              <div class="uk-background-muted uk-border-rounded" style="height: 16px; width: 80%;"></div>
            </div>
            <div class="uk-margin-small-top">
              <div class="uk-background-muted uk-border-rounded" style="height: 12px; width: 60%;"></div>
            </div>
          </div>
        </div>

        <div class="uk-grid-small uk-flex-middle uk-margin-small" uk-grid>
          <div class="uk-width-auto">
            <div class="uk-background-muted uk-border-rounded" style="width:60px; height:60px;"></div>
          </div>
          <div class="uk-width-expand">
            <div class="uk-margin-small">
              <div class="uk-background-muted uk-border-rounded" style="height: 16px; width: 80%;"></div>
            </div>
            <div class="uk-margin-small-top">
              <div class="uk-background-muted uk-border-rounded" style="height: 12px; width: 60%;"></div>
            </div>
          </div>
        </div>

        <div class="uk-grid-small uk-flex-middle uk-margin-small" uk-grid>
          <div class="uk-width-auto">
            <div class="uk-background-muted uk-border-rounded" style="width:60px; height:60px;"></div>
          </div>
          <div class="uk-width-expand">
            <div class="uk-margin-small">
              <div class="uk-background-muted uk-border-rounded" style="height: 16px; width: 80%;"></div>
            </div>
            <div class="uk-margin-small-top">
              <div class="uk-background-muted uk-border-rounded" style="height: 12px; width: 60%;"></div>
            </div>
          </div>
        </div>

        <div class="uk-grid-small uk-flex-middle uk-margin-small" uk-grid>
          <div class="uk-width-auto">
            <div class="uk-background-muted uk-border-rounded" style="width:60px; height:60px;"></div>
          </div>
          <div class="uk-width-expand">
            <div class="uk-margin-small">
              <div class="uk-background-muted uk-border-rounded" style="height: 16px; width: 80%;"></div>
            </div>
            <div class="uk-margin-small-top">
              <div class="uk-background-muted uk-border-rounded" style="height: 12px; width: 60%;"></div>
            </div>
          </div>
        </div>
      </div>
    </li>
    <li>
      <div class="ranking-content" data-range="day" data-loaded="false"></div>
    </li>
    <li>
      <div class="ranking-content" data-range="week" data-loaded="false"></div>
    </li>
    <li>
      <div class="ranking-content" data-range="month" data-loaded="false"></div>
    </li>
  </ul>
</div>

Đặc biệt, tab đầu tiên (Tất cả) sẽ có sẵn các khối skeleton loading để tránh giao diện bị nhảy khi chưa có dữ liệu.

Lưu ý: Đây chỉ là demo HTML, nếu bạn sử dụng ở trang PHP, hãy dùng vòng lặp để xuất các khối skeleton loading nhé!

2. Thêm JavaScript để gọi REST API và hiển thị dữ liệu

Tiếp theo, bạn sẽ viết JavaScript để gọi API /wp-json/initvico/v1/top, lấy dữ liệu bài viết phổ biến theo từng mốc thời gian. Mỗi tab chỉ tải dữ liệu khi người dùng nhấn vào, giúp tối ưu hiệu năng.

document.addEventListener('DOMContentLoaded', function () {
  const container = document.getElementById('view-ranking');
  if (!container) return;

  const tabs = container.querySelectorAll('.uk-tab a');
  const contents = container.querySelectorAll('.ranking-content');
  const cache = {};

  function renderItem(item) {
    return `
      <div class="uk-grid-small uk-flex-middle uk-margin-small" uk-grid>
        <div class="uk-width-auto">
          <a href="${item.link}">
            <img src="${item.thumbnail}" alt="${item.title}" width="60" height="60" style="object-fit:cover;" class="uk-border-rounded">
          </a>
        </div>
        <div class="uk-width-expand">
          <h5 class="uk-margin-remove"><a href="${item.link}" class="uk-link-heading">${item.title}</a></h5>
          <div class="uk-text-meta">${item.views.toLocaleString()} lượt xem · ${item.date}</div>
        </div>
      </div>
    `;
  }

  function renderLoading() {
    let skeleton = '';
    for (let i = 0; i < 5; i++) {
      skeleton += `
        <div class="uk-grid-small uk-flex-middle uk-margin-small" uk-grid>
          <div class="uk-width-auto">
            <div class="uk-background-muted uk-border-rounded" style="width:60px; height:60px;"></div>
          </div>
          <div class="uk-width-expand">
            <div class="uk-margin-small">
              <div class="uk-background-muted uk-border-rounded" style="height: 16px; width: 80%;"></div>
            </div>
            <div class="uk-margin-small-top">
              <div class="uk-background-muted uk-border-rounded" style="height: 12px; width: 60%;"></div>
            </div>
          </div>
        </div>
      `;
    }
    return skeleton;
  }

  function loadRanking(range, target) {
    if (cache[range]) {
      target.innerHTML = cache[range];
      return;
    }

    target.innerHTML = renderLoading();

    fetch(`/wp-json/initvico/v1/top?range=${range}&number=5`)
      .then(res => res.json())
      .then(data => {
        if (!Array.isArray(data)) return;
        const html = data.map(renderItem).join('');
        cache[range] = html;
        target.innerHTML = html || '<div class="uk-text-muted">Không có dữ liệu.</div>';
        target.dataset.loaded = "true";
      })
      .catch(() => {
        target.innerHTML = '<div class="uk-text-danger">Lỗi khi tải dữ liệu.</div>';
      });
  }

  // Map data-range => content
  const contentMap = {};
  contents.forEach(content => {
    const range = content.dataset.range;
    if (range) contentMap[range] = content;
  });

  // Auto load tab "Tất cả"
  const firstRange = container.querySelector('.uk-tab .uk-active a')?.dataset.range;
  if (firstRange && contentMap[firstRange]) {
    loadRanking(firstRange, contentMap[firstRange]);
  }

  // Load khi chuyển tab
  tabs.forEach(tab => {
    tab.addEventListener('click', function () {
      const range = this.dataset.range;
      const target = contentMap[range];
      if (!target || target.dataset.loaded === "true") return;
      loadRanking(range, target);
    });
  });
});

Đối với tab đầu tiên, script sẽ không cần render skeleton vì bạn đã chèn sẵn trong HTML. Các tab còn lại sẽ dùng function JavaScript để hiển thị loading động trước khi dữ liệu được trả về.

Lưu ý nếu không dùng UIkit

Nếu bạn đang sử dụng một CSS framework khác như Bootstrap, Tailwind hoặc tự viết CSS, hãy thay đổi các class như uk-tab, uk-switcher, uk-grid, uk-text-meta… cho phù hợp với hệ thống của bạn. Về cơ bản, chỉ cần giữ lại cấu trúc HTML, còn class có thể tùy biến để phù hợp với thiết kế của bạn.

Kết luận

Với Init View Count và một chút tùy biến JavaScript, bạn có thể tạo bảng xếp hạng bài viết mạnh mẽ, đẹp mắt và dễ mở rộng. Đây là một ví dụ thực tế về cách tận dụng REST API plugin để kết nối trực tiếp với giao diện hiện đại mà không cần gọi shortcode từ PHP.

Bình luận


  • Không có bình luận.

Init Toolbox

Nhấn Ctrl + \ trên máy tính, hoặc vuốt sang trái ở bất kỳ đâu trên mobile.

Đăng nhập





Đang tải...