更新於 2024/02/18閱讀時間約 30 分鐘

【套件筆記 - JS】頁碼 Pagination.js

Pagintaion 套件 + Axios API 請求範例

demo

demo


一、官網資源

pagination 官網:https://pagination.js.org/docs/index.html

可由此處下載 css 原始檔客製 https://pagination.js.org/docs/index.html#Theme


二、CDN 安裝

頁碼 pagination.scss(非必要引入,也可以從官網下載原始檔更改)

<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/paginationjs/2.6.0/pagination.min.css">

jQuery Import(使用 slim.min.js 版本會出錯)

<script src="https://code.jquery.com/jquery-3.7.0.min.js"></script>

頁碼 pagination.js

<script src="https://cdnjs.cloudflare.com/ajax/libs/paginationjs/2.6.0/pagination.min.js"></script>

Bootstrap 5 CSS(建立表格用,非必要)

<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css">


三、使用範例

https://codepen.io/annchou_illu/pen/oNmZxmd

以下以政府 API 做為資料來源,搭配 Bootstrap 5 切版

3-1. 目標功能

  1. 頁碼與資料渲染
  2. 簡單篩選功能
  3. 點選至多 3 筆資料

3-2. 示範步驟

  1. 規劃版位
  2. 變數、原始資料宣告、按鈕綁定
  3. 撰寫初始化渲染函式 renderData
    .原始資料請求
    .篩選資料生成
    .頁碼生成
  4. 拆解元件
  5. 加入點選物件功能

3-3. 實作開始

  1. 規劃版位 html
    最主要是先建立好渲染資料和頁碼生成位置,完整 html 可見 codepen
<!-- 被點選物件顯示區 -->
<p class="bg-light">你選擇的專案 seq:<span class="showCheckedList"></span></p>

<!-- 篩選按鈕區 -->
<div class="d-flex justify-content-end gap-1 mb-3">
<input type="button" class="btn btn-secondary btnFilter" value="全部" />
<input type="button" class="btn btn-secondary btnFilter" value="前鎮區" />
<input type="button" class="btn btn-secondary btnFilter" value="三民區" />
</div>

<!-- 資料生成位置與內容模板 -->
<div id="pagination-Container">
<ul class="list-group">
<li class="list-group-item d-flex justify-content-between">
<div>
<p>專案名稱</p>
<p>專案區域</p>
</div>
<input class="form-check-input" type="checkbox" id="checkProject" />
</li>
</ul>
</div>
<!-- 頁碼 -->
<div id="pagination-Pages" class="d-flex justify-content-end mt-3"></div>
  1. 基本前置設定
    於 JavaScript 準備 API 路徑來源宣告,以及將 DOM 元素綁定監聽。
const _url =
"https://api.kcg.gov.tw:443/api/service/Get/1f2a6afe-f953-436f-981c-92f2739b3475";
// 顯示選取專案
const showCheckedList = document.querySelector(".showCheckedList");
const checkedProjectList = []; // 被選取清單

// 初始篩選分類
let selectedFilter = "全部";
// 按鈕篩選處理
const btnFilter = document.querySelectorAll(".btnFilter");
// 按鈕監聽
btnFilter.forEach((btn) => {
btn.addEventListener("click", function () {
selectedFilter = btn.value; // 當按鈕按下,selectedFilter 的值會被改變
renderData(); // 每次被按下都會一再觸發 renderData()
});
});
  1. 在準備渲染前,先來了解 Pagination.js 和 jQuery 文件。
    - Pagination.js Asynchronous or JSONP:https://pagination.js.org/index.html
    - Pagination.js ajax:https://pagination.js.org/docs/index.html#ajaxFunction
    - jQuery ajax:https://api.jquery.com/jquery.ajax/
  2. Pagintaion.js docs/commenlyused 第三項提到 ajax 的請求格式如下。並且套件支援以 jQuery ajax 操作 API 來源資料,如果請求成功則使用 success 接收並繼續執行,請求失敗則跳到 error
dataSource: function(done){
$.ajax({
type: 'GET',
url: '/test.json',
success: function(response){
done(response);
}
});
}
  1. 建立初始化 function renderData 基本架構
    這時就可以組合出大概的渲染函式。
