JavaScript 練習 - 預算計算器

Demo

JavaScript 練習 - 預算控制器- Demo

1. Introduction 介紹

這是 Udemy 上的課程的一個練習,這個項目將達到以下目標

  • 程式碼撰寫將運用關注點分離、閉包、IIFE、封裝…等概念。使用閉包及 IIFE 創件模塊模式,能確保某些數據只能在模塊內被使用,避免數據被覆蓋。確保程式碼的安全性。
  • 取得輸入欄位數據
  • 添加項目到預算控制器
  • 添加項目到使用者介面
  • 計算預算
  • 把預算放到使用者介面

功能

設置事件監聽

首先使用 IIFE 建立三塊功能模組。建立好模組後,因為有輸入後鍵盤確認及滑鼠點擊,因此有兩個監聽項目,為了不重複撰寫同樣的功能,所以建立一個自定義函數。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//BUDGET CONTROLLER
var budgetController = (function() {})();
//UI CONTROLLER
var UIcontroller = (function() {})();
//GLOBAL APP CONTROLLER
var controller = (function(budgetCtrl, UICtrl) {
var ctrlAddItem = function() {
//1. get the input field data
//2. add item to the budget controller
//3. add item to the UI
//4. Calculate the budget
//5. Display the budget on UI
console.log("it works");
};

document.querySelector(".add__btn").addEventListener("click", ctrlAddItem);

document.addEventListener("keypress", function(event) {
if (event.keyCode === 13 || which.keyCode === 13) {
ctrlAddItem();
}
});
})(budgetController, UIcontroller);

獲取輸入數據

接下來,我們要使來自不同 HTML 類型的數據可以輸入之後,在應用程式內被使用。這個項目的邏輯是:由 APP Controller 來控制其他模組的行動;所以 我們在 UI Controller 編寫的方法及 Budget Controller 裡面獲取的數據,最後再由 Global APP 調用這些方法。以下是進入 UI 控制器獲取數據的方法,這個功能要能被其他的控制器使用,所以不能是私有函數。由於 IIFE 將會立即執行,所以回傳的物件,將被分配給 UIcontroller,所定義的變數及函數,在函數中將停留在閉包狀態。所以即使這個函數已經回傳,將從中回傳的物件,將有權限訪問這些私有變數、方法及函數。我們要在使用者介面獲取的數據有三項。如何讓控制器同時調用這個方法與三個值?因此與其使用三個獨立的變數,解決方法是回傳一個物件,裡面有三種屬性。

  1. 資料類型
  2. 資料內容描述
  3. 資料的值
1
2
3
4
5
6
7
8
9
var UIcontroller = (function() {
return {
getInput: function() {
var type = document.querySelector(".add__type").value;
var description = document.querySelector(".add__description").value;
var value = document.querySelector(".add__value").value;
}
};
})();

並且在控制器調用這個方法:

1
2
3
4
5
var controller = (function(budgetCtrl, UICtrl) {
var ctrlAddItem = function() {
var input = UICtrl.getInput();
};
})(budgetController, UIcontroller);

但是假設我們在用戶介面修改,像是修改 class 名稱,這些會很耗時,因為很多地方都需要更改。所以我們把原先放在 UI 控制器的那些類別選擇器,直接新增一個物件來管理。所以我們直接而且別忘了要把這些 DOMstrings 公開,讓它們可被調用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var UIcontroller = (function() {
var DOMstrings = {
inputType: ".add__type",
inputDescription: ".add__description",
inputValue: ".add__value",
inputBtn: ".add__btn"
};

return {
getDOMstrings: function() {
return DOMstrings;
}
};
})();

var controller = (function(budgetCtrl, UICtrl) {
var DOM = UICtrl.getDOMstrings();
document.querySelector(DOM.inputBtn).addEventListener("click", ctrlAddItem);
})(budgetController, UIcontroller);

建立初始化函數

