Be insensitive, and keep a lifelong growth mindset.

0%

Put It All Together: The Budget App Project Part I

1. Project Planning and Architecture

1.1. Architecture Preview

The following is the final architecture preview:
Architecture Preview

But let’s not to be so fast, next we’ll start planning from scratch.

1.2. Planning

Planning

1.3. Structuring Our Code with Modules

  • Modules
    • Important aspect of any robust application’s architecture;
    • Keep the units of code for a project both cleanly separated and organized;
    • Encapsulate some data into privacy and expose other data publicly.

Structuring Out Code with Modules

2. Implementing the Module Pattern

  • What you will learn in this section:
    • How to use the module pattern;
    • More about private and public data, encapsulation and separation of concerns.

2.1. Data Encapsulation Module

1
2
3
4
5
6
7
8
9
10
11
12
var budgetController = (function() {
var x = 23;
var add = function(a) {
return x + a;
}

return {
publicTest: function(b) {
console.log(add(b));
}
}
})();

Running Result:

Data Encapsulation Module Running Result

2.2. UI Controller

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
// BUDGET CONTROLLER
var budgetController = (function() {
var x = 23;
var add = function(a) {
return x + a;
}

return {
publicTest: function(b) {
return add(b);
}
}
})();

// UI CONTROLLER
var UIController = (function() {
// Some code
})();

// GLOBAL APP CONTROLLER
var controller = (function(budgetCtrl, UICtrl) {
var z = budgetCtrl.publicTest(5);
return {
anotherPublic: function() {
console.log(z);
}
}
})(budgetController, UIController);

3. Setting up the First Event Listeners

  • What you will learn in this section:
    • How to set up event listeners for keypress events
    • How to use event object
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
// BUDGET CONTROLLER
var budgetController = (function() {
// Some code
})();

// UI CONTROLLER
var UIController = (function() {
// Some code
})();

// GLOBAL APP CONTROLLER
var controller = (function(budgetCtrl, UICtrl) {

var ctrlAddItem = function() {
// 1. Get the field input data
// 2. Add the item to the budget controller
// 3. Add the item to the UI
// 4. Calculate the budget
// 5. Display the budget on the UI
console.log("It works.");
}
document.querySelector('.add_btn').addEventListener('click', ctrlAddItem);

document.addEventListener('keypress', function(event)) {
console.log(event);
if (event.keyCode === 13 || event.which === 13) {
console.log('ENTER was pressed.');
ctrlAddItem();
}
}
})(budgetController, UIController);

Reference:

4. Reading Input Data

  • What you will learn in this section:
    • How to read data from different HTML input types;
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
// UI CONTROLLER
var UIController = (function() {
var DOMstrings = {
inputType: '.add__type',
inputDescription: '.add_description',
inputValue: '.add__value'
};


return {
getInput: function() {
return {
var type = document.querySelector(DOMstrings.inputType).value,
var description = document.querySelector(DOMstrings.inputDescription).value,
var value = document.querySelector(DOMstrings.inputValue).value
};
},
getDOMstrings: function() {
return DOMstrings;
}
};
})();

// GLOBAL APP CONTROLLER
var controller = (function(budgetCtrl, UICtrl) {
var DOM = UICtrl.getDOMstrings();

var ctrlAddItem = function() {
// 1. Get the field input data
var input = UICtrl.getInput();
console.log(input);

// 2. Add the item to the budget controller
// 3. Add the item to the UI
// 4. Calculate the budget
// 5. Display the budget on the UI
console.log("It works.");
}
document.querySelector(DOM.inputBtn).addEventListener('click', ctrlAddItem);

document.addEventListener('keypress', function(event)) {
console.log(event);
if (event.keyCode === 13 || event.which === 13) {
console.log('ENTER was pressed.');
ctrlAddItem();
}
}
})(budgetController, UIController);

5. Creating an Initialization Function

  • What you will learn in this section:
    • How and why to create an initialization function.
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
// GLOBAL APP CONTROLLER
var controller = (function(budgetCtrl, UICtrl) {
var setupEventListners = function() {
var DOM = UICtrl.getDOMstrings();
document.querySelector(DOM.inputBtn).addEventListener('click', ctrlAddItem);

document.addEventListener('keypress', function(event) {
console.log(event);
if (event.keyCode === 13 || event.which === 13) {
console.log('ENTER was pressed.');
ctrlAddItem();
}
});
};

var ctrlAddItem = function() {
// 1. Get the field input data
var input = UICtrl.getInput();
console.log(input);

// 2. Add the item to the budget controller
// 3. Add the item to the UI
// 4. Calculate the budget
// 5. Display the budget on the UI
console.log("It works.");
};

return {
init: function() { //Our public init method
console.log('Application has started.');
setupEventListeners();
}
}

})(budgetController, UIController);

