Refactoring code to MVC architecture

This commit is contained in:
2023-08-13 20:44:11 +07:00
parent 20f3c42603
commit 6894474d12
5 changed files with 197 additions and 138 deletions
+17 -1
View File
@@ -10,11 +10,13 @@
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"core-js": "^3.32.0", "core-js": "^3.32.0",
"fractional": "^1.0.0",
"regenerator-runtime": "^0.14.0" "regenerator-runtime": "^0.14.0"
}, },
"devDependencies": { "devDependencies": {
"@parcel/transformer-sass": "^2.9.3", "@parcel/transformer-sass": "^2.9.3",
"parcel": "^2.9.3" "parcel": "^2.9.3",
"process": "^0.11.10"
} }
}, },
"node_modules/@babel/code-frame": { "node_modules/@babel/code-frame": {
@@ -2592,6 +2594,11 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/fractional": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fractional/-/fractional-1.0.0.tgz",
"integrity": "sha512-4huGcVdAAr0ffcQ7wQY3dhAdZqKd6QgGOP4Kd7ioNoBAS7GdzolwnqgjH/jTzzCpg71oTLbOVwcOWOH+BmwqvA=="
},
"node_modules/fsevents": { "node_modules/fsevents": {
"version": "2.3.2", "version": "2.3.2",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
@@ -3337,6 +3344,15 @@
"node": ">=12" "node": ">=12"
} }
}, },
"node_modules/process": {
"version": "0.11.10",
"resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
"integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==",
"dev": true,
"engines": {
"node": ">= 0.6.0"
}
},
"node_modules/react-error-overlay": { "node_modules/react-error-overlay": {
"version": "6.0.9", "version": "6.0.9",
"resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.9.tgz", "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.9.tgz",
+3 -1
View File
@@ -10,10 +10,12 @@
"license": "ISC", "license": "ISC",
"devDependencies": { "devDependencies": {
"@parcel/transformer-sass": "^2.9.3", "@parcel/transformer-sass": "^2.9.3",
"parcel": "^2.9.3" "parcel": "^2.9.3",
"process": "^0.11.10"
}, },
"dependencies": { "dependencies": {
"core-js": "^3.32.0", "core-js": "^3.32.0",
"fractional": "^1.0.0",
"regenerator-runtime": "^0.14.0" "regenerator-runtime": "^0.14.0"
} }
} }
+11 -136
View File
@@ -1,6 +1,8 @@
import icons from '../img/icons.svg'; import * as model from './model.js';
import recipeView from './views/recipeView.js';
const recipeContainer = document.querySelector('.recipe'); import 'core-js/stable';
import 'regenerator-runtime/runtime';
const timeout = function (s) { const timeout = function (s) {
return new Promise(function (_, reject) { return new Promise(function (_, reject) {
@@ -14,150 +16,23 @@ const timeout = function (s) {
/////////////////////////////////////// ///////////////////////////////////////
const renderSpinner = function (parentEl) { const controlRecipes = async function () {
const markup = `
<div class="spinner">
<svg>
<use href="${icons}#icon-loader"></use>
</svg>
</div>
`;
parentEl.innerHTML = '';
parentEl.insertAdjacentHTML('afterbegin', markup);
};
const showRecipe = async function () {
try { try {
const id = window.location.hash.slice(1); const id = window.location.hash.slice(1);
if (!id) return; if (!id) return;
recipeView.renderSpinner();
// 1)Loading recipe // 1)Loading recipe
renderSpinner(recipeContainer); await model.loadRecipe(id);
const res = await fetch(
`https://forkify-api.herokuapp.com/api/v2/recipes/${id}`
);
const data = await res.json();
if (!res.ok) throw new Error(`${data.message} (${res.status})`);
let { recipe } = data.data;
recipe = {
id: recipe.id,
title: recipe.title,
publisher: recipe.publisher,
sourceUrl: recipe.source_url,
image: recipe.image_url,
servings: recipe.servings,
cookingTime: recipe.cooking_time,
ingredients: recipe.ingredients,
};
console.log(recipe);
// 2) Rendering recipe // 2) Rendering recipe
const markup = ` recipeView.render(model.state.recipe);
<figure class="recipe__fig">
<img src="${recipe.image}" alt="${recipe.title}" class="recipe__img" />
<h1 class="recipe__title">
<span>${recipe.title}</span>
</h1>
</figure>
<div class="recipe__details">
<div class="recipe__info">
<svg class="recipe__info-icon">
<use href="${icons}#icon-clock"></use>
</svg>
<span class="recipe__info-data recipe__info-data--minutes">${
recipe.cookingTime
}</span>
<span class="recipe__info-text">minutes</span>
</div>
<div class="recipe__info">
<svg class="recipe__info-icon">
<use href="${icons}#icon-users"></use>
</svg>
<span class="recipe__info-data recipe__info-data--people">${
recipe.servings
}</span>
<span class="recipe__info-text">servings</span>
<div class="recipe__info-buttons">
<button class="btn--tiny btn--increase-servings">
<svg>
<use href="${icons}#icon-minus-circle"></use>
</svg>
</button>
<button class="btn--tiny btn--increase-servings">
<svg>
<use href="${icons}#icon-plus-circle"></use>
</svg>
</button>
</div>
</div>
<div class="recipe__user-generated">
<svg>
<use href="${icons}#icon-user"></use>
</svg>
</div>
<button class="btn--round">
<svg class="">
<use href="${icons}#icon-bookmark-fill"></use>
</svg>
</button>
</div>
<div class="recipe__ingredients">
<h2 class="heading--2">Recipe ingredients</h2>
<ul class="recipe__ingredient-list">
${recipe.ingredients
.map(ing => {
return `
<li class="recipe__ingredient">
<svg class="recipe__icon">
<use href="${icons}#icon-check"></use>
</svg>
<div class="recipe__quantity">${ing.quantity}</div>
<div class="recipe__description">
<span class="recipe__unit">${ing.unit}</span>
${ing.description}
</div>
</li>
`;
})
.join('')}
</ul>
</div>
<div class="recipe__directions">
<h2 class="heading--2">How to cook it</h2>
<p class="recipe__directions-text">
This recipe was carefully designed and tested by
<span class="recipe__publisher">${
recipe.publisher
}</span>. Please check out
directions at their website.
</p>
<a
class="btn--small recipe__btn"
href="h${recipe.sourceUrl}"
target="_blank"
>
<span>Directions</span>
<svg class="search__icon">
<use href="src/img/icons.svg#icon-arrow-right"></use>
</svg>
</a>
</div>
`;
recipeContainer.innerHTML = '';
recipeContainer.insertAdjacentHTML('afterbegin', markup);
} catch (error) { } catch (error) {
alert(error); alert(error);
} }
}; };
['hashchange', 'load'].forEach(ev => window.addEventListener(ev, showRecipe)); ['hashchange', 'load'].forEach(ev =>
window.addEventListener(ev, controlRecipes)
);
+31
View File
@@ -0,0 +1,31 @@
export const state = {
recipe: {},
};
export const loadRecipe = async function (id) {
try {
const res = await fetch(
`https://forkify-api.herokuapp.com/api/v2/recipes/${id}`
);
const data = await res.json();
if (!res.ok) throw new Error(`${data.message} (${res.status})`);
const { recipe } = data.data;
state.recipe = {
id: recipe.id,
title: recipe.title,
publisher: recipe.publisher,
sourceUrl: recipe.source_url,
image: recipe.image_url,
servings: recipe.servings,
cookingTime: recipe.cooking_time,
ingredients: recipe.ingredients,
};
console.log(state.recipe);
} catch (error) {
alert(error);
}
};
+135
View File
@@ -0,0 +1,135 @@
import icons from '../../img/icons.svg';
import fractional from 'fractional';
class RecipeView {
#parentElement = document.querySelector('.recipe');
#data;
render(data) {
this.#data = data;
const markup = this.#generateMarkup();
this.#clear();
this.#parentElement.insertAdjacentHTML('afterbegin', markup);
}
#clear() {
this.#parentElement.innerHTML = '';
}
renderSpinner = function () {
const markup = `
<div class="spinner">
<svg>
<use href="${icons}#icon-loader"></use>
</svg>
</div>
`;
this.#parentElement.innerHTML = '';
this.#parentElement.insertAdjacentHTML('afterbegin', markup);
};
#generateMarkup() {
return `
<figure class="recipe__fig">
<img src="${this.#data.image}" alt="${
this.#data.title
}" class="recipe__img" />
<h1 class="recipe__title">
<span>${this.#data.title}</span>
</h1>
</figure>
<div class="recipe__details">
<div class="recipe__info">
<svg class="recipe__info-icon">
<use href="${icons}#icon-clock"></use>
</svg>
<span class="recipe__info-data recipe__info-data--minutes">${
this.#data.cookingTime
}</span>
<span class="recipe__info-text">minutes</span>
</div>
<div class="recipe__info">
<svg class="recipe__info-icon">
<use href="${icons}#icon-users"></use>
</svg>
<span class="recipe__info-data recipe__info-data--people">${
this.#data.servings
}</span>
<span class="recipe__info-text">servings</span>
<div class="recipe__info-buttons">
<button class="btn--tiny btn--increase-servings">
<svg>
<use href="${icons}#icon-minus-circle"></use>
</svg>
</button>
<button class="btn--tiny btn--increase-servings">
<svg>
<use href="${icons}#icon-plus-circle"></use>
</svg>
</button>
</div>
</div>
<div class="recipe__user-generated">
<svg>
<use href="${icons}#icon-user"></use>
</svg>
</div>
<button class="btn--round">
<svg class="">
<use href="${icons}#icon-bookmark-fill"></use>
</svg>
</button>
</div>
<div class="recipe__ingredients">
<h2 class="heading--2">Recipe ingredients</h2>
<ul class="recipe__ingredient-list">
${this.#data.ingredients.map(this.#generateMarkupIngredient).join('')}
</ul>
</div>
<div class="recipe__directions">
<h2 class="heading--2">How to cook it</h2>
<p class="recipe__directions-text">
This recipe was carefully designed and tested by
<span class="recipe__publisher">${
this.#data.publisher
}</span>. Please check out
directions at their website.
</p>
<a
class="btn--small recipe__btn"
href="h${this.#data.sourceUrl}"
target="_blank"
>
<span>Directions</span>
<svg class="search__icon">
<use href="src/img/icons.svg#icon-arrow-right"></use>
</svg>
</a>
</div>
`;
}
#generateMarkupIngredient(ing) {
return `
<li class="recipe__ingredient">
<svg class="recipe__icon">
<use href="${icons}#icon-check"></use>
</svg>
<div class="recipe__quantity">${
ing.quantity ? new Fraction(ing.quantity).toString() : ''
}</div>
<div class="recipe__description">
<span class="recipe__unit">${ing.unit}</span>
${ing.description}
</div>
</li>
`;
}
}
export default new RecipeView();