建立一個初始化函數讓程式碼結構更優化。為什麼要設置初始化函式?因為要更便於管理四散的事件監聽,而且要讓它能被公開使用,我們調用 init 函數的時候,就會啟動事件監聽

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var controller = (function(budgetCtrl, UICtrl) {
var setupEventListeners = function() {
var DOM = UICtrl.getDOMstrings();
document.querySelector(DOM.inputBtn).addEventListener("click", ctrlAddItem);

document.addEventListener("keypress", function(event) {
if (event.keyCode === 13) {
ctrlAddItem();
}
});
};

return {
init: function() {
console.log("start");
setupEventListeners();
}
};
})(budgetController, UIcontroller);

controller.init();

將數據儲存到 budget controller

首先建立收入與支出的數據模型,每個項目裡面包含 ID、值與描述。但因為有很多不同的項目,為了能夠區分不同的收入跟支出,所以它們要有獨一的 ID,所以物件是儲存這些數據的方式。如何創建複數物件?使用函數建構式。

根據網路上的說明:
。https://pjchender.blogspot.com/2016/06/javascriptfunction-constructorprototype.html

在 JavaScript 中的函式其實也是一種物件,其中包含一些屬性像是該函式的名稱(Name)和該函式的內容(Code),但其實 function 這裡面還有一個屬性,這個屬性稱做 prototype,這個屬性會以空物件的型式呈現

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
var budgetController = (function() {
var Expense = function(id, description, value) {
this.id = id;
this.description = description;
this.value = value;
};

var Income = function(id, description, value) {
this.id = id;
this.description = description;
this.value = value;
};

var data = {
allItems: {
exp: [],
inc: []
},
totals: {
exp: 0,
inc: 0
}
};
})();

使用用戶輸入數據創建新項目

接下來使用用戶數據在預算控制器數據結構中創建新項目,重點是:如何避免資料結構的衝突?為什麼及如何把數據從一個模組傳到另一個模組?

首先先在預算控制器建立一個公共方法,讓其他模組可以向數據結構添加新項目。如果調用此方法創建新項目,首先要知道類型、描述以及值。使用不同的名稱減少混淆

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
var budgetController = (function() {
return {
addItem: function(type, des, val) {
var newItem, ID;
// [1 2 3 4 5], next ID =6;
// [1 2 4 6 8], next ID =9;
// ID = last ID+1;

//建立新 ID
if (data.allItems[type].length > 0) {
ID = data.allItems[type][data.allItems[type].length - 1].id + 1;
} else {
ID = 0;
}

// 以類型建立新項目
if (type === "exp") {
newItem = new Expense(ID, des, val);
} else if (type === "inc") {
newItem = new Income(ID, des, val);
}
//把項目推到資料結構
data.allItems[type].push(newItem);
return newItem;
}
};
})();

var controller = (function(budgetCtrl, UICtrl) {
var ctrlAddItem = function() {
var input, newItem;
input = UICtrl.getInput();
newItem = budgetCtrl.addItem(input.type, input.description, input.value);

console.log(input);
};
})(budgetController, UIcontroller);

向用戶介面添加項目

現在要把 HTML 加進 DOM 當中、更換字串、使用 insertAdjacentHTML 方法操作 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
var UIcontroller = (function() {
var DOMstrings = {
incomeContainer: ".income__list",
expensesContainer: ".expenses__list"
};

return {
addListItem: function(obj, type) {
var html, newHtml, element;
if (type === "inc") {
element = DOMstrings.incomeContainer;
html =
'<div class="item clearfix" id="income-%id%"><div class="item__description">%description%</div><div class="right clearfix"><div class="item__value">%value%</div><div class="item__delete"> <button class="item__delete--btn"><i class="ion-ios-close-outline"></i></button></div></div></div>';
} else if (type === "exp") {
element = DOMstrings.expensesContainer;
html =
'<div class="item clearfix" id="expense-%id%"><div class="item__description">%description%</div><div class="right clearfix"><div class="item__value">%value%</div><div class="item__percentage">21%</div><div class="item__delete"><button class="item__delete--btn"><i class="ion-ios-close-outline"></i></button></div></div></div>';
}
newHtml = html.replace("%id%", obj.id);
newHtml = newHtml.replace("%description%", obj.description);
newHtml = newHtml.replace("%value%", obj.value);
document.querySelector(element).insertAdjacentHTML("beforeend", newHtml);
}
};
})();