6. Creating Income and Expense Function Constructors

  • What you will learn in this section:
    • How to choose function constructors that meet our application’s needs;
    • How to set up a proper data structure for our budget controller.
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

// BUDGET CONTROLLER
var budgetController = (function() {
// Some code
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
}
}
})();

7. Adding a New Item to Our Budget Controller

  • What you will learn in this section:
    • How to avoid conflicts in our data structures;
    • How and why to pass data from one module to another.
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

// BUDGET CONTROLLER
var budgetController = (function() {
// Some code
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
}
};

return {
addItem: function(type, des, val) {
var newItem, ID;
//Create new ID
ID = 0;
if (data.allItems[type].length > 0) {
ID = data.allItems[type][data.allItems[type].length - 1].id + 1;
} else {
ID = 0;
}

// Create new item based on 'inc' or 'exp' type
if (type === 'exp') {
newItem = new Expense(ID, des, val);
} else if (type === 'inc') {
newItem = new Income(ID, des, val);
}

//Push it into our data structure
data.allItems[type].push(newItem);

//Return the new element
return newItem;
},
testing: function() {
console.log(data);
}
};

})();


// GLOBAL APP CONTROLLER
var controller = (function(budgetCtrl, UICtrl) {
var setupEventListners = function() {
var DOM = UICtrl.getDOMstrings();
document.querySelector(DOM.inputBtn).addEventListener('click', ctrlAddItem);

document.addEventListener('keypress', function(event) {
console.log(event);
if (event.keyCode === 13 || event.which === 13) {
console.log('ENTER was pressed.');
ctrlAddItem();
}
});
};

var ctrlAddItem = function() {
var input, newItem;

// 1. Get the field input data
input = UICtrl.getInput();
console.log(input);

// 2. Add the item to the budget controller
newItem = budgetCtrl.addItem(input.type, input.description, input.value);

// 3. Add the item to the UI
// 4. Calculate the budget
// 5. Display the budget on the UI
console.log("It works.");
};

return {
init: function() { //Our public init method
console.log('Application has started.');
setupEventListeners();
}
}

})(budgetController, UIController);

8. Adding a New Item to the UI

  • What you will learn in this section:
    • A technique for adding big chunks of HTML into the DOM;
    • How to replace parts of strings;
    • How to do DOM manipulation using the insertAdjacentHTML method.
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
// UI CONTROLLER
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() {
return {
var type = document.querySelector(DOMstrings.inputType).value,
var description = document.querySelector(DOMstrings.inputDescription).value,
var value = document.querySelector(DOMstrings.inputValue).value
};
},

addListItem: function(obj, type) {
var html, newHtml, element;
// Create HTML string with placeholder text

if (type === 'inc') {
element = DOMstrings.incomeContainer;

html = '<div class="item clearfix" id="inc-%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="exp-%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>';
}

// Replace the placeholder text with some actual data
newHtml = html.replace('%id%', obj.id);
newHtml = newHtml.replace('%description%', obj.description);
newHtml = newHtml.replace('%value%', obj.value);

// Insert the HTML into the DOM
document.querySelector(element).insertAdjacentHTML('beforeend', newHtml);
},

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

// GLOBAL APP CONTROLLER
var controller = (function(budgetCtrl, UICtrl) {
var setupEventListners = function() {
var DOM = UICtrl.getDOMstrings();
document.querySelector(DOM.inputBtn).addEventListener('click', ctrlAddItem);

document.addEventListener('keypress', function(event) {
console.log(event);
if (event.keyCode === 13 || event.which === 13) {
console.log('ENTER was pressed.');
ctrlAddItem();
}
});
};

var ctrlAddItem = function() {
var input, newItem;

// 1. Get the field input data
input = UICtrl.getInput();
console.log(input);

// 2. Add the item to the budget controller
newItem = budgetCtrl.addItem(input.type, input.description, input.value);

// 3. Add the item to the UI
UICtrl.addListItem(newItem, input.type);

// 4. Calculate the budget
// 5. Display the budget on the UI
console.log("It works.");
};

return {
init: function() { //Our public init method
console.log('Application has started.');
setupEventListeners();
}
}

})(budgetController, UIController);

9. Clearing Our Input Fields

  • What you will learn in this section:
    • How to clear HTML fields;
    • How to use querySelectorAll;
    • How to convert a list to an array;
    • A better way to loop over an array then for loops: for each
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

