iT邦幫忙

2018 iT 邦幫忙鐵人賽
DAY 15
2
Modern Web

新手也能懂的JS30系列 第 15

JS30-Day15-LocalStorage

Day15-課題內容

今天我們要將資料從頁面中的程式碼儲存到Web瀏覽器的儲存空間當中。如此一來,本來存入程式碼當中的資料,就不會因重新整理頁面或者關閉瀏覽器而消失。[1]
實作連結

Web Storage object

透過HTML中的 web storage 物件, 可以將網頁中的資料儲存在使用者的瀏覽器當中。[2]
在 HTML5 問世之前,我們只能將小筆的資料儲存在cookies當中。在 HTML5 問世之後, Web storage 提供了一個更加安全且容量更大的本地端儲存空間,除此之外也不會影響到網頁的效能。

web storage 種類

web storage 物件分為兩種,雖然都能將資料暫存在當下頁面(頁籤)的空間Storage物件裡,但是資料保存的時間不同。[3][4]

  1. window.sessionStorage :放在sessionStorage的資料會在頁面(頁籤)關閉時清空,只要該頁面頁面(頁籤)沒被關閉或者有還原(restore)該頁面,資料就會保存。
  2. window.localStorage : 放在localStorage的資料會永久保存,直到被使用者清除。

web storage method

  1. Storage.setItem('key', 'value')
    透過在 setItem() 方法中指定物件屬性的 key 以及 value
    我們可以在 storage 物件中加入屬性或修改原本的屬性內容。

  2. Storage.getItem('key')
    透過在 getItem() 方法中輸入屬性的 key ,我們可以得到 storage 物件對應的屬性 value

  3. Storage.key()
    透過在 key() 方法中輸入屬性的順序位置,我們可以取得storage 物件中該位置的屬性 key ,此順序位置如陣列一樣,由0開始。

  4. Storage.removeItem()
    透過在 removeItem() 方法,我們可以把指定的屬性從storage 物件中移除。

  5. Storage.clear()
    透過 clear() 方法,我們可以直接把storage 中的所有屬性移除。

JSON

雖然我們可以透過 Storage 方法,輕鬆的將資料儲存在本地端,但是實際應用之後,我們會發現我們要儲存的資料 value,全部被轉成字串格式,因此當我們要儲存的資料為一個物件的時候就會產生一個嚴重的問題:

let string = 'testString'
let number = 12345;
let array = ['asd', 123, true];
let object = {test:1};

localStorage.setItem('item', string);
localStorage.setItem('item2', number);
localStorage.setItem('item3', array);
localStorage.setItem('item4', object);

console.log(localStorage.getItem('item'));
console.log(localStorage.getItem('item2'));
console.log(localStorage.getItem('item3'));
console.log(localStorage.getItem('item4'));

可以發現到當我們在 setItem() 時將值指定為一個物件,再使用 getItem() 來取得資料時,會回傳一個奇怪的 [object object]字串出來。
這是因為當我們對 object 使用toString() 方法,預設情況下該方法會被每個 object 對象繼承。如果此方法在自定義對象中未被覆蓋,將會回傳 [object type],其中type是定義對象的類型,因此會發現如上圖的結果。[5]

有幾種方法可以有效避開這種情況,而在本次的課題當中,我們要使用到 JSON 這種資料格式。[6]
JASO 全名為 JavaScript Object Notation (Javascript物件符號),可以把資料轉成字串型別,方便將資料於網頁與伺服器之間做傳輸,其中包含兩種方法:

  1. JSON.stringify():將資料轉為 JSON 格式的字串。
  2. JSON.parse(myJSON):將資料由 JSON 格式字串轉回原本的資料內容及型別。
let string = 'testString'
let number = 12345;
let array = ['asd', 123, true];
let object = {test:1};

localStorage.setItem('item', JSON.stringify(string));
localStorage.setItem('item2', JSON.stringify(number));
localStorage.setItem('item3', JSON.stringify(array));
localStorage.setItem('item4', JSON.stringify(object));