var controller = (function(budgetCtrl, UICtrl) {
var ctrlAddItem = function() {
var input, newItem;
input = UICtrl.getInput();
newItem = budgetCtrl.addItem(input.type, input.description, input.value);
UICtrl.addListItem(newItem, input.type);
};
})(budgetController, UIcontroller);

清除輸入項目

現在要在點擊輸入後,可以清空輸入欄位的值,我們使用 querySelectorAll 選擇器。但是 querySelectorAll 回傳的並非陣列,而是一組類似陣列的列表 (NodeList),所以要將列表轉為陣列。這裡我們使用 prototype 取得 slice 函式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
var UIcontroller = (function() {
return {
clearFields: function() {
var fields, fieldsArr;
fields = document.querySelectorAll(
DOMstrings.inputDescription + "," + DOMstrings.inputValue
);
fieldsArr = Array.prototype.slice.call(fields);
fieldsArr.forEach(function(current, index, array) {
current.value = "";
});

fieldsArr[0].focus();
}
};
})();

var controller = (function(budgetCtrl, UICtrl) {
var ctrlAddItem = function() {
UICtrl.clearFields();
};
})(budgetController, UIcontroller);

更新控制器的小修正

接著將輸入的字串轉成數字,並且限制輸入類型的錯誤。目前輸入欄位還不是數字:

回到使用者介面,我們一開始獲取輸入資料的地方,使用 parseFloat 把字串轉成數字。分析函數 parseFloat,會在給與字串作為參數時返回數值。parseFloat 的語句如下

parseFloat(str)

此處的 parseFloat 會分析他自己的參數,字串 str,並試著返回浮點數。如果遇到正負符號 (+ 或 -)、數字 (0-9)、小數點、指數以外的字元,他就會返回在此之前的數值,並忽略那些字元。如果連第一個字元也不可以轉換為數字,就會返回〝NaN〞(不是數字)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
var UIcontroller = (function() {
var DOMstrings = {
inputType: ".add__type",
inputDescription: ".add__description",
inputValue: ".add__value",
inputBtn: ".add__btn",
incomeContainer: ".income__list",
expensesContainer: ".expenses__list"
};

return {
getInput: function() {
// var type = document.querySelector(".add__type").value;
// var description = document.querySelector(".add__description").value;
// var value = document.querySelector(".add__value").value;
return {
type: document.querySelector(DOMstrings.inputType).value, //will be either inc or ex
description: document.querySelector(DOMstrings.inputDescription).value,
value: parseFloat(document.querySelector(DOMstrings.inputValue).value)
};
}
};
})();

另一個問題是避免輸入空白欄位、0,所以回到控制器設定一個條件句

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var controller = (function(budgetCtrl, UICtrl) {
var ctrlAddItem = function() {
var input, newItem;
//1.獲得輸入欄的資料
input = UICtrl.getInput();

if (input.description !== "" && !isNaN(input.value) && input.value > 0) {
//2.把項目新增到 budget 控制器
newItem = budgetCtrl.addItem(input.type, input.description, input.value);
//3.把項目新增到 UI
UICtrl.addListItem(newItem, input.type);
//4.清除輸入欄位
UICtrl.clearFields();
//5.update budget
updateBudget();
}

console.log(input);
};
})(budgetController, UIcontroller);

計算預算

我們計算的收入與支出只需要是私有資料,現在要取得都儲存在 allItems的資料,循環遍歷取得所有值求和,並且將所得值重新存入 allItems。並且在原始數據結構裡面,新增儲存預算的結構,也就是接下來要將收入減去花費所剩下的預算。也要獲取我們花費的百分比。

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
var budgetController = (function() {
var calculateTotal = function(type) {
var sum = 0;
data.allItems[type].forEach(function(current) {
sum += current.value;
data.totals[type] = sum;
});

/* Loop 的原理
0
[200,100,100]
sum = 0+200
sum = 200+100
sum = 300+100 = 400
*/
};

var data = {
allItems: {
exp: [],
inc: []
},
totals: {
exp: 0,
inc: 0
},
budget: 0,
percentage: -1
};
return {
calculateBudget: function() {
//calculate total income and expenses
calculateTotal("exp");
calculateTotal("inc");
//calculate the budget:income-expenses
data.budget = data.totals.inc - data.totals.exp;
//calculate the percentage
data.percentage = Math.round((data.totals.exp / data.totals.inc) * 100);
// expenses = 100 income = 300, spent 33.3333% = 100/300=0.3333*100
}
};
})();

