學習到的知識點
HTML
CSS
- first-of-type
- input [type=” “]
- outline
JS
- Jquery 寫法
map()
,filter()
,reduce()
math.random()
math.floor()
- LocalStorage
簡介
Demo
可以紀錄支出與收入的記帳簿。
HTML
結構
使用模板語言 pug 讓 HTML 更好閱讀。
上半部結構
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| <body> <h2>Expense Tracker</h2> <div class="container"> <h4>Your Balance</h4> <h1 id="balance">$0.00</h1> <div class="inc-exp-container"> <div> <h4>Income</h4> <p class="money plus" id="money-plus">+$0.00</p> </div> <div> <h4>Expense</h4> <p class="money minus" id="money-minus">+$0.00</p> </div> </div> </div> </body>
|
動態生成收入與支出
li.minus
的部分是藉由 JS 控制動態生成<li>
。
1 2 3 4 5 6 7
| <h3>History <ul class="list" id="list"> <li class="minus">Cash<span>-$400</span> <button class="delete-btn">x</button> </li> </ul> </h3>
|
輸入框與送出按鈕
label 與 placeholder 的用法。
1 2 3 4 5 6 7 8 9 10 11 12
| <h3>Add new transaction</h3> <form id="form"> <div class="form-control"> <label for="text">Text</label> <input id="text" type="text" placeholder="Enter text..."/> </div> <div class="form-control"> <label for="amount">Amount<br/>(negtive - expense,positive - income)</label> <input id="amount" type="number" placeholder="Enter amount..."/> </div> <button class="btn">Add transaction</button> </form>
|
CSS
大部分與之前的練習使用到的技巧相同,紀錄幾個提醒自己。
顏色變數
1 2 3
| :root { --box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24); }
|
置中
30px
為上下預留的距離,左右使用auto
,讓網頁自己計算就會達成置中的效果。
1 2 3 4
| .container { margin: 30px auto; width: 350px; }
|
first-of-type
選擇器
這邊的效果為,選擇到 income 的 div,所以使用到first-of-type
,然後在使用border-right
。
1 2 3
| .inc-exp-container > div:first-of-type { border-right: 1px solid #dedede; }
|
沒有使用過,值得記錄。
1 2 3 4 5 6 7 8 9
| input[type="text"], input[type="number"] { border: 1px solid #dedede; border-radius: 2px; display: block; font-size: 16px; padding: 10px; width: 100%; }
|
去除點選的預設外框
outline 設定為 0
1 2 3 4
| .btn:focus, .delete-btn:focus { outline: 0; }
|
JS
以下筆記會比較原生 JS 與 Jquery 的差異
設定變數
原生 Js
1 2 3 4 5 6 7
| const balance = document.getElementById("balance"); const money_plus = document.getElementById("money-plus"); const money_minus = document.getElementById("money-minus"); const list = document.getElementById("list"); const form = document.getElementById("form"); const text = document.getElementById("text"); const amount = document.getElementById("amount");
|
Jquery
可以直接以選擇器的方式呈現。
1 2 3 4 5 6 7
| $('#balance') $('#money-plus') $('#money-minus') $('#list') $('#form') $('#text') $('#amount')
|
新增事先做好的資料陣列到 list 上
addTransactionDOM()
函式的概念
- 判斷輸入的符號(sign)為正或負數,產生新的 li
- 判斷輸入的值為正或負數(item)
- 判斷完後,將 item 放到 list 上
原生 JS
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| const dummyTransactions = [ { id: 1, text: "Flower", amount: -20 }, { id: 2, text: "Salary", amount: 300 }, { id: 3, text: "Book", amount: -10 }, { id: 4, text: "Camera", amount: 150 }, ];
let transactions = dummyTransactions;
function addTransactionDOM(transaction) { const sign = transaction.amount < 0 ? "-" : "+"; const item = document.createElement("li");
item.classList.add(transaction.amount < 0 ? "minus" : "plus"); item.innerHTML = ` ${transaction.text} <span>${sign}${Math.abs( transaction.amount )}</span> <button class="delete-btn">x</button> `;
list.appendChild(item); }
|
Jquery
createElement
改寫成要選取的元素。
.classList.add
改寫成.addClass
item.innerHTML =
改寫成item.html()
.appendChild(item)
改寫成 .append(item);
:bulb:append()和 appendTo()方法的區別
append()
方法是將方法裡面的參數添加到 jquery 對象中裡。
例如:A.append(B)
的意思是將 B 放到 A 中來,後面追加 A 的子元素的最後一個位置。
appendTo()
其方法是將 jquery 對象添加到appendTo
指定的參數中裡。
例如:A.appendTo(B)
的意思是將 A 放到 B 裡,後面追加 B 的子元素的最後一個位置。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| const dummyTransactions = [ { id: 1, text: "Flower", amount: -20 }, { id: 2, text: "Salary", amount: 300 }, { id: 3, text: "Book", amount: -10 }, { id: 4, text: "Camera", amount: 150 }, ];
let transactions = dummyTransactions;
function addTransactionDOM(transaction) { const sign = transaction.amount < 0 ? "-" : "+";
const item = $("<li></li>"); item.addClass(transaction.amount < 0 ? "minus" : "plus"); item.html(`${transaction.text} <span>${sign}${Math.abs( transaction.amount )}</span> <button class="delete-btn">x</button> `); $("#list").append(item); }
|
init()
函式的概念
使 transactions(預先做好的資料),每一筆都丟到addTransactionDOM()
。
預設listinnerHTML = ""
為空,不設定的話會出現 HTML 裡設定 Cash 的-400。
原生 Js
1 2 3 4 5 6 7 8 9 10
| function init() { list.innerHTML = ""; transactions.forEach(addTransactionDOM); }
init();
|
Jquery
BUG
這邊遇到的問題是,dummyTransactions 示範陣列的欄位會跑出來,但內容不會顯示。
原因是因為 js 原生寫法為 item.innerHTML =
,jquery 寫法為item.html()
,我沒有把=拿掉,導致它顯示不出來,也不會跳錯誤訊息。
1 2 3 4 5 6 7 8
| function init() { $("#list").html(""); transactions.forEach(addTransactionDOM); }
init();
|
設定總金額,支出,收入的元素
add updateValues function to display balance,income and expense
map()
需要回傳一個值,他會透過函式內所回傳的值組合成一個陣列。
reduce()
方法像是一個累加器,可以陣列中每項元素(由左至右)傳入回呼函式,將陣列化為單一值。
filter()
會回傳一個陣列,其條件是 return 後方為 true 的物件,適合用在搜尋符合條件的資料(本範例都是用來搜尋符合的資料)。
:bulb: .reduce((acc, item) => (acc += item), 0)
,最後面的 0,是第一次呼叫 callback 時,要傳入的累加器的初始值。若沒有提供初始值,則原陣列的第一個元素將會被當作初始的累加器。目前在這邊的程式不會影響到。
箭頭函式的寫法解說
1 2 3 4
| let newArr = oldArr.map((val,index,arr) => { });
|
- newArr — 新的 Array 會被回傳
- oldArr — 在 map( )裡面跑的 Arr
- val — the current value being processed
- index — the current index of the value being processed
- arr — 原本的 arr(資料)
一般函式與箭頭函式比較
將 function 變成箭頭,然後把 return 去掉。
1 2 3 4 5 6
| const amounts = transactions.map((transaction) => transaction.amount);
const amounts = transactions.map(function(transaction){ return transaction.amount });
|
原生 Js
BUG
這邊遇到了一個問題就是 innerHTML 一直顯示錯誤訊息。
‘cannot set property ‘innertext’ of null javascript’
後來發現問題是 html 裡的 id 拼錯字,導致它選取不到,才會一直顯示 null 。
updateValues()
函式的概念
- 使用
map()
取得新函數,函數裡有每一個函數的 amout 數值(未加總)。
- 使用
reduce()
加總每一個 amout 數值,放到 total 變數,
- 以 income 做範例,所以先使用
filter()
過濾資料,取得大於 0 的數。
- 再來,用
refuce()
加總。
- toFixed()取的小數第二位。
- 把處理好的 income,total,expense 放到 DOM 裡面更新文字。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| function updateValues() { const amounts = transactions.map((transaction) => transaction.amount); const total = amounts.reduce((acc, item) => (acc += item), 0).toFixed(2); const income = amounts .filter((item) => item > 0) .reduce((acc, item) => (acc += item), 0) .toFixed(2);
const expense = ( amounts .filter((item) => item < 0) .reduce((acc, item) => (acc += item), 0) * -1 ).toFixed(2); balance.innerText = `$${total}`; money_plus.innerText = `$${income}`; money_minus.innerText = `$${expense}`; }
function init() { list.innerHTML = ""; transactions.forEach(addTransactionDOM); updateValues(); }
|
Jquery
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| function updateValues() { const amounts = transactions.map((transaction) => transaction.amount); const total = amounts.reduce((acc, item) => (acc += item), 0).toFixed(2); const income = amounts
.filter((item) => item > 0) .reduce((acc, item) => (acc += item), 0) .toFixed(2);
const expense = ( amounts.filter((item) => item < 0).reduce((acc, item) => (acc += item), 0) * -1 ).toFixed(2); $("#balance").text(`$${total}`); $("#money-plus").text(`$${income}`); $("#money-minus").text(`$${expense}`); }
|
新增列表 與 亂數 ID
addTransaction()
函數的概念
- 判斷輸入框是否為空
- 不為空,新增一個新的 transaction
- 將新增的物件,push 到 transactions
- 更新到
addTransactionDOM
和updateValues
原生 Js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
|
function addTransaction(e) {
e.preventDefault();
if (text.value.trim() = "" || amount.value.trim() = "") { alert("Please add a text and amount"); } else { const transaction = { id: generateID(), text: text.value, amount: +amount.value, }; transactions.push(transaction); addTransactionDOM(transaction); updateValues();
text.value = ""; amount.value = ""; } }
|
Jquery
text.value
換成$("text").val()
text.value = ""
換成$("#text").val("")
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| function addTransaction(e) { e.preventDefault(); if ($("#text").val().trim() = "" || $("#amount").val().trim() = "") { alert("Please add a text and amount"); } else { const transaction = { id: generateID(), text: $("#text").val(), amount: +$("#amount").val(), }; transactions.push(transaction); addTransactionDOM(transaction); updateValues();
updateLocalStorage();
$("#text").val(""); $("#amount").val(""); } }
|
generateID()
Math.floor()
函式會回傳小於等於所給數字的最大整數,簡單來說就是,小數無條件捨去到比自身小的最大整數。
Math.random()
函式,會隨機產生出 0~1 之間的小數,出來的值永遠不會大於 1。
1 2
| Math.random(); Math.random();
|
如果將Math.random()
產生出來的數,乘上 2 就會得到 0-2 之間的小數,乘上 3 就會得到 0-3 之間的小數,再使用Math.floor()
無條件取整數位。
1 2 3 4
| Math.floor(Math.random()*2); Math.floor(Math.random()*3); Math.floor(Math.random()*5); Math.floor(Math.random()*50);
|
函數的概念
原生 JS
1 2 3 4 5
| function generateID() { return Math.floor(Math.random() * 100000000); }
|
Jquery
一樣的寫法
移除列表
1
| `<button class="delete-btn" onclick="removeTransaction(${transaction.id})">`
|
假如我們點擊 A 列表,那 A 列表的transaction.id
會傳進去removeTransaction()
函式裡。
再使用filter()
過濾,當原始的transaction.id
們不等於 A 列表的 id,就會被回傳。所以 A 列表沒有被回傳,再使用init()
,就被刪除了。
如果沒有呼叫init()
,畫面是不會改變的。所以最後要記得加上init()
函數。
1 2 3 4 5 6 7 8 9 10 11 12 13
| function removeTransaction(id) { transactions = transactions.filter((transaction) => transaction.id ! id); init(); }
function removeTransaction(id) { transactions = transactions.filter(function(transaction){ return transaction.id ! id }); init(); }
|
按下 submit 提交事件
事件監聽
原生 Js
1
| form.addEventListener("submit", addTransaction);
|
Jquery
.addEventListener("submit",xxx)
轉成.submit
1
| $("#form").submit(addTransaction);
|
增加 Local Storage
從 Localstorage 抓取資料使用 getItem
需要轉換格式使用 JSON.parse
判斷getItem
得到的資料內容是否為空。
1 2 3 4 5 6
| const localStorageTransactions = JSON.parse( localStorage.getItem("transactions") );
let transactions = localStorage.getItem("transactions") ! null ? localStorageTransactions : [];
|
更新 Localstorage 裡面的資料。
1 2 3 4
| function updateLocalStorage() { localStorage.setItem("transactions", JSON.stringify(transactions)); }
|
在addTransaction()
,removeTransaction()
函數裡需要呼叫updateLocalStorage()
,才能夠更新資料。
參考文章
JS
JavaScript 陣列處理方法 [filter(), find(), forEach(), map(), every(), some(), reduce()]
Learn & Understand JavaScript’s Map Function
jquery 動態插入 append、prepend、before、after 區別
Math.floor()