// UI CONTROLLER
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() {
return {
var type = document.querySelector(DOMstrings.inputType).value,
var description = document.querySelector(DOMstrings.inputDescription).value,
var value = document.querySelector(DOMstrings.inputValue).value
};
},

addListItem: function(obj, type) {
var html, newHtml, element;
// Create HTML string with placeholder text

if (type === 'inc') {
element = DOMstrings.incomeContainer;

html = '<div class="item clearfix" id="inc-%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="exp-%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>';
}

// Replace the placeholder text with some actual data
newHtml = html.replace('%id%', obj.id);
newHtml = newHtml.replace('%description%', obj.description);
newHtml = newHtml.replace('%value%', obj.value);

// Insert the HTML into the DOM
document.querySelector(element).insertAdjacentHTML('beforeend', newHtml);
},

clearFields: function() {
var fields;

fields = document.querySelectorAll(DOMstrings.inputDescription + ', ' + DOMstrings.inputValue);

var fieldsArr = Array.prototype.slice.call(fields);

fieldsArr.forEach(function(current, index, array) {
current.value = "";
});

fieldsArr[0].focus();
},

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


// GLOBAL APP CONTROLLER
var controller = (function(budgetCtrl, UICtrl) {
var setupEventListners = function() {
var DOM = UICtrl.getDOMstrings();
document.querySelector(DOM.inputBtn).addEventListener('click', ctrlAddItem);

document.addEventListener('keypress', function(event) {
console.log(event);
if (event.keyCode === 13 || event.which === 13) {
console.log('ENTER was pressed.');
ctrlAddItem();
}
});
};

var ctrlAddItem = function() {
var input, newItem;

// 1. Get the field input data
input = UICtrl.getInput();
console.log(input);

// 2. Add the item to the budget controller
newItem = budgetCtrl.addItem(input.type, input.description, input.value);

// 3. Add the item to the UI
UICtrl.addListItem(newItem, input.type);

// 4. Clear the fields
UICtrl.clearFields();

// 4. Calculate the budget
// 5. Display the budget on the UI
console.log("It works.");
};

return {
init: function() { //Our public init method
console.log('Application has started.');
setupEventListeners();
}
}

})(budgetController, UIController);

10. Updating the Budget: Controller

  • What you will learn in this section:
    • How to convert field inputs to numbers;
    • How to prevent false inputs

To convert a string to a number, we can use parseFloat.

1
2
3
4
5
6
7
getInput: function() {
return {
var type = document.querySelector(DOMstrings.inputType).value,
var description = document.querySelector(DOMstrings.inputDescription).value,
var value = parseFloat(document.querySelector(DOMstrings.inputValue).value)
};
},

Apply DRY and prevent false inputs

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
// GLOBAL APP CONTROLLER
var controller = (function(budgetCtrl, UICtrl) {
var setupEventListners = function() {
var DOM = UICtrl.getDOMstrings();
document.querySelector(DOM.inputBtn).addEventListener('click', ctrlAddItem);

document.addEventListener('keypress', function(event) {
console.log(event);
if (event.keyCode === 13 || event.which === 13) {
console.log('ENTER was pressed.');
ctrlAddItem();
}
});
};

var updateBudget = function() {
// 1. Calculate the budget
// 2. Return the budget
// 3. Display the budget on the UI

};

var ctrlAddItem = function() {
var input, newItem;

// 1. Get the field input data
input = UICtrl.getInput();
console.log(input);

if (input.description !== "" && !isNaN(input.value) && input.value > 0) {
// 2. Add the item to the budget controller
newItem = budgetCtrl.addItem(input.type, input.description, input.value);

// 3. Add the item to the UI
UICtrl.addListItem(newItem, input.type);

// 4. Clear the fields
UICtrl.clearFields();

// 5. Calculate and update budget
updateBudget();
}
};

return {
init: function() { //Our public init method
console.log('Application has started.');
setupEventListeners();
}
}

})(budgetController, UIController);

11. Updating the Budget: Budget Controller

  • What you will learn in this section:
    • How and why to create simple, reusable functions with only one purpose;
    • How to sum all elements of an array using the forEach method.
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

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

var data = {
allItems: {
exp: [],
inc: []
},
totals: {
exp: 0,
inc: 0
},
budget: 0,
percentage: -1
};

