Skip to content

Commit 6cd0f0e

Browse files
committed
192 Forkify: Persistent Data w. localStorage() and List (Model, View & Controller for Delete All Items)
- Added 7.7.18 named as "Implementing Persistent Data WIth localStorage() API and Implementing the Delete All Shopping Items Functionality in List's Model, View & Controller"
1 parent a9136ec commit 6cd0f0e

File tree

10 files changed

+279
-34
lines changed

10 files changed

+279
-34
lines changed

Modern-JS-ES6-NPM-Babel-Webpack/forkify_project/dist/css/style.css

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -490,3 +490,12 @@ body {
490490
.shopping__item:hover .shopping__delete {
491491
opacity: 1;
492492
visibility: visible; }
493+
494+
#delete-all-btn {
495+
text-align: center;
496+
}
497+
498+
#delete-all-btn_ {
499+
margin-left: auto;
500+
margin-right: auto;
501+
}

Modern-JS-ES6-NPM-Babel-Webpack/forkify_project/dist/index.html

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
<link href="https://fonts.googleapis.com/css?family=Nunito+Sans:400,600,700" rel="stylesheet">
99
<link rel="stylesheet" href="css/style.css">
1010
<link rel="shortcut icon" href="img/favicon.png" type="image/x-icon">
11-
<title>forkify // Search over 1,000,000 recipes</title>
11+
<title>forkify - Search a 100,000 recipes</title>
1212
</head>
1313

1414
<body>
@@ -343,6 +343,9 @@ <h2 class="heading-2">How to cook it</h2>
343343

344344
<div class="shopping">
345345
<h2 class="heading-2">My Shopping List</h2>
346+
347+
<div id="delete-all-btn"></div>
348+
<!-- <button>Delete All Items</button> this goes inside the div above-->
346349

347350
<ul class="shopping__list">
348351

@@ -429,7 +432,7 @@ <h2 class="heading-2">My Shopping List</h2>
429432
</ul>
430433

431434
<div class="copyright">
432-
Design &copy; by <a href="https://github.com/jonasschmedtmann/complete-javascript-course/blob/master/9-forkify/starter/src/index.html" target="_blank" class="link">Jonas Schmedtmann</a> <br>
435+
Design &copy; by <a href="https://github.com/jonasschmedtmann/complete-javascript-course/tree/master/9-forkify/starter" target="_blank" class="link">Jonas Schmedtmann</a> <br>
433436
Powered by <a href="https://forkify-api.herokuapp.com" target="_blank" class="link">forkify-api.herokuapp.com</a>.
434437
</div>
435438

Modern-JS-ES6-NPM-Babel-Webpack/forkify_project/dist/js/bundle.js

Lines changed: 100 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Modern-JS-ES6-NPM-Babel-Webpack/forkify_project/src/index.html

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -343,6 +343,9 @@ <h2 class="heading-2">How to cook it</h2>
343343

344344
<div class="shopping">
345345
<h2 class="heading-2">My Shopping List</h2>
346+
347+
<div id="delete-all-btn"></div>
348+
<!-- <button>Delete All Items</button> this goes inside the div above-->
346349

347350
<ul class="shopping__list">
348351

Modern-JS-ES6-NPM-Babel-Webpack/forkify_project/src/js/index.js

Lines changed: 73 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,38 @@
11
/********************************************************************************************************************
2-
* Now we will handle the UI for the likes. Whenever the user clicks on the love button for a recipe,
3-
* the love button has to be shown as it is a clicked love button. For that, we add logic inside the recipeView
4-
* Module found at ./src/js/views/recipeView.js.
2+
* What we'll learn:
3+
* ----------------
4+
* 1. How to use the localStorage() API.
5+
* 2. How to set, get and delete items from local storage.
56
*
6-
* Also, the love button should be persistent, it means that even if we reload the page, the recipe should be liked
7-
* and that love button should show that the recipe was liked. We also implement that functionality
8-
* by changing the recipeView module where we modify the renderRecipe() method. Note that the recipe won't be rendered
9-
* when we reload and that's because state.likes object is not available at the time of the call of renderRecipe()
10-
* inside the controlRecipe() controller. For that reason, we simply use a global state.likes object for testing
11-
* purposes. We can see that global state.likes object on top of the actual Likes Controller which is controlLikes()
12-
* controller method.
7+
* We will persist the likes data so that the liked recipes are kept even after the page reloads.
138
*
14-
* Also, we don't want to show the heart icon at the top right of the webapp if there are no recipes that are liked
15-
* by the user yet. Therefore, we will implement the functionality to hide the heart icon at the top right in the
16-
* likesView Module.
9+
* The Web Storage API allows us to keep the key-value pairs in the browser. This data will be persistent
10+
* even after the page reloads.
1711
*
18-
* As the final step, we will render the likes into the heart icon's list when we hover over it.
19-
* Implementation of the method related to rendering the likes can be found in the likesView Module.
12+
* We use the localStorage() method available inside the window object as follows:
13+
* localStorage.setItem(<key>, <value>); where <key> and <value> always have to be a string.
14+
* ex: localStorage.setItem('id', '233f3e');
15+
*
16+
* To get the item that we stored in the localStorage, we simple use localStorage.getItem(<key>);
17+
* where <key> is again a string type and getItem() returns the <value> associated to that <key>.
18+
* ex: localStorage.getItem('id'); // '233f3e'
19+
*
20+
* More examples:
21+
* localStorage.setItem('recipe', 'Cheese Pizza');
22+
* localStorage; // will return us window.Storage object which will store the key-value pairs.
23+
*
24+
* to remove a key-value pair from localStorage, we simply call: localStorage.removeItem(<key>);
25+
* ex: localStorage.removeItem('recipe'); // removes 'Cheese Pizza' and the key associated to it, which is
26+
* 'recipe' from the window.Storage object.
27+
*
28+
* To get the number of key-value pairs stored inside the window.Storage object, we call localStorage.length;
29+
* property.
30+
*
31+
* To implement the likes data persistency, we use the localStorage() API inside the Likes Model at ./src/js/modes/Likes.js
32+
*
33+
* We implement two methods, persistData() and readStorage(). The persistData() method is called every time a recipe
34+
* is added/deleted from the likes model. And readStorage() is only called when we load the webapp in
35+
* the browser. Therefore, we call the readStorage on the 'load' event in the window.
2036
*/
2137

