Be insensitive, and keep a lifelong growth mindset.

0%

Put It All Together: The Budget App Project: Part II

1. Project Planning and Architecture: Step 2

1.1. What We Just Completed

What We Just Completed

1.2. Panning: Step 2

Panning: Step 2

2. Event Delegation

2.1. Event Bubbling, Target Element and Event Delegation

Event Bubbling, Target Element and Event Delegation

Event bubbling mean that when an event is fired or triggered on some DOM element, for example,by clicking on our button here on the right side, then the exact same event is also triggered on all of the parent elements.

Event delegation refers to the process of using event propagation (bubbling) to handle events at a higher level in the DOM than the element on which the event originated. It allows us to attach a single event listener for elements that exist now or in the future.

2.2. Use Cases for Event Delegation

  1. When we have an element with lots of child elements that we are interested in;

  2. When we want an event handler attached to an element that is not yet in the DOM when our page is loaded.

3. Setting up the Delete Event Listener Using Event Delegation

  • What you will learn in this section:

    • How to use event delegation in practice;
    • How to use IDs in HTML to connect the UI with the data model;
    • How to use the parentNode property for DOM traversing.
  • Action Items

    • Add container in UI Controller DOMstrings;
    • Update setupEventListners in app controller;
    • Create ctrlDeleteItem method for event listener.
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
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164

// 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',
container: '.container'
};

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();
}
});

document.querySelector(DOM.container).addEventListener('click', ctrlDeleteItem);

};

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();
}
};

var ctrlDeleteItem = function(event) {
console.log(event.target);
var itemID, splitID;
itemID = event.target.parentNode.parentNode.parentNode.parentNode.id;
if (itemID) {
//inc-1
splitID = itemID.split("-");
type = splitID[0];
ID = splitID[1];

// 1. delete the item from the data structure

// 2. Delete the item from the UI

// 3. Update and show the new budget
}


};

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);

4. Deleting an Item from our Budget Controller

  • What you will learn in this section:
    • Yet another method to loop over an array: map;
    • How to remove elements from an array using the splice method.

Add deleteItem Method in 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
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

// 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;
},

deleteItem: function(type, id) {
var ids, index;
// id = 3
var ids = data.allItems[type].map(function(current) {
return current.id;
});

index = ids.indexOf(id);
if (index !=== -1) {
data.allItems[type].splice(index, 1);
}

},

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);
}
};

})();

Call deleteItem method in App Controller ctrlDeleteItem:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

var ctrlDeleteItem = function(event) {
console.log(event.target);
var itemID, splitID;
itemID = event.target.parentNode.parentNode.parentNode.parentNode.id;
if (itemID) {
//inc-1
splitID = itemID.split("-");
type = splitID[0];
ID = parseInt(splitID[1]);

// 1. delete the item from the data structure
budgetCtrl.deleteItem(type, ID);

// 2. Delete the item from the UI

// 3. Update and show the new budget
}
};

5. Deleting an Item from the UI

  • What you will learn in this section:
    • More DOM manipulation: how to remove an element from the 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
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

// 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',
container: '.container'
};

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);
},

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

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;
}
};
})();

Call UICtrl.deleteListItem and updateBudget methods in App Controller ctrlDeleteItem:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

var ctrlDeleteItem = function(event) {
console.log(event.target);
var itemID, splitID;
itemID = event.target.parentNode.parentNode.parentNode.parentNode.id;
if (itemID) {
//inc-1
splitID = itemID.split("-");
type = splitID[0];
ID = parseInt(splitID[1]);

// 1. delete the item from the data structure
budgetCtrl.deleteItem(type, ID);

// 2. Delete the item from the UI
UICtrl.deleteListItem(itemID);

// 3. Update and show the new budget
updateBudget();
}
};