iT邦幫忙

2021 iThome 鐵人賽

DAY 15
1
自我挑戰組

JS30 學習日記系列 第 15

Day 15 - LocalStorage and Event Delegation

題外話

不知不覺鐵人賽也到一半了呢! 開學後,能寫文章的時間就漸漸變少(期初各種專題、報告就紛紛露頭啦),或許文章的品質有因為趕稿略微下降不少吧XD。

這次的鐵人賽是我第一次在網路上公開發表文章,身為一個新人寫手,我還真的不是很懂要如何寫出一篇既生動又有趣還淺顯易懂的文章。

不過一路走到現在,也在不知不覺中體會到了寫文章的快樂(ps.寫得太放飛自我啦~),接下來的15天,相信我會帶著這份快樂的心情來完成這一次的鐵人賽。
/images/emoticon/emoticon12.gif


前言

JS 30 是由加拿大的全端工程師 Wes Bos 免費提供的 JavaScript 簡單應用課程,課程主打 No FrameworksNo CompilersNo LibrariesNo Boilerplate 在30天的30部教學影片裡,建立30個JavaScript的有趣小東西。

另外,Wes Bos 也很無私地在 Github 上公開了所有 JS 30 課程的程式碼,有興趣的話可以去 fork 或下載。


本日目標

將被新增到menu的項目儲存到localStorage中,使得後續刷新頁面時,可以從localStorage調用資料回復之前新增在menu的項目。


解析程式碼

HTML 部分

最外層的.wrapper代表的是整個的 menu,.plates則用來放入 menu 的內容項目。最後,.add-items是一個表單元素,裡面有一個文字輸入框(<input type="text"></input>)用來填入要新增的項目名稱,還有一個用來加入項目到 menu 的 submit button(<input type="submit"></input>)。

<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>

JS 部分

宣告常數addItems用來存放取得的整個 menu(.add-items)。

宣告常數itemsList用來存放 menu 裡的所有項目(.plates)。

宣告常數items為一個空陣列,用來存放我們每次在文字輸入框填入要新增到 menu 的 item。

const addItems = document.querySelector('.add-items');
const itemsList = document.querySelector('.plates');
const items = [];

addItem() :

我們首先用e.preventDefault()避免每次提交新的內容進入items造成頁面的 reloading。

宣告常數text用來存放在文字輸入框填入的項目名稱。

建立物件item並賦予兩個屬性text(項目的名稱)、done(是否被勾選),這裡我們原本可以用text:text;,但在 ES6 裡可以被簡寫為text

將建立出的物件放入(push)陣列items中,之後用this.reset()清掉在文字輸入框的文字以利下一次新增項目。

function addItem(e){
    e.preventDefault();//prevent page from reloading
    const text = this.querySelector('[name="item"]').value; 
    const item = {
      text, //text: text
      done: false
    }
    items.push(item);
    this.reset(); //form element clear the input
}

addItems.addEventListener('submit',addItem);

populateList():

用來將items裡所有的item逐一轉換成 HTML 的格式,藉此更新 menu 上的項目。

透過map()將陣列中的item(方法裡用plate代稱),逐一用<li>~<li>的格式重新組合成一個有checkBox可以勾選的列表項目。

因為map()回傳的是一個陣列,所以在最後用join()將陣列中的元素以空白作為間隔符號串聯成一個 HTML 格式的字串並修改列表(ul)裡的項目內容(innerHTML)。

//create the actual html here
  function populateList(plates = [], platesList){ //plates default: empty, prvent to crash javascript if you forget to pass it
    platesList.innerHTML = plates.map((plate,i) =>{ //i: index
      return `
        <li>
          <input type="checkbox" data-index=${i} id="item${i}" ${plate.done ? 'checked' : ''}>
          <label for="item${i}">${plate.text}</label>
        </li>
      `;
    }).join('');
 }

JS 的部分完成到這裡,基本上就可以將新建立的項目放到 menu 中。

