PJCHENder 未整理筆記

[JS30] Day15: LocalStorage and Event Delegation

2017-09-26

[JS30] Day15: LocalStorage and Event Delegation

@([WesBos] JS30)[JavaScript]

keywords: 客制化checkbox, event delegation, custom checkbox, localStorage

HTML 部分

蠻單純的,扣掉一張 SVG 圖片的話,結構大概長這樣:

1
2
3
4
5
6
7
8
9
10
11
<div class="wrapper">
<h2>LOCAL TAPAS</h2>
<p></p>
<ul class="plates">
<li>Loading Tapas...</li>
</ul>
<form class="add-items">
<input type="text" name="item" placeholder="Item Name" required>
<input type="submit" value="+ Add Item">
</form>
</div>

CSS 部分

客制化 Checkbox

這裡有一個蠻有趣的技巧,可以讓我們自訂 checkbox 樣式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* 客制化 checkbox
* 這裡的 checkbox 先把它 display:none
* 再利用 pseudoElement 的方式添加假的 checkbox
**/
input[type="checkbox"] {
display: none;
}
input[type="checkbox"] + label:before {
content: '⬜️';
margin-right: 10px;
}
input[type="checkbox"]:checked + label:before {
content: '🌮';
}

自訂checkbox @ PJCHENder CodePen

JS 部分

先選取我們之後會用到的元素

1
2
const addItems = document.querySelector('.add-items')
const itemLists = document.querySelector('.plates')

使用 <form> HTMLFormElement

因為 JS 再操作 DOM 的時候很方便,因此有些時候沒有真的要把表單送出(submit)的話,就都沒有把 <input> 外面在包一層 <form>。但是使用 <form> 包住有幾個好處,一個是我們在監聽的時候可以監聽 submit 事件。更重要的是 HTMLFormElement 提供了我們 reset() 這個方法,讓我們可以一次把表單的內容全部清空。

但是使用 <form> 的話要記得把 button 的 type 設成 submit (<button type="submit"> );另外,要記得在按鈕點擊時要 preventDefault() ,否則會真的把表單送出(<form> 沒有設定location 的話變成重新整理)。

另外,通常一個 form 裡面只會有一個 submit button ,如果你有多個 button 要做不同功能的話,form + submit 的方式或許就不適合。

HTMLFormElement @ MDN

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

/**
* STEP1: 添加 item 項目
* 這裡監聽的是 submit 事件,好處是當使用者按 enter 也會觸發
**/

addItems.addEventListener('submit', addItem)
function addItem (e) {
e.preventDefault() // 使用 preventDefault 避免真的 submit
let text = document.querySelector('[name="item"]').value
let item = {
text,
done: false
}
plateItems.push(item)

/**
* HTMLFormElement 可以使用 reset() 方法來清空表單
**/
e.target.reset()
}

使用 localStorage

如果沒有資料庫,又想要保存資料(本地端),那麼使用 localStorage 會是個好幫手,localStorage 的使用相當簡單,唯一要注意的是,如果不小心把瀏覽器的快取清空,那麼這些儲存的內容也會一併消失。

localStorage 儲存資料的時候就是簡單的 key: value paired,但要注意的是 value 只能是字串,所以如果你是要存物件到 localStorage 的話,記得要先把物件 JSON.stringify(),讓物件變成JSON 字串的格式儲存起來。

當要取出資料的時候,再透過 JSON.parse()把原本的 JSON 字串轉換成 JS 物件。

以下是 localStorage 的 API:

1
2
3
localStorage.setItem('<keyName>', '<value>');    // 儲存到 localStorage
localStorage.getItem('<keyName>'); // 取得 localStorage 內容
localStorage.removeItem('<keyName>'); // 移除 localStorage 項目

Using the Web Storage API @ MDN

如果想要檢視 localStorage 的內容,可以打開 Chrome Dev Tool 選擇 Application 欄位就可以看到了。

:::warning
localStorage 的使用相當簡單,唯一要注意的是,如果不小心把瀏覽器的快取清空,那麼這些儲存的內容也會一併消失
:::

產生 HTML 清單

我們用一個 function 來把陣列當中的內容轉成 HTML Element。這裡要留意的是 HTML 的 checkbox 中有一個 checked 屬性,當這個屬性存在時,這個 checkbox 就會被勾選,即使設成 checked=false 也一樣。

1
2
3
<!--  只有出現 checked attribute 則 checkbox 都會被勾選  -->
<input type="checkbox" checked>
<input type="checkbox" checked="false"> <!-- 一樣會勾選 -->
1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* STEP2: 根據 plateItems 產生 HTML Element
* 在 checkbox 中只要給 checked 都會是勾選狀態,寫 checked="false" 無用
**/
function populateHTMLList (plateItems, targetElement) {
targetElement.innerHTML = plateItems.map((item, i) => {
return `
<li>
<input type="checkbox" id="item${i}" data-index="${i}" ${item.done ? "checked" : ""}>
<label for="item${i}">${item.text}</label>
</li>
`
}).join('')
}

只要有 checked 屬性都會讓 checkbox 被勾選
<input type="checkbox" checked="false">

Event Delegation 和 Element.matches()

最後在點選清單時我們可以切換資料狀態,傳統的作法上,我們會根據 DOM 把元素綁定事件,像是這樣:

1
2
3
4
const lists = document.querySelectorAll('.plates li')
lists.forEach(list => {
list.addEventListener('click', clickHandler)
})

但這麼做會有一個問題,當我們新增 list 時,這個新增的 list 就不會被綁定到事件,因此這裡我們要使用 event delegation 的作法,我們要監聽 ul 的 click 事件,因為當 ul 裡面的 li 被點擊時一樣會觸發這個這個 click 事件,然後我們可以透過 e.target 來篩選和判斷被點擊的元素是誰。

另外針對 element 除了我們可以使用 element.querySelector<'selectorString'> 來選擇元素外,我們也可以使用 element.matches(<'selectorString'>) 這個方法,來看看根據這個 selectorString 能不能選擇到該元素,結果會回傳 Boolean:

1
2
3
4
5
6
7
8
9
10
11
/**
* STEP4: 監聽 list 的點擊事件,當被點擊時切換 checked 狀態並存到資料庫中
**/
itemLists.addEventListener('click', toggleDone)
function toggleDone (e) {
if(!e.target.matches('input')) return
let index = e.target.dataset.index
plateItems[index].done = !plateItems[index].done
localStorage.setItem('plateItems', JSON.stringify(plateItems))
// populateHTMLList(plateItems, itemLists)
}

完成作品

Day15: LocalStorage and Event Delegation @ PJCHENder CodePen

參考資料

掃描二維條碼,分享此文章