Pagintaion 套件 + Axios API 請求範例

一、官網資源
pagination 官網:https://pagination.js.org/docs/index.html
可由此處下載 css 原始檔客製 https://pagination.js.org/docs/index.html#Theme
二、CDN 安裝
頁碼 pagination.scss(非必要引入,也可以從官網下載原始檔更改)
1
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/paginationjs/2.6.0/pagination.min.css">
|
jQuery Import(使用 slim.min.js 版本會出錯)
1
| <script src="https://code.jquery.com/jquery-3.7.0.min.js"></script>
|
頁碼 pagination.js
1
| <script src="https://cdnjs.cloudflare.com/ajax/libs/paginationjs/2.6.0/pagination.min.js"></script>
|
Bootstrap 5 CSS(建立表格用,非必要)
1
| <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. 目標功能
- 頁碼與資料渲染
- 簡單篩選功能
- 點選至多 3 筆資料
3-2. 示範步驟
- 規劃版位
- 變數、原始資料宣告、按鈕綁定
- 撰寫初始化渲染函式 renderData
。原始資料請求
。篩選資料生成
。頁碼生成
- 拆解元件
- 加入點選物件功能
3-3. 實作開始
規劃版位 html
最主要是先建立好渲染資料和頁碼生成位置,完整 html 可見 codepen。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| <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>
|
1 2
| <!-- 頁碼 --> <div id="pagination-Pages" class="d-flex justify-content-end mt-3"></div>
|
基本前置設定
於 JavaScript 準備 API 路徑來源宣告,以及將 DOM 元素綁定監聽。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| 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 renderData() }) })
|
Pagintaion.js docs/commenlyused 第三項提到 ajax 的請求格式如下。並且套件支援以 jQuery ajax 操作 API 來源資料,如果請求成功則使用 success 接收並繼續執行,請求失敗則跳到 error。
1 2 3 4 5 6 7 8 9
| dataSource: function(done){ $.ajax({ type: 'GET', url: '/test.json', success: function(response){ done(response); } }); }
|
建立初始化 function renderData 基本架構
這時就可以組合出大概的渲染函式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| function renderData() { $(function () { (function (name) { let container = $("#pagination-" + name); $.ajax({ url: _url, success: function (data) { ... }, error: function (error) { console.error(error); } }); })("Pages"); }); }
renderData();
|
儲存 API 請求回傳的原始資料
使用 console 測試,發現我們需要撈回的資料被放在 data 中的 data 屬性,所以 data.data 是我們所需要的,所以宣告一個變數儲存它。
1 2 3
| success: function (data) { const allData = data.data },
|
撰寫篩選按鈕動作
因為我們在 html 中寫了三個按鈕,讓它可以做區域篩選,並且上面已經先宣告了變數 selectedFilter,預設值為「全部」。當 selectedFilter 變數的值已經不是「全部」,則將 filteredData 重新賦值成符合篩選條件的資料。
1 2 3 4
| let filteredData = [] selectedFilter !== '全部' ? (filteredData = allData.filter((item) => item.name === selectedFilter)) : (filteredData = allData)
|
將按鈕加入點擊監聽
測試上述的 selectedFilter 有沒有因點擊而更改。因為每次點擊按鈕都要重新利用 renderData 函式處理,所以我們在執行函式中再度呼叫 renderData。
1 2 3 4 5 6
| btnFilter.forEach((btn) => { btn.addEventListener('click', function () { selectedFilter = btn.value renderData() }) })
|
目前我們的 renderData 函式全部是以下狀態,並且加入 console 測試 selectedFilter 和 filteredData 值是否有跟著按鈕一起改變。確定成功就可以將 console 刪除。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| function renderData() { $(function () { (function (name) { let container = $("#pagination-" + name); $.ajax({ url: _url, success: function (data) { const allData = data.data
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"); }); }
|
計算頁碼與設定
接著用篩選後的資料計算可以生成幾頁頁碼,以下程式碼可以接在剛才 filteredData console 的下面。回到套件 docs/methods,發現我們可以透過 container.pagination({ ... }) 設定套件呈現細節。以下屬性詳細請參照官網文件。
1 2 3 4 5 6 7 8 9
| container.pagination({ dataSource: filteredData, totalNumber: filteredData.length, pageSize: 5, showPageNumbers: true, showPrevious: true, showNext: true })
|
渲染頁碼與資料
回到官網文件 docs/dataSources,container 中的 callback 屬性可用於插入自訂的 html,所以我們可以將上面 html 要渲染的資料 html 模板貼下來。input 中的各項屬性視原始資料內容加上獨特值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| 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) } })
|
最後補上執行 renderData,讓網頁一開啟就運行。就完成目標功能的第一二項,已經可以作頁碼切換與按鈕篩選資料了。
1 2 3
| function renderData(){...略}
renderData();
|
拆元件
因為目前 function renderData 內的程式碼冗長,所以先來做拆分整理。我們剛才在 function renderData 內的程式碼大致可以分為
- 基礎架構
- 資料依據按鈕篩選:需要輸入資料
allData,並生成 filteredData。
- 將篩選過的資料計算頁碼:需要輸入這兩筆資料
filteredData、container。
- 渲染頁碼與資料:需要輸入資料
res。
依據以上分類可以拆成四個函式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
| function renderData() { $(function () { ;(function (name) { let container = $('#pagination-' + name) $.ajax({ url: _url, success: function (data) { const allData = data.data const filteredData = filterData(allData) handlePagination(filteredData, container) }, error: function (error) { console.error(error) } }) })('Pages') }) }
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, 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) }
|
加入點選功能的 checkbox
先從渲染 checkbox 開始,修改剛才建立好的 function renderPagination 中的 dataHtml。
1 2 3 4 5 6 7 8 9
| $.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>` })
|
為 checkbox 加入辨識值
每一個 checkbox 都需要被辨識,觀察 $.each(res, function (index, item) 中的 item 可以發現 item.projectSEQ 這個唯一值可以被使用,所以我們可藉由 data-*(MDN) 的方式加入此值。
1 2 3 4 5 6 7 8 9 10 11
| $.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>` })
|
儲存被點選的 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 型別才不會之後影響判斷。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| function handleCheckProject() { const checkBoxes = document.querySelectorAll("input[type='checkbox']");
checkBoxes.forEach(checkbox => { checkbox.addEventListener("change", () => { const projectSeq = checkbox.getAttribute("data-seq"); checkedProjectList.push(+projectSeq)
showCheckedList.textContent = checkedProjectList.join("、"); }) }) }
|
回到 function handlePagination 函式,在 callback 處加入 handleCheckProject(),讓它能夠執行。
1 2 3 4 5 6 7 8 9 10 11
| function handlePagination(filteredData, container){ ...略
callback: function (res, pagination) { ...略
handleCheckProject(); } }); }
|