但是將網頁重新整理(F5),我們可以發現原來放到 menu 中的東西都不見了。那要怎麼保留我們之前在 menu 放入的項目呢? 相信 localStorage 可以幫我們這個忙。

Window.localStorage

localStorage 允許我們存取目前文件(Document)隸屬網域來源的Storage 物件,簡單來說我們可以用key-value(鍵-值)的方式來存取這個Storage物件的資料,localStorage存放的資料是沒有時間限制的(不會關閉網頁就不見)。另外,這邊的鍵-值都是以字串型態存放。

實際在localStorage放資料(localStorage.setItem()):

localStorage.setItem('myCat', 'Tom');

打開檢查模式,查看我們放的資料(Application -> Local Storage):

資料真的被放到 Local Storage 裡面了,我們之後就可以用 Local Storage 的方法調用這些已存的資料。

用 localStorage 存取資料

items :

我們原本宣告常數items為一個空陣列,但是我們也可以改成如果localStorage有資料(localStorage.getItem('items'))就將其放入items。要注意localStorage放的資料是字串型態,所以還要藉助JSON.parse()把它還原成物件再傳遞給items

addItem() :

每一次加入新的itemitems,我們都需要同時更新現在menu的項目內容,所以我們在方法裡面加上populateList(items,itemsList);

localStorage.setItem('items',JSON.stringify(items));用來把我們新增的item存進localStorage保存新增在menu的項目。要注意items本身是物件型別(Object),所以要先用JSON.stringify()將其轉換成字串再存入localStorage

const items = JSON.parse(localStorage.getItem('items')) || []; // dump data from localstorage if existed 

function addItem(e){
    /*上略*/
    populateList(items,itemsList);
    localStorage.setItem('items',JSON.stringify(items));
    /*下略...*/
 }

只要做到這裡,之前加入到menuitem就不會在重新整理網頁的時候消失不見,每次都會從localStorage取得之前的資料(如果有資料的話)。

但這樣好像還是少了什麼,如果我們將checkBox勾起來再去重新整理網頁,就會發現checkBox又回到沒勾選的狀態。要儲存checkBox的勾選狀態,我們可以用另一個方法toggleDone()來處理。

toggleDone() :

Event.target 指向最初觸發事件的 DOM 物件。

if(!e.target.matches('input')) return;,如果觸發事件的 DOM 物件不是input element的話,就停止繼續執行。

宣告常數el存放觸發事件的 DOM 物件。
宣告常數index存放觸發事件的 DOM 物件的data-index屬性。

items[index].done = !items[index].done;,讓觸發事件的 DOM 物件的done變為相反值(true to false;false to true),前面我們在populateList()裡設定item (plate)done屬性是true就將checkBox打勾(checked)。(${plate.done ? 'checked' : ''})

最後我們需要將變更後的items再次的放入localStorage中,要注意先用JSON.stringify()轉換成字串再放入,接著用populateList()去更新現在menu的內容就完成了。

function toggleDone(e){
    if(!e.target.matches('input')) return;
    const el = e.target;
    const index = el.dataset.index;
    items[index].done = !items[index].done;
    localStorage.setItem('items',JSON.stringify(items));
    populateList(items,itemsList);
}

itemsList.addEventListener('click',toggleDone);
//create the actual html here
function populateList(plates = [], platesList){ //plates default: empty, prvent to crash javascript if you forget to pass it
    platesList.innerHTML = plates.map((plate,i) =>{ //i: index
      return `
        <li>
          <input type="checkbox" data-index=${i} id="item${i}" ${plate.done ? 'checked' : ''}>
          <label for="item${i}">${plate.text}</label>
        </li>
      `;
    }).join('');
}
補充資料:

Array.prototype.map()
Window.localStorage
Storage
Event.target
Event Delegation — 事件委派介紹 與 觸發委派的回呼函數

範例網頁請點此


上一篇
Day 14 - Object and Arrays - Reference VS Copy
下一篇
Day 16 - CSS Text Shadow Mouse Move Effect
系列文
JS30 學習日記31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言