自學日記

學習程式相關知識及學習法

0%

作品練習- Expense Tracker

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">
<!-- li 這裡先測試顯現的樣式,之後是採用JS輸入品項及價格後會顯現 -->
<!-- <li class="minus">Cash <span>-$400</span><button class="delete-btn">x</button></li> -->
</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>
<!-- placholder 輸入提示訊息 -->
<input type="text" id="text" placeholder="Enter text...">
</div>
<div class="form-control">
<!-- br斷行 -->
<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');

/*使用根目錄選取器,在之後css的設定中設定此值,此處設定為將其凸顯 */
: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:設定最低高度限制; */
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:字元之間的距離 */
letter-spacing: 1px;
margin: 0;
}

h3 {
border-bottom: 1px solid #bbb;
padding-bottom: 10px;
margin: 40px 0 10px;
}

h4 {
margin: 0;
/* 控制字母大小寫: uppercase:皆大寫*/
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這個class下的直屬div有影響*/
.inc-exp-container > div {
/* flex:1 讓class下的直屬div平分空間,此處為income與expense評分*/
flex: 1;
text-align: center;
}
/* :first-of-type選取對於父元素下的第1個子元素做設定*/
.inc-exp-container > div:first-of-type {
border-right: 1px solid #dedede;
}/* 此處設定為選取到第一個income div 設定gorder-right,讓其看起來像中間產生了框綫(於下方子標題進一步說明)*/
.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
/* 設定成inline-block,同時擁有block可設定寬高的屬性,但其排版仍像inline屬性,並不會向右占滿整個容器。 */
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 {
/*去除li前面的"˙" */
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注意 為了之後delete鍵設置 */
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;
/* 將原本於右側的X按鈕移動至左側*/
position: absolute;
top: 50%;
left: 0;
/* 將delete按鍵些微調整制比較合適的位子 */
transform: translate(-100%, -50%);
/* 將delete btn隱藏 */
opacity: 0;
/* 設置顯現時間 */
transition: opacity 0.3s ease;
}

/* opacity 0透明 1不透明 0.6半透明 */
/* 滑鼠滑過顯現 */
.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 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 }
// ];

const localStorageTransactions = JSON.parse(localStorage.getItem
('transactions'));
// 從dummyTransactions改為: 目的為清零
// let transactions = dummyTransactions;
let transactions =
localStorage.getItem('transactions') !== null ?
localStorageTransactions : [];

// 下方設定完事件監聽至此處撰寫function
// 3.設定下方花費或淨賺提交處
// Add transaction
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
};

// console.log(transaction);確定輸入text及amount有數值
transactions.push(transaction);

addTransactionDOM(transaction);

updateValues();
// 在設定下方Update local storage transactions Function後設置
updateLocalStorage();

text.value = '';
amount.value = '';
}
}
// 注意 生成ID 在後續刪除中見效
// Generate random ID
function generateID() {
return Math.floor(Math.random() * 100000000);
}

// Add transactions to DOM list
function addTransactionDOM(transaction) {
// Get sign
const sign = transaction.amount < 0 ? '-' : '+';

const item = document.createElement('li');

// Add class based on value
item.classList.add(transaction.amount < 0 ? 'minus' : 'plus');
// Math.abs 將負數轉為正數
// onclick="removeTransaction(${transaction.id})"注意
item.innerHTML = `
${transaction.text} <span>${sign}${Math.abs(
transaction.amount
)}</span> <button class="delete-btn" onclick="removeTransaction(${transaction.id})">x</button>
`;
// 注意
list.appendChild(item);
}
// 2.設定總數值,淨賺及花費數值
// Upate the balance, income and expense
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}`;
}
// 在設定完上方onclick後,接續此設定為讓history紀錄除去,以ID作為刪除的基礎
// Remove transaction by ID
function removeTransaction(id) {
transactions = transactions.filter(transaction => transaction.id
!== id);
// 在設定Update local storage transactions 後設置
updateLocalStorage();

init();
}
// 4.設定本地存儲
// Update local storage transactions
function updateLocalStorage() {
localStorage.setItem('transactions', JSON.stringify(transactions))
;
}


// 注意
// Init app
function init() {
list.innerHTML = '';

transactions.forEach(addTransactionDOM);
updateValues();
}

init ();

// 設定事件監聽後 至上方撰寫function
// not addTransactionDOM
form.addEventListener('submit', addTransaction);