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 備份。