function renderData() {
$(function () {
(function (name) {
let container = $("#pagination-" + name); // 這裡面包裝了套件所需設定,暫時不用理會
$.ajax({
url: _url, // 資料來源 API 網址
success: function (data) {
... // 請求成功後執行
},
error: function (error) {
console.error(error); // 請求失敗時執行
}
});
})("Pages");
});
}

renderData(); // 一啟動網頁就運行
  1. 儲存 API 請求回傳的原始資料
    使用 console 測試,發現我們需要撈回的資料被放在 data 中的 data 屬性,所以 data.data 是我們所需要的,所以宣告一個變數儲存它。
success: function (data) {
const allData = data.data​ //宣告一個 allData 變數來存放 API 回傳的全部資料
},
  1. 撰寫篩選按鈕動作
    因為我們在 html 中寫了三個按鈕,讓它可以做區域篩選,並且上面已經先宣告了變數 selectedFilter,預設值為「全部」。當 selectedFilter 變數的值已經不是「全部」,則將 filteredData 重新賦值成符合篩選條件的資料。
let filteredData = []; // 真正要被渲染的已篩選資料
selectedFilter !== "全部" // 我們在步驟 2 時宣告過全域,並設定初始值為"全部"
? (filteredData = allData.filter((item) => item.name === selectedFilter))
: (filteredData = allData);
  1. 將按鈕加入點擊監聽,測試上述的 selectedFilter 有沒有因點擊而更改。因為每次點擊按鈕都要重新利用 renderData 函式處理,所以我們在執行函式中再度呼叫 renderData
btnFilter.forEach((btn) => {
btn.addEventListener("click", function () {
selectedFilter = btn.value;
renderData();
});
});
  1. 目前我們的 renderData 函式全部是以下狀態,並且加入 console 測試 selectedFilterfilteredData 值是否有跟著按鈕一起改變。確定成功就可以將 console 刪除。
function renderData() {
$(function () {
(function (name) {
let container = $("#pagination-" + name); // 這裡面包裝了套件所需設定,暫時不用理會
$.ajax({
url: _url, // 資料來源 API 網址
success: function (data) {
const allData = data.data​ // 宣告一個 allData 變數來存放 API 回傳的全部資料

let filteredData = []; // 真正要被渲染的已篩選資料
selectedFilter !== "全部"
? (filteredData = allData.filter((item) => item.name === selectedFilter))
: (filteredData = allData);

console.log(selectedFilter)
console.log(filteredData)
},
error: function (error) {
console.error(error); // 請求失敗時執行
}
});
})("Pages");
});
}
  1. 計算頁碼與設定
    接著用篩選後的資料計算可以生成幾頁頁碼,以下程式碼可以接在剛才 filteredData console 的下面。回到套件 docs/methods,發現我們可以透過 container.pagination({ ... }) 設定套件呈現細節。以下屬性詳細請參照官網文件。
container.pagination({
dataSource: filteredData, //我們剛才建立的實際渲染資料
//locator: "data", // 資料來源中的屬性,因為上面使用的 filterData 已經是處理過的資料,所以可忽略設定
totalNumber: filteredData.length, //實際資料總數
pageSize: 5, //每頁資料數
showPageNumbers: true,
showPrevious: true,
showNext: true
});
  1. 渲染頁碼與資料
    回到官網文件 docs/dataSourcescontainer 中的callback 屬性可用於插入自訂的 html,所以我們可以將上面 html 要渲染的資料 html 模板貼下來。input 中的各項屬性視原始資料內容加上獨特值。
container.pagination({
//...略

//頁碼渲染與監聽
callback: function (res, pagination) {
let dataHtml = '<ul class="list-group">';

$.each(res, function (index, item) {
dataHtml += `<li class="list-group-item d-flex justify-content-between">
<div>
<p>${item.projectName}</p>
<p>${item.name}</p>
</div>
</li>`;
});

dataHtml += "</ul>";
$("#pagination-Container").html(dataHtml); // 設定 HTML 內容到 container​
}
});
  1. 最後補上執行 renderData,讓網頁一開啟就運行。就完成目標功能的第一二項,已經可以作頁碼切換與按鈕篩選資料了。
function renderData(){...}

renderData();
  1. 拆元件
    因為目前 function renderData 內的程式碼冗長,所以先來做拆分整理。我們剛才在 function renderData 內的程式碼大致可以分為
    .基礎架構
    .資料依據按鈕篩選:需要輸入資料 allData,並生成 filteredData
    .將篩選過的資料計算頁碼:需要輸入這兩筆資料 filteredDatacontainer
    .渲染頁碼與資料:需要輸入資料 res
    依據以上分類可以拆成四個函式
