成品連結:LocalStorage、操作前程式碼、完成後程式碼
如標題揭示,今天的主題是 localStorage 以及如何使用它。
使用 localStorage 主要的目的是當瀏覽器重新整理時原畫面的資料依舊能夠存在。舉例來說,今天我們做了 to-do list,但不希望關閉視窗後待辦清單消失,這時候就是使用 localStorage 的絕佳時機了!
這天開始會附上操作前的程式碼,提供給想要一起練習的朋友,之前幾天未附上抱歉了...
從操作前程式碼可以看到已經預先宣告了幾個變數 addItems
、itemsList
以及一個要用來存放新增項目但目前沒有內容的 array
一開始先從新增項目開始,這裡目標是當按下 + Add Item 按鈕時將輸入內容加入至清單
首先先設定監聽事件。從 addItems
可以看到綁定元素是表單本身而不是按鈕而已;這裡用的事件是 submit
addItems.addEventListener('submit', addItem);
也就是當按下 + Add Item,表單會自動提交
items
這裡會發現當按下新增項目時瀏覽器會重整,如果你試著在 addItem
內印出東西會一瞬間跳掉;這是因為 submit
預設動作會提交表單並重新整理,如果要畀秒這個情況,要寫入 e.preventDefault()
function addItem(e) {
e.preventDefault();
}
接著要把使用者輸入的資料輸入 items
當中,這裡使用一個新的 object 儲存資料,而 object 中除了要記錄輸入內容,還要記錄是否被選取(也就是被加到清單內後是否被打勾),預設是 false
,並把資料推入 items
中
function addItem(e) {
e.preventDefault();
const item = {};
const inputText = document.querySelector('input[type="text"]');
item.text = inputText.value;
item.done = false;
items.push(item);
}
接著重點來了,我們要把 items
內的資料存入 localStorage,使用的方法是 localStorage.setItem(..)
這裡要注意的是存入的內容只能是 string,所以要先使用 JSON.stringify(..)
先將 items
轉型別
若不熟悉 localStorage 語法請參考下方 MDN 連結
function addItem(e) {
e.preventDefault();
const item = {};
const inputText = document.querySelector('input[type="text"]');
item.text = inputText.value;
item.done = false;
items.push(item);
localStorage.setItem('items', JSON.stringify(items));
}
最後資料新增了,要記得把輸入匡內容清空。有兩種方法,一種是 inputText.value = ''
,而另一種是使用表單的方法 this.reset()
(這裡 this
指的是表單)
完成後的 addItem
會是這樣
function addItem(e) {
e.preventDefault();
const item = {};
const inputText = document.querySelector('input[type="text"]');
item.text = inputText.value;
item.done = false;
items.push(item);
localStorage.setItem('items', JSON.stringify(items));
this.reset();
}
新增新的 function 來處理渲染畫面,也就是將 items
中的內容印出來
function render() {
// code here
}
資料來源可以直接取用 localStorage 的資料,但如果 localStorage 內沒有資料呢?加上之後其他 function 可能與會取用,所以我們要先修改一下 items
的值。
目前的狀況是,當網頁重整時 items
會變成空 array,所以要修改一下
const items = JSON.parse(localStorage.getItem('items')) || [];
也就是說當網頁讀取時,先看一下 localStorage 中有沒有資料,如果有就取用、但假如沒有就給一個空 array
有了資料來源接下來使用迴圈分別印出 HTML 的內容,並相加至變數 str
當中,最後加至 itemsList.innerHTML
function render() {
let str = '';
items.forEach((item, index) => {
let checked = item.done ? `checked` : '';
str += `
<li>
<input type="checkbox" data-index="${index}" id="item${index}" ${checked}>
<label for="item${index}">${item.text}</label>
</li>`;
});
itemsList.innerHTML = str;
}
可以看到在 forEach
當中多了一個變數 checked
,這個變數其實是要判斷選單 itemsList
內的選項是否已被勾選、或是 item.done
是 true
的狀況下會在 input[type="checkbox"]
的 tag 中加入 required
的屬性。這裡的寫法是指當 item.done === true
時 checked = checked
,如果 item.done === false
則 checked = ''
(空 string)
到這裡渲染畫面的 function 已大致完成,但我們要在剛剛的 addItem
最後加入 render()
,也就是在每次新增項目時一併渲染畫面。
function addItem(e) {
// 上方原 code 省略
render();
}
接下來要處理的是當渲染在網頁後並勾選時,需要做到:
item.done = false 或是 true
)這裡你可能會想,既然要監聽有沒有勾選,那就是監聽 input[type="checkbox"]
囉?
不對。你會發現歲然設定監聽,但即便勾選了還是沒也任何反應,這是因為是當你設定監聽時列表內或許根本沒有項目;或是原本有,但當你新增項目後新的項目一樣沒有反應。
因此我們要監聽的元素應該是原本就在,而且無論如何都會存在的元素。沒有錯!就是監聽 itemsList
。
input
、'label'... 等等還有 li
?但當你實際使用時卻發現不是每次都只選到 input[type="checkbox"]
,有 label
,有時甚至選到 li
。原因是 itemsList
內含有許多元素,而 input
與 label
甚至是綁在一起的;為確保每次都是選到 checkbox,我們可以寫 if (e.target.nodeName !== 'INPUT') return;
或是 if (!e.target.matches('input')) return;
,意思是當選到的元素不是 input
就 return,如果是才會執行下面的程式碼
function markDoneToggle(e) {
if (!e.target.matches('input')) return;
// 若選到的是 input 則執行以下程式碼
// code here
}
接著如同在 render
做的判斷,當使用者勾選或取消勾選時要做出相對應的動作:
items
內項目中的 done
的值,如原本是 true
則更改成 false
,反之更改成 true
render
)簡單來說就是先建立一個變數 isChecked
來判斷是否勾選,接著更新 items
以及 localStorage 的內容,最後更新畫面
function markDoneToggle(e) {
if (!e.target.matches('input')) return;
let isChecked;
isChecked = e.target.checked ? true : false;
items[e.target.dataset.index].done = isChecked;
localStorage.setItem('items', JSON.stringify(items));
render();
}
到這裡就大致完成啦!但成品我有再新增全選、全取消勾選以及清空列表(包含 localStorage)的功能,算是給你的作業啦!如果途中有問題可以看我的程式碼或問我喔~