使用 JavaScript 製作分頁效果 - 順便造個輪子做成套件
Fri Nov 12 2021
前端開發# 這篇是我在 2021-11-04 受邀在六角學院直播分享的主題
目標是在不使用任何框架與外部套件的情況下,用純粹的 JavaScript 打造出分頁的效果,而為了完整性、復用性的考量下,我幾乎是做了一個與 UI 解耦的分業管理套件了。
雖然有些地方準備不足,不過我已經盡力把我認為最實用的內容分享出來,也希望能為你帶來一些啟發。
# 綱要
- 設計思路
- 知識點補充
- DOM render 處理
- Pagination 處理
- history API
- 延伸討論
- 問題與討論
# 頁面 Demo
# 設計思路
為什麼要需要分頁?
要達成分頁效果的常見做法:
- 一次性取得所有資料
- 批次獲取各頁面資料
# 分頁獲取的前提
- API 有回傳資料總筆數
# API 資料取得
# OffStreetParkingCityAPI : 各縣市[路外]停車場資料
GET /v1/Parking/OffStreet/CarPark/City/{City}
取得指定[縣市]之停車場基本資料
https://tdx.transportdata.tw/api-service/swagger
# 狀態管理與頁面渲染
# 知識點補充
- Event Bubbling
- Dataset
- ES module
- 解構賦值
- 展開運算子
- 閉包 Closure
# Event Bubbling
paginationElement.addEventListener('click', e => {
const target = e.target.dataset
const currentTarget= e.currentTarget.dataset
})
e.target
e.currentTarget
# Dataset
<a
href="?page=1"
onclick="return false"
data-action="setPage"
data-value="1"
>
1
</a>
paginationElement.addEventListener('click', e => {
const { action, value } = e.target.dataset
action === 'setPage' // true
value === '1' // true
})
# ES module
<script type="module" src="./simple_all.js"></script>
// simple_all.js
import { getParkingApi } from './module/service.js'
// module/service.js
export function getBikeApi() {}
export function getParkingApi() {}
export default { getBikeApi, getParkingApi }
# 解構賦值
function foo({ a = 1, b = 1, c }) {}
foo({
a: 10,
c: () => {},
})
剛剛 Dataset 講到的範例
paginationElement.addEventListener('click', e => {
const { action, value } = e.target.dataset
action === 'setPage' // true
value === '1' // true
})
# 展開運算子
Math.max(1, 2, 3) === Math.max([1, 2, 3]) //false
Math.max(1, 2, 3) === Math.max(...[1, 2, 3]) //true
const arr1 = [1, 2, 3]
const arr2 = arr1
arr1 === arr2 // true
const arr1 = [1, 2, 3]
const arr2 = [...arr1]
arr1 === arr2 // false
# 閉包 Closure
function foo(data) {
const getData = () => data
const setData = n => data = n
return {getData, setData}
}
const { getData, setData } = foo(10)
getData() //10
setData(50)
getData() //50
# DOM render 處理
- 建立渲染函式
- 建立共用組件
建立渲染函式
export function renderFunction(bindDom, renderContent, contentData = []) {
if (contentData.length) bindDom.innerHTML = updateElement([...contentData])
function updateElement(newData) {
contentData = newData
bindDom.innerHTML = newData.map(renderContent).join('')
}
return updateElement
}
產生頁面結構的函式
export function createCard({ Telephone, CarParkName, Description, Address }) {
return /*html*/ `
<article class="flex flex-col shadow m-4 max-w-xs">
<div class="bg-white flex flex-col justify-start p-6">
<p class="text-blue-700 text-sm font-bold uppercase pb-4">${Telephone}</p>
<h2 class="text-3xl font-bold hover:text-gray-700 pb-4">${CarParkName?.Zh_tw}</h2>
<p class="text-sm pb-3">${Address}</p>
<p class="uppercase text-gray-800 hover:text-black">${Description}</p>
</div>
</article>
`
}
# 宣告式與命令式的程式設計
明明是簡單的 innerHTML,為什麼要多此一舉?
# Pagination 處理
- 建立 create 函式儲存 Pagination 需要的狀態
- create 函式回傳操作 Pagination 需要的方法
export default function createPagination({ pagesLength = 1, currentPage = 1, onChange }) {
const setPage = n => {...}
const setPagesLength = (newPagesLength, newCurrentPage) => {}
const getPages = () => {...}
const getCurrentPage = () => currentPage
const nextPage = () => setPage(currentPage + 1)
const previousPage = () => setPage(currentPage - 1)
const firstPage = () => setPage(1)
const lastPage = () => setPage(pagesLength)
return {
setPage,
setPagesLength,
getPages,
getCurrentPage,
nextPage,
previousPage,
firstPage,
lastPage,
}
}
# history API
讓換頁功能不只是虛有其表
# history.pushState
history.pushState(state, title [, url])
history.pushState({ page: 10 }, '', '?page=' + 10)
# 監聽使用者「上一頁」、「下一頁」
window.addEventListener('popstate', ({ state }) => {
const { page = 1 } = state || {}
updateElements(pagination.setPage(page))
})
# 延伸討論
- 資料暫存
- 資料更新
- 每頁筆數
- 搜尋篩選
# Pagination-UI-LESS 套件
- Repo: Pagination-UI-LESS (opens new window)
- CDN: https://cdn.jsdelivr.net/npm/pagination-ui-less (opens new window)
# npm install
npm i pagination-ui-less
or
yarn add pagination-ui-less
# 使用 CDN
<script src="https://cdn.jsdelivr.net/npm/pagination-ui-less"></script>
paginationUiLess({
pagesLength: 1,
currentPage: 1,
onChange: () => {}
})