// 基礎架構
function renderData() {
$(function () {
(function (name) {
let container = $("#pagination-" + name);
$.ajax({
url: _url,
success: function (data) {
// 將完整資料交給 handleFilterData 函式篩選
const allData = data.data
const filteredData = filterData(allData);
// 將返回的 filteredData 計算頁碼
handlePagination(filteredData, container);
},
error: function (error) {
console.error(error);
}
});
})("Pages");
});
}
// 資料依據按鈕篩選:需要輸入資料 allData,並生成 filteredData。​
function filterData(data) {
let filteredData = [];
selectedFilter !== "全部"
? (filteredData = data.filter((item) => item.name === selectedFilter))
: (filteredData = data);
return filteredData;
}
//使用篩選資料,計算頁碼
function handlePagination(filteredData, container) { container.pagination({
dataSource: filteredData, // 實際資料來源
//locator: "data", // 資料來源中的屬性,因為上面使用的 filterData 已經是處理過的資料,所以可忽略設定
totalNumber: filteredData.length, // 實際資料總數
pageSize: 5, // 每頁資料數
showPageNumbers: true,
showPrevious: true,
showNext: true,

// 頁碼渲染與監聽
callback: function (res, pagination) {
//渲染頁碼與資料
renderPagination(res); }
});}
// 渲染頁碼與資料
function renderPagination(res) {
let dataHtml = '<ul class="list-group">';
$.each(res, function (index, item) {
dataHtml += `<li class="list-group-item d-flex justify-content-between">
<div>
<p>${item.projectName}</p>
<p>${item.name}</p>
</div>
</li>`;
});

dataHtml += "</ul>";
$("#pagination-Container").html(dataHtml); // 設定 HTML 內容到 container
}
  1. 加入點選功能的 checkbox
    先從渲染 checkbox 開始,修改剛才建立好的 function renderPagination 中的 dataHtml
$.each(res, function (index, item) {
dataHtml += `<li class="list-group-item d-flex justify-content-between">
<div>
<p>${item.projectName}</p>
<p>${item.name}</p>
</div>
<input class="form-check-input" type="checkbox">
</li>`;
});
  1. 為 checkbox 加入辨識值
    每一個 checkbox 都需要被辨識,觀察 $.each(res, function (index, item) 中的 item 可以發現 item.projectSEQ 這個唯一值可以被使用,所以我們可藉由 data-*MDN) 的方式加入此值。
$.each(res, function (index, item) {
dataHtml += `<li class="list-group-item d-flex justify-content-between">
<div>
<p>${item.projectName}</p>
<p>${item.name}</p>
</div>
<input class="form-check-input" type="checkbox"
id="checkProject${item.projectSEQ}"
data-seq=${item.projectSEQ}> //這裡加入 data-seq
</li>`;
});
  1. 儲存被點選的 checkbox
    完成以上步驟後,我們已經可以點選 checkbox,也可以換頁。但如果回到上一頁,會發現剛才點選的 checkbox 狀態因為換頁資料重新渲染,導致失去被點選狀態。因此我們要紀錄有哪些 checkbox 被點選過,之後才能在渲染時判定每個 checkbox 該呈現什麼狀態。
    步驟 2 中,我們有宣告一個 checkedProjectList 變數用來儲存被點選的 checkbox 清單。現在我們建立一個函式來將資料寫入變數中。判讀被點選的 checkbox data-seq,將 seq 存入。讀取 data-* 的方法使用 checkbox.getAttribute("data-seq"),或 checkbox.dataset.seq 都可以。
    另外特別注意的是,我們從 API 取得回來的 projectSEQ 參數是 number 型別,所以我們在這裡存入checkedProjectList 變數的值也必須轉成 number 型別才不會之後影響判斷。
function handleCheckProject() {
// 將所有 checkbox 綁定
const checkBoxes = document.querySelectorAll("input[type='checkbox']");

// 因為 checkBoxes 有多個 checkbox,所以必須先使用 forEach 才能 addEventListener 監聽
checkBoxes.forEach(checkbox => {
checkbox.addEventListener("change", () => {
const projectSeq = checkbox.getAttribute("data-seq");
checkedProjectList.push(+projectSeq) // 使用「+」轉 number

//因為步驟 2 中已經有做 DOM 綁定,所以在這裡將儲存的資料渲染
showCheckedList.textContent = checkedProjectList.join("、");
})
})
}
  1. 回到 function handlePagination 函式,在 callback 處加入 handleCheckProject(),讓它能夠執行。
