Skip to content

Commit c7ae0cd

Browse files
committed
165 Forkify: Recipe Model (Part 2)
- Added 7.7.10 named as "Building the Recipe (Data) Model [Part 2] Using the eval() and map() methods & Map object"
1 parent 6e59a56 commit c7ae0cd

File tree

2 files changed

+112
-11
lines changed

2 files changed

+112
-11
lines changed

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

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
/********************************************************************************************************************
22
* What we'll learn:
33
* ----------------
4-
* 1. How to read data from the page URL.
5-
* 2. How to respond to the 'hashchange' event.
6-
* 3. How to add the same event listener to multiple events.
4+
* 1. Use array methods like map(), slice(), findIndex() and includes().
5+
* 2. How and why to use the eval() method.
76
*
8-
* Whenever we click on one of the recipes that is shown in .results_list class' element, we get the ID of the recipe
9-
* in the URL as: 'localhost:8080/?#46956'. Now the URL has a hash-link (after the '?') which has the ID
10-
* of the respective recipe we clicked. Therefore, we can take advantage of that using the 'hashchange' event
11-
* in the DOM, using an event listener for that event. The code is implemented below.
7+
* This time, we want to read through the list of ingredients and at the same time, separate the quantity
8+
* (count), unit and the description of each ingredient such that we can increase/decrease the quantities
9+
* of the ingredients depending on the number of servings the user wants.
10+
*
11+
* This is done by implementing the parseIngredients() method at ./src/js/models/Recipe.js module.
12+
* Look into the Recipe model module to know more about the parseIngredients() method.
1213
*
1314
*/
1415

@@ -109,11 +110,14 @@ const controlRecipe = async () => {
109110

110111
// Create new recipe object
111112
state.recipe = new Recipe(id);
113+
// window.r = state.recipe; // TEST CODE
114+
112115

113116
try {
114-
// Get the recipe data
117+
// Get the recipe data and parse ingredients
115118
await state.recipe.getRecipe();
116-
119+
state.recipe.parseIngredients();
120+
117121
// Calculate servings of the recipe and time required to make the recipe.
118122
state.recipe.calcServings();
119123
state.recipe.calcTime();
@@ -137,5 +141,3 @@ const controlRecipe = async () => {
137141
// Instead of adding the same event handler - controlRecipe() for two events on window, we can simply
138142
// do it in a single line using the forEach() method as shown below.
139143
['hashchange', 'load'].forEach(event => window.addEventListener(event, controlRecipe));
140-
141-
// Next, we will process the ingredients that we receive we have in the Recipe Model, at ./src/js/models/Recipe.js

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

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,4 +59,103 @@ export default class Recipe {
5959
// by just saying that every dish will serve 4 people no matter what.
6060
this.servings = 4;
6161
}
62+
63+
parseIngredients() {
64+
// We create 2 Maps where one map stores the units that can occur in plural form,
65+
// for eg: teaspoons, tablespoons, etc and then store their values into short-form
66+
// units, which are tsp, tbsp, etc, respectively. And another map for units that
67+
// can occur in singular form i.e., teaspoon, tablespoon, etc. We will convert all of
68+
// the singular units also into an their respective short-forms as show n above.
69+
const abbrUnits = ['tbsp', 'tsp', 'cup', 'oz', 'pound'];
70+
71+
const pluralUnits = new Map();
72+
pluralUnits.set('tablespoons', abbrUnits[0]);
73+
pluralUnits.set('teaspoons', abbrUnits[1]);
74+
pluralUnits.set('cups', 'cup');
75+
pluralUnits.set('ounces', abbrUnits[3]);
76+
pluralUnits.set('pounds', abbrUnits[4]);
77+
78+
const singularUnits = new Map();
79+
singularUnits.set('tablespoon', abbrUnits[0]);
80+
singularUnits.set('teaspoon', abbrUnits[1]);
81+
singularUnits.set('ounce', abbrUnits[3]);
82+
83+
const newIngredients = this.ingredients.map(el => {
84+
// 1. Uniform units
85+
let ingredient = el.toLowerCase();
86+
for (let [key, value] of pluralUnits.entries()) {
87+
if (ingredient.includes(key))
88+
ingredient = ingredient.replace(key, value);
89+
}
90+
91+
for (let [key, value] of singularUnits.entries()) {
92+
if (ingredient.includes(key))
93+
ingredient = ingredient.replace(key, value);
94+
}
95+
96+
// 2. Remove Parentheses --- Regex taken from:
97+
// https://stackoverflow.com/questions/4292468/javascript-regex-remove-text-between-parentheses
98+
ingredient = ingredient.replace(/\([^)]*\)/g, '');
99+
100+
// 3. Parse ingredients into count, unit and ingredient
101+
// The hardest part of parsing the ingredients is to separate out the count and units.
102+
// That's because in the list of ingredients, some have the count in-front
103+
// and some don't. Therefore, we have to be clever about the way we decouple the quantity
104+
// and the unit of the ingredient. A good way is to use the index of the unit
105+
// and then decouple the quantity from the unit.
106+
const arrIng = ingredient.split(' ');
107+
const unitIndex = arrIng.findIndex(el => abbrUnits.includes(el));
108+
109+
let objIng;
110+
if (unitIndex > -1) {
111+
// There is a unit -- the difficult one to implement.
112+
113+
// first we pick the quantity(s) before the units
114+
// Ex. if arrIng is ['4, '1/2', 'cup', ...] => arrCount is ['4', 1/2']
115+
// Ex. if arrIng is ['4', 'cup', ...] => arrCount is ['4']
116+
const arrCount = arrIng.slice(0, unitIndex);
117+
118+
let count;
119+
if (arrCount.length === 1)
120+
count = eval(arrIng[0].replace('-', '+')); // For the strings which are like: '1-1/2 cup'
121+
else {
122+
// Now, we have the elements of arrCount as strings, but we are also sure that they are
123+
// strings that are numeric in nature. Therefore, whenever we have such a situation, we can
124+
// simply call the eval() method on the string (which is an arithmetic expression), so that it
125+
// can evaluate the arithmetic expression passed to it.
126+
count = eval(arrCount.slice().join('+'));
127+
128+
// if arrCount is ['4', '1/2'], then the line above will convert into '4+1/2' and then that's
129+
// passed onto the eval() method which will then evaluate the expression as JS Code, and actually
130+
// the string as an arithmetic expression and return 4.5
131+
}
132+
133+
objIng = {
134+
count,
135+
unit: arrIng[unitIndex],
136+
ingredient: arrIng.slice(unitIndex + 1).join(' ')
137+
};
138+
139+
} else if (parseInt(arrIng[0], 10)) {
140+
// There is NO unit, but 1st element is a number
141+
objIng = {
142+
count: parseInt(arrIng[0], 10),
143+
unit: '',
144+
ingredient: arrIng.slice(1).join(' ')
145+
};
146+
} else if (unitIndex === -1) {
147+
// There is NO unit and NO number in 1st position
148+
objIng = {
149+
count: 1,
150+
unit: '',
151+
ingredient // In ES6, to mention 'ingredient: ingredient', we need not assign it, we simply
152+
// mention it. Therefore, 'ingredient: ingredient' can be written as 'ingredient'
153+
};
154+
}
155+
156+
return objIng;
157+
});
158+
159+
this.ingredients = newIngredients;
160+
}
62161
};

0 commit comments

Comments
 (0)