-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathapp.js
576 lines (456 loc) · 16.7 KB
/
app.js
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
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
// Storage Controller
const StrCtrl = (function (){
// Public methods
return {
storeItem: (item) => {
let items;
// Check if items is empty
if (localStorage.getItem("items") === null){
// If so, create new array, push items to array
items = [];
items.push(item);
// convert items array to string
localStorage.setItem("items", JSON.stringify(items));
} else {
// get items (remember to parse it since it is retrieved as a string)
items = JSON.parse(localStorage.getItem('items'));
// push new item to array
items.push(item);
// store again
localStorage.setItem("items", JSON.stringify(items));
console.log(items);
}
},
// Retreive items from the local storage
getItemsFromStorage: () => {
// Get from local storage (Parse into an object)
let items;
if (localStorage.getItem("items") === null){
items = [];
} else {
items = JSON.parse(localStorage.getItem("items"));
}
return items;
},
// Update items in local storage
updateItemLocalStorage: (updatedItem) => {
// Retrieving items from local storage and parsing them into an object
let items = JSON.parse(localStorage.getItem("items"));
// Iterating over the items with their index
items.forEach(function(item, index){
if (updatedItem.id === item.id){
// Removing the item based on it's index and updating it with the updated one
items.splice(index, 1, updatedItem);
}
})
// Storing items again in local storage
localStorage.setItem("items", JSON.stringify(items));
console.log(items);
},
// Delete items from local storage
deleteItemLocalStorage: (id) => {
let items = JSON.parse(localStorage.getItem("items"));
items.forEach(function(item, index){
if (id === item.id) {
items.splice(index, 1);
}
})
localStorage.setItem("items", JSON.stringify(items));
console.log(items);
},
// Clear all items from local storage
clearItemsLocalStorage: () => {
localStorage.removeItem("items");
}
}
})();
// Item Controller
const ItemCtrl = (function(){
// Inside, we should do the item constructor
const Item = function(id, name, calories){
this.id = id;
this.name = name;
this.calories = calories;
}
// Data structure / State
const data = {
// items: [
// // {id: 0, name:"Hamburger", calories:1200},
// // {id: 1, name:"French Fries", calories:800},
// // {id: 2, name:"Soda", calories:950}
// ],
items: StrCtrl.getItemsFromStorage(),
currentItem: null,
totalCalories: 0
}
// Since the previous data is private, we need to return it in order to access it for testing purposes
// Public methods
return {
// Following function is for getting the items and passing them to our App controller
getItems: function(){
return data.items;
},
addItem: function(name, calories){
// Add logic for ID
let ID;
if (data.items.length > 0){
ID = data.items[data.items.length - 1].id + 1;
} else {
ID = 0;
}
// Convert calories to integer
calories = parseInt(calories);
// Create new item with info from inputs
newMeal = new Item(ID, name, calories);
// Push item to data structure.
data.items.push(newMeal);
// Returning the new meal for later use in variables
return newMeal;
},
// Get item by ID from data structure
getItemById: (id) => {
let found;
data.items.forEach(function(item){
if (id == item.id) {
found = item;
}
});
return found;
},
// Set current item in data structure
setCurrentItem: (item) => {
data.currentItem = item;
},
// Getting current item from data structure
getCurrentItem: () => {
return data.currentItem;
},
// Get total calories function
getTotalCalories: () => {
let sum = 0;
// Loop through items and add cals
data.items.forEach(function(item){
sum += item.calories;
})
// Set total cal in data structure
data.totalCalories = sum;
// Return total
return data.totalCalories;
},
// Update Item function
updateItem: (name, calories) => {
let found;
// Iterate over items to find the item to be changed inside our data structure and reassign the values
data.items.forEach(function(item){
if(data.currentItem.id === item.id){
item.name = name;
item.calories = parseInt(calories);
found = item;
}
})
return found;
},
deleteItem: (id) => {
// Map all ids
const ids = data.items.map(function(item){
return item.id;
})
// Get the index of the selected item through it's id
const index = ids.indexOf(id);
// Delete the item from the array through it's index
data.items.splice(index, 1);
},
deleteAllItems: () => {
data.items = [];
},
// Method to check inner workings of data structure
logData: function(){
return data;
},
}
})();
// UI Controller
const UICtrl = (function(){
const UISelectors = {
itemList: '#item-list',
listItems: '#item-list li',
addBtn: '.add-btn',
updateBtn: '.update-btn',
deleteBtn: '.delete-btn',
backBtn: '.back-btn',
clearBtn: '.clear-btn',
itemName: '#item-name',
itemCalories: '#item-calories',
totalCalories: '.total-calories'
}
// Public methods
return {
// Following function is responsible for inserting the items that we fetched from the ItemCtrl inside the html's <ul>
populateItemsList: function(items){
let html = '';
// Iterate over each item to use dynamic data on them
items.forEach(function(item){
html += `<li class="collection-item" id="item-${item.id}"><strong>${item.name}:</strong> <em>${item.calories} calories</em><a href="#" class="secondary-content"><i class="edit-item fa fa-pencil"></i></a></li>`
})
// Insert list items inside HMTL
document.querySelector(UISelectors.itemList).innerHTML = html;
},
// Get input values
getItemInput: function(){
return {
name:document.querySelector(UISelectors.itemName).value,
calories:document.querySelector(UISelectors.itemCalories).value
}
},
// Add item to UI list
addListItem: function(item){
document.querySelector(UISelectors.itemList).style.display = 'block';
// create a new li
const li = document.createElement('li');
// add className to li item
li.className = 'collection-item';
// add id to li item
li.id = `item-${item.id}`;
// add HTML (content inside the li)
li.innerHTML = `<strong>${item.name}:</strong> <em>${item.calories} calories</em>
<a href="#" class="secondary-content">
<i class="edit-item fa fa-pencil"></i>
</a>`
// insert new li element to the end of the itemsList
document.querySelector(UISelectors.itemList).insertAdjacentElement("beforeend", li);
},
updateListItem: (item) => {
// Select ALL li elements inside #item-list (This returns a node list)
let listItems = document.querySelectorAll(UISelectors.listItems);
// Turn nodelist into array
listItems = Array.from(listItems);
let itemId;
// Iterate over li elements
listItems.forEach(function(li){
itemId = parseInt(li.id.split('-')[1]);
if (item.id === itemId){
li.innerHTML = `<strong>${item.name}:</strong> <em>${item.calories} calories</em>
<a href="#" class="secondary-content">
<i class="edit-item fa fa-pencil"></i>
</a>`;
}
})
},
deleteListItem: (id) => {
// Selected all <li> elements inside <ul> #item-list
let listItems = document.querySelectorAll(UISelectors.listItems);
// Turned the node list into an array so that we can iterate over it
listItems = Array.from(listItems);
// Variable for storing the element's ids
let itemId;
// Variable for storing the found element
let found;
// iterating over the <li> elements to identify the correct one and storing it in found variable
listItems.forEach(function(li){
itemId = parseInt(li.id.split('-')[1]);
if (id === itemId){
found = li;
}
})
// removing the found element
found.remove();
},
clearListItems: () => {
// Select all <li> elements inside <ul>
let listItems = document.querySelectorAll(UISelectors.listItems);
// Turn node list into array
listItems = Array.from(listItems);
// Iterate and remove items
listItems.forEach(function(item){
item.remove();
})
},
// Function for showing total calories in UI
showTotalCalories: (totalCalories) => {
document.querySelector(UISelectors.totalCalories).textContent = totalCalories;
},
// Setting initial state
setInitialState: () => {
UICtrl.clearInput();
document.querySelector(UISelectors.addBtn).style.display = 'inline';
document.querySelector(UISelectors.updateBtn).style.display = 'none';
document.querySelector(UISelectors.deleteBtn).style.display = 'none';
document.querySelector(UISelectors.backBtn).style.display = 'none';
},
// Setting edit state function (click on edit)
setEditState: () => {
document.querySelector(UISelectors.addBtn).style.display = 'none';
document.querySelector(UISelectors.updateBtn).style.display = 'inline';
document.querySelector(UISelectors.deleteBtn).style.display = 'inline';
document.querySelector(UISelectors.backBtn).style.display = 'inline';
document.addEventListener('keypress', UICtrl.disableEnter);
},
// Disable Enter Key
disableEnter: (e) => {
if(e.keyCode === 13 || e.which === 13){
e.preventDefault();
return false;
}
},
//Clear Input function
clearInput: () => {
document.querySelector(UISelectors.itemName).value = '';
document.querySelector(UISelectors.itemCalories).value = '';
},
addItemToForm: () => {
document.querySelector(UISelectors.itemName).value = ItemCtrl.getCurrentItem().name;
document.querySelector(UISelectors.itemCalories).value = ItemCtrl.getCurrentItem().calories;
UICtrl.setEditState();
},
// Clear UL line
hideList: () => {
document.querySelector(UISelectors.itemList).style.display = 'none';
},
// Access UISelectors from outside
accessUISelectors: function(){
return UISelectors;
}
}
})();
// App Controller
const App = (function(ItemCtrl, StrCtrl, UICtrl){
// Load event listeners
const loadEventListeners = function(){
// Get UI selectors
const UISelectors = UICtrl.accessUISelectors();
// Add item event
document.querySelector(UISelectors.addBtn).addEventListener('click', itemAddSubmit);
// Edit icon event (remember that we need to target the parent element given that the child is still unborn)
document.querySelector(UISelectors.itemList).addEventListener('click', itemEditClick);
// Item update submit event listener
document.querySelector(UISelectors.updateBtn).addEventListener('click', itemUpdateSubmit);
// Delete item event listener
document.querySelector(UISelectors.deleteBtn).addEventListener('click', itemDeleteSubmit);
// Back button event listener
document.querySelector(UISelectors.backBtn).addEventListener('click', UICtrl.setInitialState);
// Clear button event listener
document.querySelector(UISelectors.clearBtn).addEventListener('click', clearAllItemsClick);
}
// Item add submit event listener function
const itemAddSubmit = function(e){
// Get form input from UI Controller
const input = UICtrl.getItemInput();
// Check if inputs are empty
if (input.name !== '' && input.calories !== '') {
// Add item to data structure (Item Controller's responsibility)
const newItem = ItemCtrl.addItem(input.name, input.calories);
// Add item to UI list
UICtrl.addListItem(newItem);
// Store Item in local storage
StrCtrl.storeItem(newItem);
// Calling ItemCrtl Function for getting total calories
const totalCalories = ItemCtrl.getTotalCalories();
// Calling UICtrl Function to display total calories
UICtrl.showTotalCalories(totalCalories);
// Calling UICtrl function for clearing inputs
UICtrl.clearInput();
} else {
alert("Meal name & calories can't be empty");
}
e.preventDefault();
}
// Edit item click event listener function
const itemEditClick = function(e){
// Conditional to target the edit button only
if (e.target.classList.contains('edit-item')){
// Get item's id (select element that has the id)
const listId = e.target.parentNode.parentNode.id;
// Turn value into array
const listIdArr = listId.split('-');
// Get just the id number
const id = parseInt(listIdArr[1]);
// Get item to edit from Data Structure
const itemToEdit = ItemCtrl.getItemById(id);
// Set current item
ItemCtrl.setCurrentItem(itemToEdit);
// Add Item to form
UICtrl.addItemToForm();
};
e.preventDefault();
}
// Item update submit event listener function
const itemUpdateSubmit = function(e){
// To update the item, first we get the input
const input = UICtrl.getItemInput();
// Then we call our updateItem function from ItemCtrl (takes two parameters, name and calories)
const updatedItem = ItemCtrl.updateItem(input.name, input.calories);
// Update UI with new name/calories
UICtrl.updateListItem(updatedItem);
// Update local storage
StrCtrl.updateItemLocalStorage(updatedItem);
// Update Total Calories
// Calling ItemCrtl Function for getting total calories
const totalCalories = ItemCtrl.getTotalCalories();
// Calling UICtrl Function to display total calories
UICtrl.showTotalCalories(totalCalories);
// Clear input and display add meal button again (initial state)
UICtrl.setInitialState();
e.preventDefault();
}
// Delete Item Submit event listener function
const itemDeleteSubmit = function(e){
// First we get the current item
const currentItem = ItemCtrl.getCurrentItem();
// Then call deleteItem method with the currentItem id as an argument
ItemCtrl.deleteItem(currentItem.id);
// Delete Item from UIlist
UICtrl.deleteListItem(currentItem.id);
StrCtrl.deleteItemLocalStorage(currentItem.id)
// Calling ItemCrtl Function for getting total calories
const totalCalories = ItemCtrl.getTotalCalories();
// Calling UICtrl Function to display total calories
UICtrl.showTotalCalories(totalCalories);
// Clear input and display add meal button again (initial state)
UICtrl.setInitialState();
e.preventDefault();
}
// Clear all items click event listener function
const clearAllItemsClick = function(e){
// Delete all items from data structure
ItemCtrl.deleteAllItems();
// Delete all elements from the UI
UICtrl.clearListItems();
// Delete all items from the local storage
StrCtrl.clearItemsLocalStorage();
// Update Calories
// Calling ItemCrtl Function for getting total calories
const totalCalories = ItemCtrl.getTotalCalories();
// Calling UICtrl Function to display total calories
UICtrl.showTotalCalories(totalCalories);
// Clear Input
UICtrl.setInitialState();
// Hide list
UICtrl.hideList();
e.preventDefault();
}
// Public Methods
// Main app controller will return one single function called init
// Basically, init contains anything that we need to run right away when the app loads
return {
init: function(){
// Setting initial state (everything is clean)
UICtrl.setInitialState();
// Fetching items from Data Structure
const items = ItemCtrl.getItems();
if (items.length == 0 ){
UICtrl.hideList();
} else {
// Populate list with items
UICtrl.populateItemsList(items);
}
// Calling ItemCrtl Function for getting total calories
const totalCalories = ItemCtrl.getTotalCalories();
// Calling UICtrl Function to display total calories
UICtrl.showTotalCalories(totalCalories);
// Calling load even listeners function
loadEventListeners();
}
}
})(ItemCtrl, StrCtrl, UICtrl);
// First thing to initialize the controllers is to use a IIFE Function (Immediately Invoked Function Expressions)
App.init();