return {
addItem: function(type, des, val) {
var newItem, ID;
//Create new ID
ID = 0;
if (data.allItems[type].length > 0) {
ID = data.allItems[type][data.allItems[type].length - 1].id + 1;
} else {
ID = 0;
}

// Create new item based on 'inc' or 'exp' type
if (type === 'exp') {
newItem = new Expense(ID, des, val);
} else if (type === 'inc') {
newItem = new Income(ID, des, val);
}

//Push it into our data structure
data.allItems[type].push(newItem);

//Return the new element
return newItem;
},

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 of income that we spent
if (data.totals.inc > 0) {
data.percentage = Math.round((data.totals.exp / data.totals.inc) * 100);
} else {
data.percentage = -1;
}
},

getBudget: function() {
return {
budget: data.budget,
totalInc: data.totals.inc,
totalExp: data.totals.exp,
percentage: data.percentage
};
},

testing: function() {
console.log(data);
}
};

})();

In updateBudget method:

1
2
3
4
5
6
7
8
9
10
...
var updateBudget = function() {
// 1. Calculate the budget
budgetCtrl.calculateBudget();
// 2. Return the budget
var budget = bubdgetctrl.getBudget();
// 3. Display the budget on the UI
console.log(budget);
};
...

12. Updating the Budget: UI Controller

  • What you will learn in this section:
    • Practice DOM manipulation by updating the budget and total values.
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
131
132
133
134
135
136
137
138
139
140
141

// UI CONTROLLER
var UIController = (function() {
var DOMstrings = {
inputType: '.add__type',
inputDescription: '.add_description',
inputValue: '.add__value'
inputBtn: '.add_btn',
incomeContainer: '.income__list',
expensesContainer: '.expenses__list',
budgetLabel: '.budget__value',
incomeLabeL: '.budget__income--value',
expensesLabel: '.budget__expenses--value',
percentageLabel: '.budget__expenses--percentage'
};


return {
getInput: function() {
return {
var type = document.querySelector(DOMstrings.inputType).value,
var description = document.querySelector(DOMstrings.inputDescription).value,
var value = document.querySelector(DOMstrings.inputValue).value
};
},

addListItem: function(obj, type) {
var html, newHtml, element;
// Create HTML string with placeholder text

if (type === 'inc') {
element = DOMstrings.incomeContainer;

html = '<div class="item clearfix" id="inc-%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="exp-%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>';
}

// Replace the placeholder text with some actual data
newHtml = html.replace('%id%', obj.id);
newHtml = newHtml.replace('%description%', obj.description);
newHtml = newHtml.replace('%value%', obj.value);

// Insert the HTML into the DOM
document.querySelector(element).insertAdjacentHTML('beforeend', newHtml);
},

clearFields: function() {
var fields;

fields = document.querySelectorAll(DOMstrings.inputDescription + ', ' + DOMstrings.inputValue);

var fieldsArr = Array.prototype.slice.call(fields);

fieldsArr.forEach(function(current, index, array) {
current.value = "";
});

fieldsArr[0].focus();
},

displayBudget: function(obj) {
document.querySelector(DOMstrings.budgetLabel).textContent = obj.budget;
document.querySelector(DOMstrings.incomeLabel).textContent = totoalInc;
document.querySelector(DOMstrings.expensesLabel).textContent = totalExp;

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

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

// GLOBAL APP CONTROLLER
var controller = (function(budgetCtrl, UICtrl) {
var setupEventListners = function() {
var DOM = UICtrl.getDOMstrings();
document.querySelector(DOM.inputBtn).addEventListener('click', ctrlAddItem);

document.addEventListener('keypress', function(event) {
console.log(event);
if (event.keyCode === 13 || event.which === 13) {
console.log('ENTER was pressed.');
ctrlAddItem();
}
});
};

var updateBudget = function() {
// 1. Calculate the budget
budgetCtrl.calculateBudget();
// 2. Return the budget
var budget = bubdgetctrl.getBudget();
// 3. Display the budget on the UI
console.log(budget);
};

var ctrlAddItem = function() {
var input, newItem;

// 1. Get the field input data
input = UICtrl.getInput();
console.log(input);

if (input.description !== "" && !isNaN(input.value) && input.value > 0) {
// 2. Add the item to the budget controller
newItem = budgetCtrl.addItem(input.type, input.description, input.value);

// 3. Add the item to the UI
UICtrl.addListItem(newItem, input.type);

// 4. Clear the fields
UICtrl.clearFields();

// 5. Calculate and update budget
updateBudget();
}
};

return {
init: function() { //Our public init method
console.log('Application has started.');
UICtrl.displayBudget({
budget: 0,
totalInc: 0,
totalExp: 0,
percentage: -1
});
setupEventListeners();
}
}

})(budgetController, UIController);