自主練習「純手工pagination」技術記錄
2021-04-15 19:29 JavaScript
總結
練習為 pagination 加上 JavaScript,使其可與使用者互動。
- 使用到的技術
- DOM Traversing: parent, siblings
- 新學習到的概念
pointer-events: noneparseInt()與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
Numberis 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()處理的手法並不一樣