回到全局控器使用這些方法,但我們還需要一種可以回傳預算的方法,讓它可以儲存在變數中,才能將預算傳遞並顯示在 UI

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
var controller = (function(budgetCtrl, UICtrl) {
var updateBudget = function() {
// 1. Calculate the budget
budgetCtrl.calculateBudget();

// 2. Return the budget
var budget = budgetCtrl.getBudget();

// 3. Display the budget on the UI
console.log(budget);
};

var ctrlAddItem = function() {
var input, newItem;
//1.獲得輸入欄的資料
input = UICtrl.getInput();

if (input.description !== "" && !isNaN(input.value) && input.value > 0) {
//2.把項目新增到 budget 控制器
newItem = budgetCtrl.addItem(input.type, input.description, input.value);
//3.把項目新增到 UI
UICtrl.addListItem(newItem, input.type);
//4.清除輸入欄位
UICtrl.clearFields();
//5.update budget
updateBudget();
}
};
})(budgetController, UIcontroller);

更新使用者介面控制器

這裡我們要把預算、收入、支出、百分比都呈現在畫面上。回到 UI 控制器,傳入的參數是資料結構裡的物件。在呈現百分比的地方設定一個條件句

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
var UIcontroller = (function() {
var DOMstrings = {
budgetLabel: ".budget__value",
incomeLabel: ".budget__income--value",
expensesLabel: ".budget__expenses--value",
percentageLabel: ".budget__expenses--percentage"
};
return {
displayBudget: function(obj) {
document.querySelector(DOMstrings.budgetLabel).textContent = obj.budget;
document.querySelector(DOMstrings.incomeLabel).textContent = obj.totalInc;
document.querySelector(DOMstrings.expensesLabel).textContent =
obj.totalExp;

if (obj.percentage > 0) {
document.querySelector(DOMstrings.percentageLabel).textContent =
obj.percentage + "%";
} else {
document.querySelector(DOMstrings.percentageLabel).textContent = "---";
}
}
};
})();

var controller = (function(budgetCtrl, UICtrl) {
var updateBudget = function() {
UICtrl.displayBudget(budget);
};
})(budgetController, UIcontroller);

每次重載畫面的時候,別忘了要清除之前輸入的資料,所以要設定初始值歸 0

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var controller = (function(budgetCtrl, UICtrl) {
return {
init: function() {
console.log("start");
UICtrl.displayBudget({
budget: 0,
totalInc: 0,
totalExp: 0,
percentage: -1
});
setupEventListeners();
}
};
})(budgetController, UIcontroller);

事件委派

這個部分將會用到事件委派(Event Delegation) 概念。事件委派的特性有以下幾點:是一種受惠於 Event Bubbling 而能減少監聽器數目的方法。

事件委派的使用時機有兩個:

  1. 父元素底下有好幾個我們想使用的子元素,這時不必在所有子元素使用事件處理程序,我們把事件委派添加到父元素,例如將 click事件加到父元素,
    藉由 Event Bubbling 然後決定事件要傳遞在哪個子元素觸發,而非綁定在子元素上。優點是可減少監聽數目。缺點是由於需要判斷是哪些子元素節點是我們有興趣觸發的項目,因此需額外寫程式碼判斷。
  2. 當我們想要把事件處理程序附加到元素上時,但是載入畫面的時候,還沒有出現在 DOM 上面

