04實作練習:Expense Tracker
html
製作收支平衡及收入及支出表格
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| <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 id="money-plus" class="money plus">+$0.00</p> </div> <div> <h4>Expense</h4> <p id="money-minus" class="money minus">-$0.00</p> </div> </div>
|
製作收支歷史紀錄
1 2 3 4 5
| <h3>History</h3> <ul id="list" class="list">
</ul>
|
製作收支品項及價格輸入格
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| <h3>Add new transaction</h3> <form id="form"> <div class="form-control"> <label for="text">Text</label>
<input type="text" id="text" placeholder="Enter text..."> </div> <div class="form-control"> <label for="amount">amount <br> (negative - expense, positive - Income)</label> <input type="number" id="amount" placeholder="Enter amount..."> </div> <button class="btn">Add transaction</button> </form>
</div>
|
Html成品

CSS
設定根目錄選擇器及整體CSS基礎樣式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| @import url('https://fonts.googleapis.com/css?family=Lato&display=swap');
:root { --box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0.24); }
* { box-sizing: border-box; }
body { background-color: #f7f7f7; display: flex; flex-direction: column; align-items: center; justify-content: center; min-height: 100vh; margin: 0; font-family: 'Lato', sans-serif; }
|
資料參考:
CSS min-height 屬性
設定container區塊及各標題
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| .container { margin: 30px auto; width: 350px; }
h1 {
letter-spacing: 1px; margin: 0; }
h3 { border-bottom: 1px solid #bbb; padding-bottom: 10px; margin: 40px 0 10px; }
h4 { margin: 0; text-transform: uppercase; }
|
參考資料:Letter-spacing - 金魚都能懂的CSS必學屬性
css text-transform 控制字母大小寫
透過 text-transform 來控制文章中的文字字母大小寫,但指的是英文字母而不是中文字母。
uppercase 大寫 //GOOD MORNING
lowercase 小寫 //good morning
capitalize 第一個字母大寫,其他字母小寫 //Good Morning
參考資料:
CSS text-transform 控制文章字母的大小寫
設定收入及支出表格
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
| .inc-exp-container { background-color: #fff; box-shadow: var(--box-shadow); padding: 20px; display: flex; justify-content: space-between; margin: 20px 0; }
.inc-exp-container > div { flex: 1; text-align: center; }
.inc-exp-container > div:first-of-type { border-right: 1px solid #dedede; } .money { font-size: 20px; letter-spacing: 1px; margin: 5px 0; }
.money.plus { color: #2ecc71; }
.money.minus { color: #c0392b; }
|
參考資料:
flex:1 到底代表什麼?
直屬選擇器
.box>p
/ .box p

參考資料:HTML&CSS| 選擇器大於(>)的用法
:first-of-type選擇器
於上方程式碼中.inc-exp-container > div:first-of-type
我們進一步以圖示說明讓讀者更明白,
回頭看html讓我們更清楚知道程式碼想表示的:

從上圖我們看到紫色框與黃色框皆是.inc-exp-container > div
,我們選擇第一個子元素income,設定border-right
,結果就會如下圖所示,看起來就是中間設了線,但其實是income div做了操作

參考資料:
30個你必須記住的CSS選擇器
設置收支品項及價格輸入格及按鈕
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
| label { display: inline-block; margin: 10px 0; }
input[type='text'], input[type='number'] { border: 1px solid #dedede; border-radius: 2px; display: block; font-size: 16px; padding: 10px; width: 100%; } .btn { cursor: pointer; background-color: #9c88ff; box-shadow: var(--box-shadow); color: #fff; border: 0; display: block; font-size: 16px; margin: 10px 0 30px; padding: 10px; width: 100%; }
|
設置歷史紀錄
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 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
| .list {
list-style-type: none; padding: 0; margin-bottom: 40px; }
.list li { background-color: #fff; box-shadow: var(--box-shadow); color: #333; display: flex; justify-content: space-between; position: relative; padding: 10px; margin: 10px 0; }
.list li.plus { border-right: 5px solid #2ecc71; }
.list li.minus { border-right: 5px solid #c0392b; }
.delete-btn { cursor: pointer; background-color: #e74c3c; border: 0; color: #fff; font-size: 20px; line-height: 20px; padding: 2px 5px;
position: absolute; top: 50%; left: 0;
transform: translate(-100%, -50%); opacity: 0;
transition: opacity 0.3s ease; }
.list li:hover .delete-btn { opacity: 1; }
|
.list li.plus/ .list li.minus示意圖

delete按鈕移動示意圖

設置按鈕狀態
1 2 3 4 5
| .btn:focus, .delete-btn:focus { outline: 0; }
|
Javascript
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 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130
| 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')
const localStorageTransactions = JSON.parse(localStorage.getItem ('transactions'));
let transactions = localStorage.getItem('transactions') !== null ? localStorageTransactions : [];
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();
updateLocalStorage();
text.value = ''; amount.value = ''; } }
function generateID() { return Math.floor(Math.random() * 100000000); }
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" onclick="removeTransaction(${transaction.id})">x</button> `;
list.appendChild(item); }
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.innerHTML = `$${total}`; money_plus.innerHTML = `$${income}`; money_minus.innerHTML = `$${expense}`; }
function removeTransaction(id) { transactions = transactions.filter(transaction => transaction.id !== id);
updateLocalStorage();
init(); }
function updateLocalStorage() { localStorage.setItem('transactions', JSON.stringify(transactions)) ; }
function init() { list.innerHTML = '';
transactions.forEach(addTransactionDOM); updateValues(); }
init ();
form.addEventListener('submit', addTransaction);
|