function handlePagination(filteredData, container){
...

callback: function (res, pagination) {
...

//處理 checkbox 選擇專案
handleCheckProject();
}
});
}

Demo

  1. 加入判斷當前 checkbox 是否已被點選,生成樣板
    回到 function renderPagination 函式,在進入 each 之後,執行當前 item.projectSEQ 值是否存在於 checkedProjectList 變數中。includes 會返回 true/falseMDN)。
    checkbox (MDN)文件中提到如果是被勾選狀態,則會被加入 checked 在 HTML 中標示,所以也在 dataHTML 中使用三元運算子根據剛才 isChecked 的結果判斷是否加入 checked
function renderPagination(res) {
let dataHtml = '<ul class="list-group">';
$.each(res, function (index, item) {
// 是否已經被選取
const isChecked = checkedProjectList.includes(item.projectSEQ);

dataHtml += `<li class="list-group-item d-flex justify-content-between"><div><p>${
item.projectName
}</p><p>${
item.name
}</p></div><input class="form-check-input" type="checkbox" id="checkProject${
item.projectSEQ
}" data-seq=${item.projectSEQ}
${isChecked ? "checked" : ""}> // 三元運算子判斷當前 checkbox 狀態
</li>`;
});

dataHtml += "</ul>";
$("#pagination-Container").html(dataHtml); // 設定 HTML 內容到 container
}
  1. 完成後的資料表即使換頁再回上一頁,也能繼續顯示相同 checkbox 勾選狀態了。但還有一些小錯誤,即使 checkbox 取消,或是再次點選都會不斷累積相同 seq 編號在 checkedProjectList 變數中。
  2. 限制選取資料數量
    我們要解決上述的 bug 以及完成最後一個任務:限制最多只能選取 3 筆資料。回到 function handleCheckProject,我們要在這裡根據 checkedProjectList 變數內的資料數量判斷是否還能再繼續點選動作。
    ■ 我們可能有幾種情境及對應執行:
    (1)已達最多選取數 → 禁止用戶點選,但因為 checkbox 沒有 disable 屬性,所以我們只能讓 checked 強制取消
    (2)未達最多選取數 → 加入清單
    (3)清單中已有目前點選的 seq 編號 → 判斷為取消點選,從清單中移除
    (4)清單中沒有目前的 seq 編號 → 加入清單
    ■ 綜合以上情境狀況,我們把要情境總結成
    (1)已達最多選取數,並且清單中不存在此 seq → 取消當前 checkbox 勾選狀態
    (2)未達最多選取數 → 根據清單是否存在 seq,執行加入清單或刪除該值的動作​,
    以上不管是判斷清單中是否存在當前 seq,或是執行刪除,都會需要知道該 seq 在清單中的位置,所以可以使用 indexOf 尋找。
function handleCheckProject() {
const checkBoxes = document.querySelectorAll("input[type='checkbox']");
const maxChecked = 3; // 最多選取數

checkBoxes.forEach((checkbox) => {
checkbox.addEventListener("change", () => {
const projectSeq = checkbox.getAttribute("data-seq");

// 判斷目前的 seq 在 checkedProjectList 中的 index,若無則返回 -1
const findCheckBoxIndex = checkedProjectList.indexOf(+projectSeq);

// 如果已達最多選取數,並且清單中不存在此 seq
if (
checkedProjectList.length === maxChecked &&
findCheckBoxIndex === -1
) {
checkbox.checked = false;
} else {
// 未達最多選取數​
findCheckBoxIndex === -1
? checkedProjectList.push(+projectSeq)
: checkedProjectList.splice(findCheckBoxIndex, 1);
}

//渲染選取專案
showCheckedList.textContent = checkedProjectList.join("、");
});
});
}
  1. 完成!


四、結尾

這是我原本發布在六角學院 2023 JS 直播班討論區分享的心得,現移植到部落格存放,謝謝 Paul 提供了改寫優化。

五、資料參考

分享至
成為作者繼續創作的動力吧!
© 2024 vocus All rights reserved.