首先先在父元素上綁定事件,並建立刪除功能。由於事件冒泡發生了,所以父元素藉由事件的目標屬性(target property) 知道它的來源,事件冒泡觸發的地點:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var controller = (function(budgetCtrl, UICtrl) {
var setupEventListeners = function() {
var DOM = UICtrl.getDOMstrings();
document
.querySelector(DOM.container)
.addEventListener("click", ctrlDeleteItem);
};
var ctrlDeleteItem = function(event) {
var itemID, splitID, type, ID;
itemID = event.target.parentNode.parentNode.parentNode.parentNode.id;
if (itemID) {
splitID = itemID.split("-");
type = splitID[0];
ID = splitID[1];
}
};
})(budgetController, UIcontroller);

但在我們點擊的時候,儘管我們點擊圖示,但我們要刪除的是包含子元素及它的父元素的整個 HTML 結構,所以這裡使用 DOM 遍歷同時使用 split,獲取父元素的 ID。在使用 split拆分的時候, JavaScript 會把拆分的結果從其原始類型轉換為物件。像這樣:

1
2
3
4
5
6
7
8
9
10
11
var controller = (function(budgetCtrl, UICtrl) {
var ctrlDeleteItem = function(event) {
var itemID, splitID, type, ID;
itemID = event.target.parentNode.parentNode.parentNode.parentNode.id;
if (itemID) {
splitID = itemID.split("-");
type = splitID[0];
ID = parseInt(splitID[1]); //字串轉為數字
}
};
})(budgetController, UIcontroller);

從預算控制器刪除一個項目

如果要刪除一個項目,應用程式控制器要傳遞給刪除方法的參數包括類型、ID。indexOf方法回傳元素的索引號。

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
var budgetController = (function() {
return {
deleteItem: function(type, id) {
var ids, index;
//ids =[1,2,4,5,6]
//index = 3
ids = data.allItems[type].map(function(current) {
return current.id;
});

index = ids.indexOf(id);
if (index !== -1) {//索引不是負值的時候執行
data.allItems[type].splice(index, 1);
}
}
};
})();

