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 — 事件委派介绍 与 触发委派的回呼函数

范例网页请点此


<<:  不只懂 Vue 语法:Vue 3 如何使用 Proxy 实现响应式(Reactivity)?

>>:  [CSS] Flex/Grid Layout Modules, part 10

Day14 - 动态 新增/删除 Collection 项目(二) - Html Helper

这篇调整的方向是 透过 Partial View 来 Render Collection 项目 透过...

Day 30 / 结语

30 天挑战终於完成了,终於恢复自由身了!这 30 篇不只是传递分享知识的过程,更是让自己巩固加深原...

Swift纯Code之旅 Day16. 「页面传值?代理? Delegate?Protocol?(2)」

前言 目前已经将Protocol都设置完毕了,那现在就要来实作让其他的ViewController执...

你会用Alt + = 键吗?

Alt + = 在Excel中是设置SUM求和公式的快捷键。可是,它还有其他功能哦。 1、多列资料求...

[必学]如何简单快捷备份及还原 Facebook 聊天记录

这边推荐你一款 FonePaw iOS 资料备份及还原 软件,能够一键备份 iPhone Faceb...