console.log(JSON.parse(localStorage.getItem('item')));
console.log(JSON.parse(localStorage.getItem('item2')));
console.log(JSON.parse(localStorage.getItem('item3')));
console.log(JSON.parse(localStorage.getItem('item4')));

form Element

進入課題之前,我們可以看到作者使用了 form 這個元素做為容器,並在裡頭放了兩個input元素,type 分別為 text 以及 submit
而這邊要特別提一下,<input type="submit"> 元素為 form 元素的綁定元素,透過點擊該<input type="submit"> 元素才能觸發 formsubmit 事件,跟一般的 click 事件不同。
<input type="submit"> 元素被視為 form元素的提交按鈕(submit button),點選的話就能把表單提交到伺服器,並同時對頁面執行重新整理 reload()。[7]

<form class="add-items">
    <input type="text" name="item" placeholder="Item Name" required>
    <input type="submit" value="+ Add Item">
</form>

因此在我們點擊<input type="submit"> 元素時,會觸發頁面 reload() 這個動作,因此在觸發的函式當中,我們必須加上 event.preventDefault() 這個方法。
event.preventDefault() 這個方法可以將允許被取消的事件,取消該事件的預設行為,但不會影響事件的傳遞,事件仍會繼續傳遞。[8]

進入課題

要完成今天的課題,我們需要完成以下的事項:

  1. 建立資料以及資料庫
  2. 新增資料
  3. 呈現資料
  4. 修改資料

步驟一

在作者範例的中,當我們頁面中新增一個項目,該項目中會包含一個 checkbox 元素用來確認以及一個 li 元素來保存輸入的資料,因此我們會將資料做成一個包含兩種屬性的物件放入陣列當中,再將這個陣列放入到我們的 storage 物件中:

const addItems = document.querySelector('.add-items');
const itemsList = document.querySelector('.plates');
const submitBtn = document.querySelector('input[type="submit"]');
const inputArea = document.querySelector('input[type="text"]');

//當localStorage沒有資料陣列,指定一個空陣列放入資料庫
if (localStorage.getItem('item') === null) {
    var storageArray = [];
    localStorage.setItem('item', JSON.stringify(storageArray));
//當localStorage已存在資料陣列,指定一個內容與陣列資料庫相同的陣列
} else {
    var storageArray = JSON.parse(localStorage.getItem('item'));
};

步驟二

再來我們要將在 form 元素當中藉由 input 新增的資料,加到我們的資料庫當中。
form 元素加上監聽事件以及函式之後,首先我們需要把新增的資料物件,丟入我們的陣列之中,並將內容改變之後的陣列,更新到我們的資料庫當中,然後再把目前資料庫當中的資料呈現在頁面上。此外就如前面所提到的,我們要先加上 event.preventDefault() ,來避免頁面一直重新整理:

addItems.addEventListener('submit', addItem);

function addItem(event) {
    event.preventDefault();
    //取得輸入欄位的資料
    inputValue = inputArea.value;
    //建立一個符合我們需求的物件資料
    inputObject = { value: inputValue, done: false };
    //將新物件加入我們的陣列
    storageArray.push(inputObject);
    //將陣列修改成JSON字串
    stringJson = JSON.stringify(storageArray)
    //將處理後的JSON字串更新到資料庫中
    localStorage.setItem(`item`, stringJson);
    //將輸入欄位清空
    inputArea.value = '';
    //將資料呈現在頁面上
    createlist()
};

要將資料呈現在頁面上,我們需要先判斷資料庫裡頭的是否已經存在資料,因此當資料庫內的資料不為空值,也就是資料庫內的陣列長度不為0的時候,透過陣列的 forEach 方法執行函數,才資料一筆一筆加到HTML的 DOM 上。
這次使用的方法是透過在 DOM 上新增元素並給予元素屬性的方法,與之前直接給予 innerHTML 的方法不同:

步驟三

