不知不覺鐵人賽也到一半了呢! 開學後,能寫文章的時間就漸漸變少(期初各種專題、報告就紛紛露頭啦),或許文章的品質有因為趕稿略微下降不少吧XD。
這次的鐵人賽是我第一次在網路上公開發表文章,身為一個新人寫手,我還真的不是很懂要如何寫出一篇既生動又有趣還淺顯易懂的文章。
不過一路走到現在,也在不知不覺中體會到了寫文章的快樂(ps.寫得太放飛自我啦~),接下來的15天,相信我會帶著這份快樂的心情來完成這一次的鐵人賽。
JS 30 是由加拿大的全端工程師 Wes Bos 免費提供的 JavaScript 簡單應用課程,課程主打 No Frameworks
、No Compilers
、No Libraries
、No Boilerplate
在30天的30部教學影片裡,建立30個JavaScript的有趣小東西。
另外,Wes Bos 也很無私地在 Github 上公開了所有 JS 30 課程的程式碼,有興趣的話可以去 fork 或下載。
將被新增到menu
的項目儲存到localStorage
中,使得後續刷新頁面時,可以從localStorage
調用資料回復之前新增在menu
的項目。
最外層的.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>
宣告常數addItems
用來存放取得的整個 menu(.add-items
)。
宣告常數itemsList
用來存放 menu 裡的所有項目(.plates
)。
宣告常數items
為一個空陣列,用來存放我們每次在文字輸入框填入要新增到 menu 的 item。
const addItems = document.querySelector('.add-items');
const itemsList = document.querySelector('.plates');
const items = [];
我們首先用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);
用來將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 可以幫我們這個忙。
localStorage
允許我們存取目前文件(Document
)隸屬網域來源的Storage
物件,簡單來說我們可以用key-value
(鍵-值)的方式來存取這個Storage
物件的資料,localStorage存放的資料是沒有時間限制的(不會關閉網頁就不見)。另外,這邊的鍵-值都是以字串
型態存放。
實際在localStorage
放資料(localStorage.setItem()
):
localStorage.setItem('myCat', 'Tom');
打開檢查模式,查看我們放的資料(Application -> Local Storage):
資料真的被放到 Local Storage 裡面了,我們之後就可以用 Local Storage 的方法調用這些已存的資料。
items :
我們原本宣告常數items
為一個空陣列,但是我們也可以改成如果localStorage
有資料(localStorage.getItem('items')
)就將其放入items
。要注意localStorage
放的資料是字串型態,所以還要藉助JSON.parse()
把它還原成物件再傳遞給items
。
addItem() :
每一次加入新的item
到items
,我們都需要同時更新現在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));
/*下略...*/
}
只要做到這裡,之前加入到menu
的item
就不會在重新整理網頁的時候消失不見,每次都會從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 — 事件委派介紹 與 觸發委派的回呼函數