製作一個待辦清單,該清單所儲存的待辦事項即使網頁關掉再重開,記錄仍是保存的。
一個簡單的待辦清單架構如下
<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>
若暫時不考慮永久儲存的功能,我們先讓待辦清單能夠順利顯示、以及能夠手動增加新項目就好。邏輯如下:
先來看看如何實現第一點與第二點:
// 儲存清單項目的陣列
let items = [];
// 網頁上裝著清單項目的元素 <ul>
const itemsList = document.querySelector('.plates');
// 顯示清單項目
function populateList(plates = [], platesList) {
platesList.innerHTML = plates.map((plate, i) => {
return `
<li>
<input type="checkbox" data-index="${i}" id="item${i}" ${plate.done ? 'checked' : ''}>
<label for="item${i}">${plate.text}</label>
</li>
`;
}).join('');
}
// 網頁載入後先執行
populateList(items, itemsList)
在 populateList
函式中有兩個參數,第一個參數為待辦清單的資料,預設參數為空陣列。預設參數允許我們在缺乏輸入參數或是輸入參數值為 undefined
的時後使用預設的值充當參數,適當使用能夠避免一些意外狀況。
第二個參數為轉換格式後的清單資料輸出的對象,這裡為 <ul>
標籤。
在函式內部,我們只是單純將資料轉換為HTML格式的標籤組合而成的字串並塞回輸出對象 <ul>
內部而已。
整個流程為,讀取 items
陣列內記錄的待辦事項資料,轉換成HTML格式後貼到元素內,如此而已。
完成函數後,在最下方執行 populateList(items, itemsList)
到此,網頁一載入就會先將所有待辦事項顯示到畫面中。 接下來要實現第三點,增加資料:
function addItem(e) {
// 得先把 submit 預設的功能取消
e.preventDefault();
// 從使用者輸入欄位得到新資料, 處理後加到資料陣列內
const text = this.querySelector('[name=item]').value;
const item = {
text,
done: false
};
items.push(item);
// 顯示更新後的資料到畫面上
populateList(items, itemsList);
// 將輸入欄位清空
this.reset();
}
// 監聽到 `submit` 就執行函式
addItems.addEventListener('submit', addItem);
我們用 type
屬性為 submit
的按鈕作為用來增加資料的按鈕,好處是輸入完資料後,不管按 Enter 或是按按鈕都會觸發表單的 submit
事件,壞處是預設的 submit
事件觸發後,視同提交該表單,會重新整理一次頁面。
先設立監聽器監聽 submit
事件。在自訂函數內,第一步必須先將預設回應的動作取消,監聽器回傳的事件物件中有個方法為 preventDefault()
就是用來做這件事。
待辦清單的資料陣列 items
中,每個陣列元素代表一項待辦事項,可用物件表示。需要有兩個屬性,一個為事項內容,以字串表示;另一個為事項完成與否,以布林值表示。
為此我們可以將取得的每筆新資料整理為儲存上述資訊的物件,再用 push
新增到資料陣列中。
事項內容屬性 text
使用了 ES6 新增的語法糖,原本我們要將儲存著新輸入資料的變數 text
指定給該屬性,由於命名相同,我們可以直接簡寫,輸入該變數名稱即可。
預設事項剛建立時都是未完成的,因此 done
預設為 false
。
推進去資料陣列後,將新內容更新到畫面上,再將輸入欄位清空即可。
接下來是重頭戲,雖然現在能夠順利增加清單,但是清單卻不具備儲存功能,一但網頁重新整理、或是關閉瀏覽器頁面,資料就要重來,因此需要一個能夠永久儲存資訊的方法。而瀏覽器本身提供的 Local Storage
就是這次的救星!
Web Storage API
是瀏覽器本身提供一套標準化的API,透過該API,我們能夠很輕易的在客戶端的瀏覽器中儲存資料,資料格式基本上為 Key - Value pair
,類似物件的屬性一樣,有個資料名稱(key)和與之配對的資料值(value)。被儲存的資料們會自動被轉換成字串格式。
儲存資料的方式分為兩種,一種是 sessionStorage
,該種方式儲存的資料為具有生命週期的資料,其生命週期僅維持到瀏覽器關閉,並非我們首選。另一種為 localStorage
,其資料的生命週期為永久,就是我們這次要使用的。
使用方法很簡單,利用 localStorage
提供的 setItem()
方法,第一個參數是 key , 第二個參數是 value 。讀取則是用 getItem()
方法,刪除資料則是用 removeItem()
方法,參數皆同上。
要引入 localStorage
,我們先讓每次新增待辦事項時會將資料寫入 localStorage
內。在 addItem
的 this.reset()
前加入下列程式碼:
function addItem(e) {
// 前略, 加入 local... 這行
localStorage.setItem('items', JSON.stringify(items));
this.reset();
}
每次新增資料後,將整包 items
陣列用 JSON.stringify
轉換為字串後存入 localStorage
內,名稱為 items
。為什麼要先轉換成字串呢?前面有提到 localStorage
只能分辨及儲存字串,它不懂陣列解析。若未轉換成字串, localStorage
只會存下代表該陣列的名稱而已,而非內容。
接下來實作讀取資料。
原本資料會在重開瀏覽器後消失,是因為 <script>
會在每次重新載入網頁時執行,因此 items
陣列就會被初始化為空陣列,只要改變 items
陣列,讓其初始化時就從 localStorage
中讀取資料就好。
程式如下:
const items = JSON.parse(localStorage.getItem('items')) || [];
意思為如果 localStorage
有資料,就用 JSON.parse()
將該資料由字串轉換回 Javascript 看得懂的格式,在此為陣列。然後再指定給 items
, 否則初始化為空陣列。
到此,資料就會被永久儲存了。
若有勾選或取消特定待辦事項是否完成,該如何讓 localStorage
存取其狀態?一樣的道理,看以下程式碼:
// <ul> 元素本身
const itemsList = document.querySelector('.plates');
function toggleDone(e) {
// 我們只需要針對 <input> checkbox 做回應
if (!e.target.matches('input')) return;
const el = e.target;
const index = el.dataset.index;
items[index].done = !items[index].done;
// 更新完 items 資料後再存到 localStorage 內
localStorage.setItem('items', JSON.stringify(items));
populateList(items, itemsList);
}
// 監聽按鍵事件
itemsList.addEventListener('click', toggleDone);
Event.target
指向觸發該事件的元素,在這裡每次點擊會有多項元素被觸發,但我們只需要回應一項觸發,利用 Element.matches
,能夠篩出選擇器名稱的元素。
在先前的 populateList
函式中,資料整理為HTML格式後,單項為以下結構:
<li>
<input type="checkbox" data-index=${i} id="item${i}" ${plate.done ? 'checked' : ''} />
<label for="item${i}">${plate.text}</label>
</li>
也就是說,利用 data-index
這個屬性,我們讓每項待辦事項的HTML標籤都能夠與 items
陣列內相同鍵值的資料相互對應。
因此我們只需要讀取該元素的 data-index
值,找出具有相同值的陣列索引,再修改其資料就好。
完成後將結果儲存到 localStorage
內,便能永久紀錄待辦事項的完成狀況。
事實上,只要依循下列模式,便能依序做出刪除資料、全選資料、清除所有已選取資料等功能:
items
內資料items
陣列儲存到 localStorage 內populateList
更新畫面實作功能在下方參考資料中,有興趣可以研究,也歡迎一起討論!
以上就是 JS30 第十五篇!
預設參數
Local Storage
Event.preventDefault
JSON.Stringify
Element.matches
練習的完整程式碼