自主練習「純手工pagination」技術記錄
2021-04-15 19:29 JavaScript
總結
練習為 pagination 加上 JavaScript,使其可與使用者互動。
- 使用到的技術
- DOM Traversing: parent, siblings
- 新學習到的概念
pointer-events: none
parseInt()
與Number()
的差異
成品
See the Pen pagination practice by Charlie (@Charlie7779) on CodePen.
環境
Google Chrome: 90.0.4430.72 (Official Build) (64-bit)
Bootstrap: 5.0.0-beta3
os: Windows_NT 10.0.18363 win32 x64
流程
- 使用Bootstrap: Pagination的 HTML 原始碼,並移除不需要的部分
aria-label
在練習中用不到,移除<li>
使用 JavaScript 產生,全部移除;加上備註,提示內容會由 JavaScript 處理- 整理完畢後,HTML 部分的原始碼如下
<nav>
<ul class="pagination justify-content-center" id="pagination">
<!-- generated by JavaScript -->
</ul>
</nav>
- 粗略分解任務:「使用者與 pagination 互動時,會發生什麼事情?」
- 點選 previous 或 next 來前進、後退
- 直接點選頁數來進行換頁
- 抵達頁數的「頭」或「尾」後,繼續點選 previous 或 next 不會有任何事情發生
- 換頁後,畫面須呈現相對應的內容
- 將以上任務流程精緻化
- 「點選 previous 或 next 來前進、後退」與「直接點選頁數來進行換頁」
- 需監聽滑鼠點擊事件
- 需判定滑鼠點擊的目標是 previous、next 或頁數
- 要判定使用者點擊 pagination 後會在「哪一頁」
- previous 或 next 與頁數頭尾互動事件:
- 使用者抵達首尾頁後,點擊 previous 或 next 不可繼續前進或後退
- 使用者不在首尾頁時,previous 或 next 應恢復功能
- 換頁後,畫面須呈現相對應的內容
- 要判定使用者點擊 pagination 後會在「哪一頁」
- 使用程式碼滿足以上列出的需求
JavaScript 原始碼
全域變數
pageCount
:儲存總頁數的變數;除了直接賦值外,也可承接其他 function 回傳的值。比如在 ALPHA Camp 的練習情境中,pageCount
可承接「電影總數(80 筆)除以每一頁的電影數量(12 筆)後無條件進位」此情境的總頁數數量(pagination 應展示 7 頁)initialPage
:畫面載入時,預設顯示的頁面;在本練習中預設顯示第 1 頁
dynamicallyGeneratePage (page)
目的:根據傳入的總頁數(page)數量來產生 pagination;比如傳入 8,即要產生 8 頁的 pagination
- 第 3 行:加入 Previous 按鈕。而因為起始頁是第一頁(
initialPage
設定為 1),故 Previous 按鈕預設是不會有任何功能的(不可繼續前進),所以在<li>
加上.disabled
- 第 4-8 行:加入頁數,頁數根據傳入
dynamicallyGeneratePage()
的參數決定。並將initialPage
加上.active
,標記其為起始頁 - 第 9 行:加入 Next 按鈕
- 第 10 行:將第三行到第九行的內容放進
pagination
(document.querySelector('#pagination')
)中 - 第 11 行:設定
display
(document.querySelector('#display')
)內容 - 關於
data-page
:使用dataset為每一個<a>
元素加上data-page
,讀取data-page
的值即可判定該<a>
為 Previous、Next 或頁數按鈕
innerHTMLContent (page)
目的:根據傳入的頁數,產生對應的畫面內容
- 第 4 行:根據傳入的
page
來產生相對應的頁數 - 第 5 行:使用Lorem Picsum透過 id指定特定圖片的服務,根據傳入的
page
來產生圖片,達成換頁換圖的效果 - 第 7 行:
innerHTMLContent()
回傳的值可直接賦予innerHTML
paginationStatusUpdate (event)
目的:根據使用者點擊的目標,更新 pagination 的狀態;並呼叫innerHTMLContent()
來修改畫面內容
- 第 2 行:
const pageData = event.target.dataset.page
- 點擊事件發生後,抓取「被點擊的目標」其
data-page
的值,每一個<a>
元素的data-page
是透過dynamicallyGeneratePage()
產生的 - 點擊到 Previous 時,
pageData
為p
;點擊到 Next 時,pageData
為n
- 點擊到處於
disabled
狀態的 Previous 與 Next 時,pageData
為undefined
,理由如下:- 參考 Bootstrap 5.0.0-beta3 的原始碼可得知
.page-item.disabled .page-link
的設定為
注意這段選取器的作用對象是.page-item.disabled 下的「.page-link」,而 HTML 結構如下.page-item.disabled .page-link { color: #6c757d; pointer-events: none; background-color: #fff; border-color: #dee2e6; }
所以<li class="page-item disabled"> <a class="page-link" href="#" data-page="p">Previous</a> </li>
pointer-events: none;
實際作用的對象是<a>
- 根據MDN 的規格,
pointer-events
設定為none
時,該元素(在本練習中為<a>
)無法成為滑鼠游標點擊事件的目標(原文:The element is never the target of pointer events.) - 所以在 Previous 與 Next 處於
disabled
時,<a>
不會成為滑鼠游標點擊事件的目標;在這樣的情況下,游標點擊事件會觸發的目標會變成該元素的親元素(在本練習中為<li>
)(原文:In these circumstances, pointer events will trigger event listeners on this parent element as appropriate on their way to/from the descendant during the event capture/bubble phases.) - 而
<li>
沒有設定pageData
,所以當我想要取<li>
的pageData
的值的時候,我會得到undefined
- 參考 Bootstrap 5.0.0-beta3 的原始碼可得知
- 結論:
pageData
的值有以下可能性undefined
:代表 Previous 與 Next 處於disabled
的狀態p
或n
:當 Previous 與 Next 不處於disabled
時,點擊 Previous 或 Next 會讓pageData
的值為p
或n
- 頁數的數字:當使用者點擊頁數時,
pageData
的值就是該頁數的數字,但資料型態會是String
- 點擊事件發生後,抓取「被點擊的目標」其
- 第 3 行:
const activePage = document.querySelector('#pagination li.active')
- 取「處於
active
狀態」的<li>
- 取「處於
- 第 4 行:
const activePageNumber = activePage.firstElementChild.dataset.page
- 會取得點擊事件發生時,處於
active
狀態的<li>
其子元素<a>
的data-page
資料 - 舉例:若目前
.active
的是第 1 頁,而我點了第 4 頁,activePageNumber
的值會是 1
- 會取得點擊事件發生時,處於
- 第 5-6 行:固定取得 Previous
<li>
與 Next<li>
,因為.disabled
與.active
是在<li>
上操作的 - 第 8 行開始的
switch (pageData)
:case undefined
:代表使用者在 Previous 或 Next 處於disabled
時點擊這兩個按鈕,這時不應該再繼續執行翻頁的動作,故直接return
,什麼事都不發生case "p"
:- 代表使用者點擊 Previous 時,此按鈕不處於
disabled
狀態,所以 Previous 應該帶領使用者前往上一頁 - 第 12 行代表「點擊 Previous 時從最後一頁離開」,這時 Next 就不應繼續處於
disabled
狀態,故需移除.disabled
- 第 14-16 行:代表「點擊 Previous 時正在通過第二頁」,而從第二頁往前移動後,就會位在第一頁,位在第一頁後就不可繼續往前翻頁了,所以 Previous 要加上
.disabled
,進入disabled
狀態 - 第 18-19 行:若點擊 Previous 時,Previous 不符合第 12-16 行列出的條件,就進行「移除目前處於
active
頁數的.active
,並為前一頁加上.active
」 - 第 20 行:因為移動到前一頁了,所以
display
的內容也要更新,換成前一頁(activePageNumber - 1)的內容
- 代表使用者點擊 Previous 時,此按鈕不處於
case "n"
:換頁的邏輯與 Previous 相反- 第 23 行代表「點擊 Next 時從第一頁離開」,這時 Previous 就不應繼續處於
disabled
狀態,故需移除.disabled
- 第 25-27 行:代表「點擊 Next 時正在通過倒數第二頁」,而從倒數第二頁往後移動後,就會位在最末頁,位在最末頁後就不可繼續往後翻頁了,所以 Next 要加上
.disabled
,進入disabled
狀態 - 第 29-31 行:若點擊 Next 時,Next 不符合第 23-27 行列出的條件,就進行「移除目前處於
active
頁數的.active
,並為下一頁加上.active
」
- 第 23 行代表「點擊 Next 時從第一頁離開」,這時 Previous 就不應繼續處於
default
:代表pageData
不屬於上述任何一種狀態,亦即使用者直接點選了頁數- 第 34 行:移除
activePage
的active
狀態 - 第 35 行:為
event.target
(被滑鼠點選到)的parentElement
(即是<li>
)加上active
狀態 - 第 36 行:更新
display
的內容 - 第 38-40 行:使用者點選到最末頁時,Next 要變為
disabled
狀態 - 第 42-44 行:使用者點選到第一頁時,Previous 要變為
disabled
狀態
- 第 34 行:移除
bonus track:parseInt()
與Number()
的差別
- 在過去的專案與練習中,習慣直接使用
parseInt()
來將字串解析(parsing)為數字,本次改為使用Number()
來將字串型變(type converting)為數字 - 雖然這兩者都可以回傳我需要的資料,但好奇這其中是否有差異,而經過搜尋後得知:
Number()
做的事:convert- Annotated ECMAScript 5.1: When
Number
is called as a function (rather than as a constructor), it performs a type conversion. - MDN: Values of other types can be converted to numbers using the
Number()
function.
- Annotated ECMAScript 5.1: When
parseInt()
做的事:parse,而非 convert- MDN: The
Number.parseInt()
method parses a string argument and returns an integer of the specified radix or base.
- MDN: The
- 結論:雖然都可以透過傳入
String
得到Number
,但parseInt()
與Number()
處理的手法並不一樣