2238
// Import Data Models
@@ -49,7 +65,7 @@ const controlSearch = async () => {
4965
// 1. Get the search query from the view
5066
// const query = 'pizza'; // for now, this is just a placeholder string.
5167
const query = searchView.getInput(); // we get the search query from the searchView module that we imported
52-
console.log(query); // testing purposes
68+
// console.log(query); // testing purposes
5369

5470
if (query) {
5571
// 2. If there's a query, then add it to the state as a search object.
@@ -178,6 +194,9 @@ const controlList = () => {
178194
if (!state.list)
179195
state.list = new List();
180196

197+
// Add the delete all items button
198+
listView.renderDeleteBtn();
199+
181200
// Add each ingredient to the list and the UI
182201
state.recipe.ingredients.forEach(el => {
183202
const item = state.list.addItem(el.count, el.unit, el.ingredient);
@@ -210,13 +229,21 @@ elements.shopping.addEventListener('click', event => {
210229
});
211230

212231

232+
// Handle deleting all the items in the shopping list
233+
document.querySelector('#delete-all-btn').addEventListener('click', event => {
234+
listView.deleteAllItems();
235+
listView.removeDeleteBtn();
236+
state.list.deleteAllItems();
237+
});
213238

214239
// LIKES CONTROLLER
215-
state.likes = new Likes(); // TESTING
216-
likesView.toggleLikeMenu(state.likes.getNumberOfLikedItems()); // TESTING
240+
241+
// This same testing code goes inside the 'load' event handler for window object.
242+
// state.likes = new Likes(); // TESTING
243+
// likesView.toggleLikeMenu(state.likes.getNumberOfLikedItems()); // TESTING
217244

218245
const controlLike = () => {
219-
// if (!state.likes) state.likes = new Likes();
246+
if (!state.likes) state.likes = new Likes();
220247
const currentID = state.recipe.id;
221248

222249
// If a recipe is liked, then we have to include it in state.likes map, otherwise we don't.
@@ -252,6 +279,33 @@ const controlLike = () => {
252279
};
253280

254281

282+
// Handling liked recipes on page load/reload
283+
window.addEventListener('load', () => {
284+
state.likes = new Likes();
285+
286+
// Restore likes
287+
state.likes.readStorage();
288+
289+
// Toggle like menu button
290+
likesView.toggleLikeMenu(state.likes.getNumberOfLikedItems());
291+
292+
// Render the existing likes
293+
state.likes.likes.forEach((val, id) => {
294+
// Each key-value pair inside the Map is converted into an object
295+
const like = {
296+
id,
297+
value: {
298+
title: val.title,
299+
author: val.author,
300+
img: val.img
301+
}
302+
};
303+
304+
likesView.renderLike(like);
305+
});
306+
});
307+
308+
255309
// Handling recipe button clicks
256310
elements.recipe.addEventListener('click', event => {
257311
// This time we cannot use the closest() method because this time we have two buttons that can be clicked.

Modern-JS-ES6-NPM-Babel-Webpack/forkify_project/src/js/models/Likes.js

Lines changed: 61 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
export default class Likes {
22
constructor() {
33
/** Array Version */
4-
// this.likes = [];
4+
// this.likesArr = [];
55

66
/** Map Version */
77
this.likes = new Map();
@@ -11,23 +11,37 @@ export default class Likes {
1111
addLikedItem(id, title, author, img) {
1212
/** Array Version */
1313
// const likedItem = { id, title, author, img };
14-
// this.likes.push(likedItem);
14+
// this.likesArr.push(likedItem);
15+
16+
// Persist data in window.Storage using localStorage()
17+
// this.persistData();
18+
1519
// return likedItem;
1620

1721
/** Map Version */
1822
this.likes.set(id, { title, author, img });
23+
24+
// Persist data in window.Storage using localStorage()
25+
this.persistData();
26+
1927
return { id, value: this.likes.get(id) };
2028
}
2129

2230

2331
deleteLikedItem(id) {
2432
/** Array Version */
25-
// const index = this.likes.findIndex(el => el.id === id);
33+
// const index = this.likesArr.findIndex(el => el.id === id);
2634
// this.likes.splice(index, 1);
2735

36+
// // Persist data in window.Storage using localStorage()
37+
// this.persistData();
38+
2839
/** Map Version */
2940
if (this.likes.has(id))
3041
this.likes.delete(id);
42+
43+
// Persist data in window.Storage using localStorage()
44+
this.persistData();
3145
}
3246

3347
// When we load a particular recipe, we have to know whether that recipe has been liked previously
@@ -47,4 +61,48 @@ export default class Likes {
4761
/** Map Version */
4862
return this.likes.size;
4963
}
64+
65+
persistData() {
66+
// we know that both key-value pairs should be a string, therefore, we can convert the likes
67+
// object into a string using JSON.stringify() method and pass this.likes onto the method.
68+
// convert the Map elements into JSON format:
69+
const likesArr = [];
70+
for (let [key, value] of this.likes.entries()) {
71+
// console.log(key, value);
72+
likesArr.push({
73+
id: key,
74+
title: value.title,
75+
author: value.author,
76+
img: value.img
77+
});
78+
}
79+
80+
81+
// this.likesArr = likesArr;
82+
// localStorage.clear();
83+
localStorage.setItem('likes', JSON.stringify(likesArr));
84+
}
85+
86+
readStorage() {
87+
// To retrieve the data stored in the localStorage for likes data persistency.
88+
const storage = JSON.parse(localStorage.getItem('likes'));
89+
90+
// Restoring the likes from the localStorage, convert the likesArr back to a Map() object
91+
if (storage) {
92+
/** Array Version */
93+
// this.likesArr = storage;
94+
95+
/** Map Version */
96+
storage.forEach(el => {
97+
if (!this.likes.has(el.id)) {
98+
this.likes.set(el.id, {
99+
title: el.title,
100+
author: el.author,
101+
img: el.img
102+
});
103+
}
104+
});
105+
}
106+
}
107+
50108
}

Modern-JS-ES6-NPM-Babel-Webpack/forkify_project/src/js/models/List.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@ export default class List {
5858
this.items.get(id).count = newCount;
5959
}
6060

61-
61+
deleteAllItems() {
62+
this.items.clear();
63+
}
6264

6365
}

Modern-JS-ES6-NPM-Babel-Webpack/forkify_project/src/js/views/likesView.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,6 @@ export const renderLike = like => {
5151

5252

5353
export const deleteLike = id => {
54-
const el = document.querySelector(`.likes__link[href="#${id}"]`).parentElement;
54+
const el = document.querySelector(`.likes__link[href*="#${id}"]`).parentElement;
5555
if (el) el.parentElement.removeChild(el);
5656
};

Modern-JS-ES6-NPM-Babel-Webpack/forkify_project/src/js/views/listView.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,3 +50,22 @@ export const deleteItem = id => {
5050
if (item) item.parentElement.removeChild(item);
5151
};
5252

53+
export const renderDeleteBtn = () => {
54+
const element = document.querySelector('#delete-all-btn');
55+
console.log(element.hasChildNodes());
56+
console.log(element);
57+
if (!element.hasChildNodes()) {
58+
const markup = `<button class="btn" id="delete-all-btn_">Delete All Items</button>`;
59+
element.insertAdjacentHTML('afterbegin', markup);
60+
}
61+
};
62+
63+
64+
export const removeDeleteBtn = () => {
65+
document.querySelector('#delete-all-btn').innerHTML = '';
66+
};
67+
68+
69+
export const deleteAllItems = () => {
70+
document.querySelector('.shopping__list').innerHTML = '';
71+
};

Modern-JS-ES6-NPM-Babel-Webpack/forkify_project/src/js/views/searchView.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,16 @@ export const clearResults = () => {
2727
export const highlightSelected = id => {
2828
// Before highlighting the selected recipe from the .results__list class, we have to remove the
2929
// .results__link--active class from all the recipes in the .results__list class' elements.
30-
const recipeArr = Array.from(document.querySelectorAll(`.results__link--active`));
30+
const recipeArr = Array.from(document.querySelectorAll(`.results__link`));
3131
recipeArr.forEach(el => {
3232
el.classList.remove('results__link--active');
3333
});
3434

3535
// highlight the selected recipe from .results__list class
36-
document.querySelector(`.results__link[href="#${id}"]`).classList.add('results__link--active');
36+
const element = document.querySelector(`.results__link[href*="#${id}"]`);
37+
if (element) {
38+
element.classList.add('results__link--active');
39+
}
3740
};
3841

3942

0 commit comments

Comments
 (0)