We create the model part, and then get the controller to use it:
First create the Recipe where we fetch the data from the backend. We dynamically assign properties title, author, image…etc to the data that we get back.
What you’ll notice is that the ingredients we get back are whole words. What we want to do is to parse ingredients words into a short-handed format. But in the end, we also want to put them into an array of objects. That way, we can pass the measurement, unit, and text.
src/js/Model/Recipe.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 |
import axios from 'axios' export default class Recipe { constructor(id) { this.id = id; } async getRecipe() { try { const res = await axios(`https://forkify-api.herokuapp.com/api/get?rId=${this.id}`); this.title = res.data.recipe.title; this.author = res.data.recipe.publisher; this.image = res.data.recipe.image_url; this.url = res.data.recipe.source_url; this.ingredients = res.data.recipe.ingredients; console.log(res) } catch (err) { console.log(error) } } calcTime() { // assuming that we need 15 min for each 3 ingredients const numIng = this.ingredients.length; const periods = Math.ceil(numIng/3); this.time = periods * 15; } calcServings() { this.servings = 4; } // change ingredient long names to short parseIngredients() { console.log('parse ingredients'); const unitsLong = ['tablespoons', 'tablespoon', 'ounce', 'ounces', 'teaspoon', 'teaspoons', 'cups', 'pounds']; const unitsShort = ['tbsp', 'tbsp', 'oz', 'oz', 'tsp', 'tsp', 'cup', 'pound']; const newIngredients = this.ingredients.map( el => { // 1) uniform units let ingredient = el.toLowerCase(); unitsLong.forEach((unit, i) => { ingredient = ingredient.replace(unit, unitsShort[i]); }) // 2) remove parenthesis ingredient = ingredient.replace(/ *\([^)]*\) */g, ' ') // 3) parse ingredients into count, unit, and ingredient const arrIng = ingredient.split(' '); const unitIndex = arrIng.findIndex(el2 => unitsShort.includes(el2)); let objIng; if (unitIndex > -1) { // there is a unit // Ex. 4 1/2 cups, arrCount is [4, 1/2] ---> "4+1/2" ---> 4.5 // Ex. 4 cups, arrCunt is [4] const arrCount = arrIng.slice(0, unitIndex); let count; if (arrCount.length === 1) { count = eval(arrIng[0].replace('-', '+')); } else { count = eval(arrIng.slice(0, unitIndex).join('+')); } objIng = { count, unit: arrIng[unitIndex], ingredient: arrIng.slice(unitIndex+1).join(' ') }; } else if (parseInt(arrIng[0], 10)) { // there is NO unit, but 1st element is number objIng = { count: parseInt(arrIng[0], 10), unit: '', ingredient: arrIng.slice(1).join(' ') } } else if (unitIndex === -1) { // there is no unit and no 1st position objIng = { count: 1, unit: '', ingredient }; } return objIng; }); this.ingredients = newIngredients } } |
We do the same thing for Recipe as we did for Search.
We get data from server. Parse it, and do some processing on it.
We also implement functionality for ‘load’ event.
src/js/index.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 |
import Search from './models/Search' import Recipe from './models/Recipes' import * as searchView from './views/searchView' import { elements, renderLoader, clearLoader } from './views/base' const state = {} /////////////// search controller ///////////////////// const controlSearch = async () => { const query = searchView.getInput(); if (query) { state.search = new Search(query); searchView.clearInput(); searchView.clearResults(); renderLoader(elements.searchRes) try { await state.search.getResults() searchView.renderResults(state.search.result) } catch(err) { console.log(err) } clearLoader() } } // other code ////// recipe ///////////// const controlRecipe = async () => { const id = window.location.hash.replace('#', ''); // entire url, then get the hash if (id) { state.recipe = new Recipe(id) try { await state.recipe.getRecipe(); state.recipe.parseIngredients(); state.recipe.calcTime() state.recipe.calcServings(); } catch (err) { console.log(err) } } }; ['hashchange', 'load'].forEach(event => window.addEventListener(event, controlRecipe)) |
Make sure we render the results from our fetch operation. We put the result into a list on the left. The result is basically an array of recipes.
src/js/views/searchView.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
export const renderResults = (recipes, page=1, resPerPage=10) => { const start = (page - 1) * resPerPage; const end = page * resPerPage; recipes.slice(start, end).forEach(recipe => renderRecipe(recipe)) renderButtons(page, recipes.length, resPerPage) } const renderRecipe = receipe => { const markup = `<li> <a class="results__link results__link--active" href="#${receipe.recipe_id}"> <figure class="results__fig"> <img src="${receipe.image_url}" alt="${limitRecipeTitle(receipe.title)}"> </figure> <div class="results__data"> <h4 class="results__name">${limitRecipeTitle(receipe.title)}</h4> <p class="results__author">${receipe.publisher}</p> </div> </a> </li>`; elements.searchResList.insertAdjacentHTML('beforeend', markup) } |