加入判斷當前 checkbox 是否已被點選,生成樣板
回到 function renderPagination 函式,在進入 each 之後,執行當前 item.projectSEQ 值是否存在於 checkedProjectList 變數中。includes 會返回 true/false(MDN)。
checkbox (MDN)文件中提到如果是被勾選狀態,則會被加入 checked 在 HTML 中標示,所以也在 dataHTML 中使用三元運算子根據剛才 isChecked 的結果判斷是否加入 checked。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| 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) }
|
完成後的資料表即使換頁再回上一頁,也能繼續顯示相同 checkbox 勾選狀態了。但還有一些小錯誤,即使 checkbox 取消,或是再次點選都會不斷累積相同 seq 編號在 checkedProjectList 變數中。
限制選取資料數量
我們要解決上述的 bug 以及完成最後一個任務:限制最多只能選取 3 筆資料。回到 function handleCheckProject,我們要在這裡根據 checkedProjectList 變數內的資料數量判斷是否還能再繼續點選動作。
■ 我們可能有幾種情境及對應執行:
(1)已達最多選取數 → 禁止用戶點選,但因為 checkbox 沒有 disable 屬性,所以我們只能讓 checked 強制取消
(2)未達最多選取數 → 加入清單
(3)清單中已有目前點選的 seq 編號 → 判斷為取消點選,從清單中移除
(4)清單中沒有目前的 seq 編號 → 加入清單
■ 綜合以上情境狀況,我們把要情境總結成
(1)已達最多選取數,並且清單中不存在此 seq → 取消當前 checkbox 勾選狀態
(2)未達最多選取數 → 根據清單是否存在 seq,執行加入清單或刪除該值的動作 ,
以上不管是判斷清單中是否存在當前 seq,或是執行刪除,都會需要知道該 seq 在清單中的位置,所以可以使用 indexOf 尋找。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| function handleCheckProject() { const checkBoxes = document.querySelectorAll("input[type='checkbox']") const maxChecked = 3
checkBoxes.forEach((checkbox) => { checkbox.addEventListener('change', () => { const projectSeq = checkbox.getAttribute('data-seq')
const findCheckBoxIndex = checkedProjectList.indexOf(+projectSeq)
if ( checkedProjectList.length === maxChecked && findCheckBoxIndex === -1 ) { checkbox.checked = false } else { findCheckBoxIndex === -1 ? checkedProjectList.push(+projectSeq) : checkedProjectList.splice(findCheckBoxIndex, 1) }
showCheckedList.textContent = checkedProjectList.join('、') }) }) }
|
完成!
四、結尾
這是我原本發布在六角學院 2023 JS 直播班討論區分享的心得,現移植到部落格存放,謝謝 Paul 提供了改寫優化。
五、資料參考
Paul 版本 codepen:https://codepen.io/paul-1997/pen/xxMqowP
本文原發表於 Vocus,此篇為個人 blog 備份。