diff --git a/src/js/config.js b/src/js/config.js
index 540732e..2f8e4c6 100644
--- a/src/js/config.js
+++ b/src/js/config.js
@@ -1,2 +1,3 @@
export const API_URL = 'https://forkify-api.herokuapp.com/api/v2/recipes';
export const TIMEOUT_SEC = 10;
+export const RES_PER_PAGE = 10;
diff --git a/src/js/controller.js b/src/js/controller.js
index b22ff2d..edb7699 100644
--- a/src/js/controller.js
+++ b/src/js/controller.js
@@ -2,13 +2,14 @@ import * as model from './model.js';
import recipeView from './views/recipeView.js';
import searchView from './views/searchView.js';
import resultsView from './views/resultsView.js';
+import paginationView from './views/paginationView.js';
import 'core-js/stable';
import 'regenerator-runtime/runtime';
-if (module.hot) {
- module.hot.accept();
-}
+// if (module.hot) {
+// module.hot.accept();
+// }
const controlRecipes = async function () {
try {
@@ -17,6 +18,9 @@ const controlRecipes = async function () {
if (!id) return;
recipeView.renderSpinner();
+ // 0) Update results view to mark selected search results
+ resultsView.update(model.getSearchResultsPage());
+
// 1)Loading recipe
await model.loadRecipe(id);
@@ -27,7 +31,7 @@ const controlRecipes = async function () {
}
};
-const controlSearchResults = async () => {
+const controlSearchResults = async function () {
try {
resultsView.renderSpinner();
@@ -39,15 +43,37 @@ const controlSearchResults = async () => {
await model.loadSearchResults(query);
// 3) Render results
- resultsView.render(model.state.search.results);
+ resultsView.render(model.getSearchResultsPage());
+
+ // 4) Render initial pagination buttons
+ paginationView.render(model.state.search);
} catch (error) {
console.error(error);
}
};
-const init = () => {
+const controlPagination = goToPage => {
+ // 1) Render new results
+ resultsView.render(model.getSearchResultsPage(goToPage));
+
+ // 2) Render new initial pagination buttons
+ paginationView.render(model.state.search);
+};
+
+const controlServings = function (newServings) {
+ // Update the recipe servings (in state)
+ model.updateServings(newServings);
+
+ // Update the recipe view
+ // recipeView.render(model.state.recipe);
+ recipeView.update(model.state.recipe);
+};
+
+const init = function () {
recipeView.addHandleRender(controlRecipes);
+ recipeView.addHandlerUpdateServings(controlServings);
searchView.addHandlerSearch(controlSearchResults);
+ paginationView.addHandlerClick(controlPagination);
};
init();
diff --git a/src/js/helpers.js b/src/js/helpers.js
index 0b3a8d3..15e7232 100644
--- a/src/js/helpers.js
+++ b/src/js/helpers.js
@@ -14,8 +14,6 @@ export const getJSON = async function (url) {
const res = await Promise.race([fetch(url), timeout(TIMEOUT_SEC)]);
const data = await res.json();
- console.log(data);
-
if (!res.ok) throw new Error(`${data.message} (${res.status})`);
return data;
diff --git a/src/js/model.js b/src/js/model.js
index 005ea20..8b25ead 100644
--- a/src/js/model.js
+++ b/src/js/model.js
@@ -1,5 +1,5 @@
import { async } from 'regenerator-runtime';
-import { API_URL } from './config';
+import { API_URL, RES_PER_PAGE } from './config';
import { getJSON } from './helpers';
export const state = {
@@ -7,6 +7,8 @@ export const state = {
search: {
query: '',
results: [],
+ page: 1,
+ resultsPerPage: RES_PER_PAGE,
},
};
@@ -26,8 +28,6 @@ export const loadRecipe = async function (id) {
cookingTime: recipe.cooking_time,
ingredients: recipe.ingredients,
};
-
- console.log(state.recipe);
} catch (error) {
throw error;
}
@@ -52,3 +52,20 @@ export const loadSearchResults = async function (query) {
throw error;
}
};
+
+export const getSearchResultsPage = function (page = state.search.page) {
+ if (page) state.search.page = page;
+
+ const start = (page - 1) * state.search.resultsPerPage;
+ const end = page * state.search.resultsPerPage;
+
+ return state.search.results.slice(start, end);
+};
+
+export const updateServings = function (newServings) {
+ state.recipe.ingredients.forEach(ing => {
+ ing.quantity = (ing.quantity * newServings) / state.recipe.servings;
+ });
+
+ state.recipe.servings = newServings;
+};
diff --git a/src/js/views/View.js b/src/js/views/View.js
index e5fa4da..3b50d4d 100644
--- a/src/js/views/View.js
+++ b/src/js/views/View.js
@@ -13,6 +13,34 @@ export default class View {
this._parentElement.insertAdjacentHTML('afterbegin', markup);
}
+ update(data) {
+ this._data = data;
+ const newMarkup = this._generateMarkup();
+
+ const newDom = document.createRange().createContextualFragment(newMarkup);
+ const newElements = Array.from(newDom.querySelectorAll('*'));
+ const curElements = Array.from(this._parentElement.querySelectorAll('*'));
+
+ newElements.forEach((newEl, i) => {
+ const curEl = curElements[i];
+ console.log(curEl, newEl.isEqualNode(curEl));
+
+ // Update change TEXT
+ if (
+ !newEl.isEqualNode(curEl) &&
+ newEl.firstChild.nodeValue.trim() !== ''
+ ) {
+ curEl.textContent = newEl.textContent;
+ }
+
+ // Updated changes ATTRIBUTES
+ if (!newEl.isEqualNode(curEl))
+ Array.from(newEl.attributes).forEach(attr =>
+ curEl.setAttribute(attr.name, attr.value)
+ );
+ });
+ }
+
_clear() {
this._parentElement.innerHTML = '';
}
diff --git a/src/js/views/paginationView.js b/src/js/views/paginationView.js
new file mode 100644
index 0000000..5cd5a6a
--- /dev/null
+++ b/src/js/views/paginationView.js
@@ -0,0 +1,79 @@
+import View from './View.js';
+import icons from '../../img/icons.svg';
+
+class PaginationView extends View {
+ _parentElement = document.querySelector('.pagination');
+
+ addHandlerClick(handler) {
+ this._parentElement.addEventListener('click', function (e) {
+ const btn = e.target.closest('.btn--inline');
+ if (!btn) return;
+
+ const goToPage = +btn.dataset.goto;
+
+ handler(goToPage);
+ });
+ }
+
+ _generateMarkup() {
+ const curPage = this._data.page;
+ const numPages = Math.ceil(
+ this._data.results.length / this._data.resultsPerPage
+ );
+
+ // Page 1 and there are other pages
+ if (curPage === 1 && numPages > 1) {
+ return `
+
+ `;
+ }
+
+ // Last page
+ if (curPage === numPages && numPages > 1) {
+ return `
+
+ `;
+ }
+
+ // Other pages
+ if (this._data.page < numPages) {
+ return `
+
+
+ `;
+ }
+
+ // Page 1 and there are No other pages
+ return '';
+ }
+}
+
+export default new PaginationView();
diff --git a/src/js/views/recipeView.js b/src/js/views/recipeView.js
index 14cf122..dff3827 100644
--- a/src/js/views/recipeView.js
+++ b/src/js/views/recipeView.js
@@ -11,6 +11,15 @@ class RecipeView extends View {
['hashchange', 'load'].forEach(ev => window.addEventListener(ev, handler));
}
+ addHandlerUpdateServings(handler) {
+ this._parentElement.addEventListener('click', function (e) {
+ const btn = e.target.closest('.btn--update-servings');
+ if (!btn) return;
+ const { updateTo } = btn.dataset;
+ if (+updateTo > 0) handler(+updateTo);
+ });
+ }
+
_generateMarkup() {
return `
@@ -42,12 +51,16 @@ class RecipeView extends View {
servings