var controller = (function(budgetCtrl, UICtrl) {
var ctrlDeleteItem = function (event) {
var itemID, splitID, type, ID;
itemID = event.target.parentNode.parentNode.parentNode.parentNode.id;
if (itemID) {
//inc-1
splitID = itemID.split('-');
type = splitID[0];
ID = parseInt(splitID[1]);
//delete item from data structure
budgetCtrl.deleteItem(type, ID);
//delete item from UI
//update and show the new budget
}
})();

刪除畫面上的項目,並且更新預算

我們要先移動到該元素的父層再移除其子節點

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
var UIcontroller = (function() {

return {
deleteListItem: function (selectorId) {
var el = document.getElementById(selectorId);
el.parentNode.removeChild(el);
},
};
})();

var controller = (function(budgetCtrl, UICtrl) {
var ctrlDeleteItem = function (event) {
var itemID, splitID, type, ID;
itemID = event.target.parentNode.parentNode.parentNode.parentNode.id;
if (itemID) {

//delete item from UI
UICtrl.deleteListItem(itemID);
//update and show the new budget
updateBudget();
}
})();

更新百分比

透過在函數建構式裡添加新方法,同時也會將它新增到它的原型當中,所有透過此費用原型創建的物件,因為原型鏈的關係,都會繼承這個方法

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
var budgetController = (function() {
Expense.prototype.calcPercentage = function(totalIncome) {
if (totalIncome > 1) {
this.percentage = Math.round((this.value / totalIncome) * 100);
} else {
this.percentage = -1;
}
};
Expense.prototype.getPercentage = function() {
return this.percentage;
};

return {
calculatePercentages: function() {
/*a=40
b=10
c=20
income=100
a=40/100=40%
b=10/100=10%
c=20/100=20% */
data.allItems.exp.forEach(function(cur) {
cur.calcPercentage(data.totals.inc);
});
},
getPercentages: function() {
var allPerc = data.allItems.exp.map(function(cur) {
return cur.getPercentage();
});
return allPerc;
}
};
})();

var controller = (function(budgetCtrl, UICtrl) {
var updatePercentage = function() {
//1. Calculate the percentage
budgetCtrl.calculatePercentages();

//2. Read the percentage from the budget controller
var percentage = budgetCtrl.getPercentages();
//3. Update the UI withe the new percentage
console.log(percentage);
};
var ctrlAddItem = function() {
if (input.description !== "" && !isNaN(input.value) && input.value > 0) {
//6. calculate and update percentage
updatePercentage();
}
};
var ctrlDeleteItem = function(event) {
var itemID, splitID, type, ID;
itemID = event.target.parentNode.parentNode.parentNode.parentNode.id;
if (itemID) {
// calculate and update percentage
updatePercentage();
}
};
})();

把百分比顯示在 UI

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
var UIcontroller = (function() {
var DOMstrings = {
expensesPercLabel: ".item__percentage"
};
return {
displayPercentages: function(percentage) {
var fields = document.querySelectorAll(DOMstrings.expensesPercLabel);

var nodeListForEach = function(list, callback) {
for (var i = 0; i < list.length; i++) {
callback(list[i], [i]);
}
};

nodeListForEach(fields, function(current, index) {
if (percentage[index] > 0) {
current.textContent = percentage[index] + "%";
} else {
current.textContent = "---";
}
});
}
};
})();
var controller = (function(budgetCtrl, UICtrl) {
var updatePercentages = function()

UICtrl.displayPercentages(percentages);
};
})(budgetController, UIcontroller);

格式化數字

接著在 UI 控制器添加方法,接下來每次在銀幕上顯示數字,就會調用這個方法,然後把數字輸入該方法,然後輸出格式化的數字。使用substr()取得我們要放入千分號的位置

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
var UIcontroller = (function() {
var formateNumber = function(num, type) {
var numSplit, int, dec, type;
/*
數字前有+或-
小數點後正好2位數
逗號分隔千位數
*/
num = Math.abs(num);
num = num.toFixed(2);
numSplit = num.split(".");
int = numSplit[0];

if (int.length > 3) {
int = int.substr(0, int.length - 3) + "," + int.substr(int.length - 3, 3);
}

dec = numSplit[1];

return (type === "exp" ? "-" : "+") + "" + int + "." + dec;
};
return {
addListItem: function(obj, type) {
newHtml = newHtml.replace("%value%", formateNumber(obj.value, type));
},
displayBudget: function(obj) {
var type;
obj.budget > 0 ? (type = "inc") : (type = "exp");
document.querySelector(
DOMstrings.budgetLabel
).textContent = formateNumber(obj.budget, type);
document.querySelector(
DOMstrings.incomeLabel
).textContent = formateNumber(obj.totalInc, "inc");
document.querySelector(
DOMstrings.expensesLabel
).textContent = formateNumber(obj.totalExp, "exp");
}
};
})();

取得日期

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
var UIcontroller = (function() {
return {
displayMonth: function() {
var now, month, year;
now = new Date();
months = [
"January",
"February",
"March",
"April",
"May",
"June",
"July",
"August",
"September",
"October",
"November",
"December"
];
month = now.getMonth();
year = now.getFullYear();
document.querySelector(DOMstrings.dateLabel).textContent =
months[month] + " " + year;
}
};
})();
var controller = (function(budgetCtrl, UICtrl) {
return {
init: function() {
UICtrl.displayMonth();
}
};
})();

觸發 change 事件改變輸入欄位顏色

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
var UIcontroller = (function() {
var nodeListForEach = function(list, callback) {
for (var i = 0; i < list.length; i++) {
callback(list[i], [i]);
}
};

return {
changeType: function() {
var fields = document.querySelectorAll(
DOMstrings.inputType +
"," +
DOMstrings.inputDescription +
"," +
DOMstrings.inputValue
);
nodeListForEach(fields, function(cur) {
cur.classList.toggle("red-focus");
});
document.querySelector(DOMstrings.inputBtn).classList.toggle("red");
}
};
})();
var controller = (function(budgetCtrl, UICtrl) {
var setupEventListeners = function() {
document
.querySelector(DOM.inputType)
.addEventListener("change", UICtrl.changeType);
};
})();

Powered by Hexo and Hexo-theme-hiker

Copyright © 2013 - 2019 CYC'S BLOG All Rights Reserved.

UV : | PV :