function createlist() {
    //將資料庫的陣列取出
    let arrayJason = JSON.parse(localStorage.getItem('item'));
    //假如資料庫內的陣列有內容存在,執行以下的程式碼
    if (arrayJason.length !== 0) {
        //先清空ul容器內的元素
        itemsList.innerHTML = '';
        //對陣列裡的每個元素執行函式
        arrayJason.forEach(function (item) {
            //在DOM上創造一個<li>元素
            let createLi = document.createElement('li');
            //在DOM上創造一個<input>元素
            let createInput = document.createElement('input');
            //將<input>元素加上 type="checkbox"這個屬性
            createInput.setAttribute('type', 'checkbox');
            //在DOM上創造一個<label>元素
            let createLabel = document.createElement('label');
            //將物件中的 done 值指定給 <label> 元素的 checked 屬性
            createInput.checked = item['done'];
            //將<input>元素加上監聽事件與觸發函式
            createLabel.addEventListener('click', checkStatus);
            //將物件中的 value 值指定給 <input> 元素的文字內容
            createLabel.textContent = item['value'];
            //將 <input> 元素加到 <li> 容器元素底下
            createLi.appendChild(createInput);
            //將 <label> 元素加到 <li> 容器元素底下
            createLi.appendChild(createLabel);
            //將 <li> 元素加到 <ul> 容器元素底下
            itemsList.appendChild(createLi);
        });
      //假如資料庫內的陣列有內容存在,執行以下的程式碼
      } else {
        itemsList.innerHTML = '<li>Loading Tapas...</li>';
    }
};

步驟四

經由上面的程式碼,會發現這邊是利用資料庫的資料,直接對 checkbox 元素進行勾選的判定,因此當我們要觸發 click 事件對 checkbox 元素進行勾選狀態的改變時,我們直接修改物件中的 done 屬性值,並將資料重新取出來即可,但是因為版面配置的關係,我們是將事件綁定在 <label> 元素上,不過因為 .checkbox 元素與 <label> 元素是同時一起被產生出來且在同一層 DOM 結構下,表示取得<label> 元素的 index 會與 label 元素的 index 相同,並可也與該筆資料在陣列中的 index 相同:

function checkStatus(event) {
    //將目前頁面中的所有 <label> 元素選出來
    let allLable = document.querySelectorAll('label');
    //將前一步中的 Nodelist 轉為陣列
    labelArray = Array.from(allLable);
    //取得觸發事件元素的 index
    let getIndex = labelArray.indexOf(event.target);
    //將資料庫中的陣列資料叫出來
    let arrayJason = JSON.parse(localStorage.getItem('item'));
    //當 click 事件觸發時,將 done 的屬性布林值改為相反
    arrayJason[getIndex]['done'] = !arrayJason[getIndex]['done'];
    //將新的資料陣列轉成 JSON string 結構
    stringJson = JSON.stringify(arrayJason);
    //將新的 JSON string 丟到資料庫中
    localStorage.setItem(`item`, stringJson);
    //重新將資料呈現在頁面上
    createlist();
    };

完成以上步驟之後,我們的每一筆資料更動都會與資料庫綁定,再將最新的資料取出之後更新在頁面上!

總結

今天的課題中我們學到以下的知識:

  1. Web Storage 的屬性與方法
  2. JSON資料格式
  3. <form> 元素以及 <input> 元素的 submit 屬性與事件

在今天的課題當中,我們學習到資料傳輸的相關資訊,並且成功將資料從程式碼當中,搬到 web storage 這個瀏覽器的資料庫中。除此之外,我們利用到之前 Javascript 30 所學到的各種方法,將提取出來的資料呈現在頁面上,相信今天大家都對資料的處理以及運用更加得熟悉。以上是今天的心得與感想,感謝您的閱讀。

參考資料

  1. javascript30
  2. w3schools-HTML5 Web Storage
  3. MDN-Window.localStorage
  4. MDN-Window.sessionStorage
  5. MDn-Object.prototype.toString()
  6. w3schools-JSON
  7. MDN-input type="submit"
  8. MDN-Event.preventDefault()

上一篇
JS30-Day14-JS Reference VS Copy
下一篇
JS30-Day16-Mouse Move Shadow
系列文
新手也能懂的JS3030

尚未有邦友留言

立即登入留言