mirror of
https://github.com/Nezumi-2711/practice-js.git
synced 2026-09-22 05:32:05 +00:00
Update code
- Implement pagination - Updating recipe servings - Upating Algorithm
This commit is contained in:
@@ -1,2 +1,3 @@
|
|||||||
export const API_URL = 'https://forkify-api.herokuapp.com/api/v2/recipes';
|
export const API_URL = 'https://forkify-api.herokuapp.com/api/v2/recipes';
|
||||||
export const TIMEOUT_SEC = 10;
|
export const TIMEOUT_SEC = 10;
|
||||||
|
export const RES_PER_PAGE = 10;
|
||||||
|
|||||||
+32
-6
@@ -2,13 +2,14 @@ import * as model from './model.js';
|
|||||||
import recipeView from './views/recipeView.js';
|
import recipeView from './views/recipeView.js';
|
||||||
import searchView from './views/searchView.js';
|
import searchView from './views/searchView.js';
|
||||||
import resultsView from './views/resultsView.js';
|
import resultsView from './views/resultsView.js';
|
||||||
|
import paginationView from './views/paginationView.js';
|
||||||
|
|
||||||
import 'core-js/stable';
|
import 'core-js/stable';
|
||||||
import 'regenerator-runtime/runtime';
|
import 'regenerator-runtime/runtime';
|
||||||
|
|
||||||
if (module.hot) {
|
// if (module.hot) {
|
||||||
module.hot.accept();
|
// module.hot.accept();
|
||||||
}
|
// }
|
||||||
|
|
||||||
const controlRecipes = async function () {
|
const controlRecipes = async function () {
|
||||||
try {
|
try {
|
||||||
@@ -17,6 +18,9 @@ const controlRecipes = async function () {
|
|||||||
if (!id) return;
|
if (!id) return;
|
||||||
recipeView.renderSpinner();
|
recipeView.renderSpinner();
|
||||||
|
|
||||||
|
// 0) Update results view to mark selected search results
|
||||||
|
resultsView.update(model.getSearchResultsPage());
|
||||||
|
|
||||||
// 1)Loading recipe
|
// 1)Loading recipe
|
||||||
await model.loadRecipe(id);
|
await model.loadRecipe(id);
|
||||||
|
|
||||||
@@ -27,7 +31,7 @@ const controlRecipes = async function () {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const controlSearchResults = async () => {
|
const controlSearchResults = async function () {
|
||||||
try {
|
try {
|
||||||
resultsView.renderSpinner();
|
resultsView.renderSpinner();
|
||||||
|
|
||||||
@@ -39,15 +43,37 @@ const controlSearchResults = async () => {
|
|||||||
await model.loadSearchResults(query);
|
await model.loadSearchResults(query);
|
||||||
|
|
||||||
// 3) Render results
|
// 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) {
|
} catch (error) {
|
||||||
console.error(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.addHandleRender(controlRecipes);
|
||||||
|
recipeView.addHandlerUpdateServings(controlServings);
|
||||||
searchView.addHandlerSearch(controlSearchResults);
|
searchView.addHandlerSearch(controlSearchResults);
|
||||||
|
paginationView.addHandlerClick(controlPagination);
|
||||||
};
|
};
|
||||||
|
|
||||||
init();
|
init();
|
||||||
|
|||||||
@@ -14,8 +14,6 @@ export const getJSON = async function (url) {
|
|||||||
const res = await Promise.race([fetch(url), timeout(TIMEOUT_SEC)]);
|
const res = await Promise.race([fetch(url), timeout(TIMEOUT_SEC)]);
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
|
|
||||||
console.log(data);
|
|
||||||
|
|
||||||
if (!res.ok) throw new Error(`${data.message} (${res.status})`);
|
if (!res.ok) throw new Error(`${data.message} (${res.status})`);
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
|
|||||||
+20
-3
@@ -1,5 +1,5 @@
|
|||||||
import { async } from 'regenerator-runtime';
|
import { async } from 'regenerator-runtime';
|
||||||
import { API_URL } from './config';
|
import { API_URL, RES_PER_PAGE } from './config';
|
||||||
import { getJSON } from './helpers';
|
import { getJSON } from './helpers';
|
||||||
|
|
||||||
export const state = {
|
export const state = {
|
||||||
@@ -7,6 +7,8 @@ export const state = {
|
|||||||
search: {
|
search: {
|
||||||
query: '',
|
query: '',
|
||||||
results: [],
|
results: [],
|
||||||
|
page: 1,
|
||||||
|
resultsPerPage: RES_PER_PAGE,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -26,8 +28,6 @@ export const loadRecipe = async function (id) {
|
|||||||
cookingTime: recipe.cooking_time,
|
cookingTime: recipe.cooking_time,
|
||||||
ingredients: recipe.ingredients,
|
ingredients: recipe.ingredients,
|
||||||
};
|
};
|
||||||
|
|
||||||
console.log(state.recipe);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
@@ -52,3 +52,20 @@ export const loadSearchResults = async function (query) {
|
|||||||
throw error;
|
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;
|
||||||
|
};
|
||||||
|
|||||||
@@ -13,6 +13,34 @@ export default class View {
|
|||||||
this._parentElement.insertAdjacentHTML('afterbegin', markup);
|
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() {
|
_clear() {
|
||||||
this._parentElement.innerHTML = '';
|
this._parentElement.innerHTML = '';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 `
|
||||||
|
<button data-goto="${
|
||||||
|
curPage + 1
|
||||||
|
}" class="btn--inline pagination__btn--next">
|
||||||
|
<span>Page ${curPage + 1}</span>
|
||||||
|
<svg class="search__icon">
|
||||||
|
<use href="${icons}#icon-arrow-right"></use>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Last page
|
||||||
|
if (curPage === numPages && numPages > 1) {
|
||||||
|
return `
|
||||||
|
<button data-goto="${
|
||||||
|
curPage - 1
|
||||||
|
}" class="btn--inline pagination__btn--prev">
|
||||||
|
<svg class="search__icon">
|
||||||
|
<use href="${icons}#icon-arrow-left"></use>
|
||||||
|
</svg>
|
||||||
|
<span>Page ${curPage - 1}</span>
|
||||||
|
</button>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Other pages
|
||||||
|
if (this._data.page < numPages) {
|
||||||
|
return `
|
||||||
|
<button data-goto="${
|
||||||
|
curPage - 1
|
||||||
|
}" class="btn--inline pagination__btn--prev">
|
||||||
|
<svg class="search__icon">
|
||||||
|
<use href="${icons}#icon-arrow-left"></use>
|
||||||
|
</svg>
|
||||||
|
<span>Page ${curPage - 1}</span>
|
||||||
|
</button>
|
||||||
|
<button data-goto="${
|
||||||
|
curPage + 1
|
||||||
|
}" class="btn--inline pagination__btn--next">
|
||||||
|
<span>Page ${curPage + 1}</span>
|
||||||
|
<svg class="search__icon">
|
||||||
|
<use href="${icons}#icon-arrow-right"></use>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Page 1 and there are No other pages
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new PaginationView();
|
||||||
@@ -11,6 +11,15 @@ class RecipeView extends View {
|
|||||||
['hashchange', 'load'].forEach(ev => window.addEventListener(ev, handler));
|
['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() {
|
_generateMarkup() {
|
||||||
return `
|
return `
|
||||||
<figure class="recipe__fig">
|
<figure class="recipe__fig">
|
||||||
@@ -42,12 +51,16 @@ class RecipeView extends View {
|
|||||||
<span class="recipe__info-text">servings</span>
|
<span class="recipe__info-text">servings</span>
|
||||||
|
|
||||||
<div class="recipe__info-buttons">
|
<div class="recipe__info-buttons">
|
||||||
<button class="btn--tiny btn--increase-servings">
|
<button class="btn--tiny btn--update-servings" data-update-to="${
|
||||||
|
this._data.servings - 1
|
||||||
|
}">
|
||||||
<svg>
|
<svg>
|
||||||
<use href="${icons}#icon-minus-circle"></use>
|
<use href="${icons}#icon-minus-circle"></use>
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
<button class="btn--tiny btn--increase-servings">
|
<button class="btn--tiny btn--update-servings" data-update-to="${
|
||||||
|
this._data.servings + 1
|
||||||
|
}">
|
||||||
<svg>
|
<svg>
|
||||||
<use href="${icons}#icon-plus-circle"></use>
|
<use href="${icons}#icon-plus-circle"></use>
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
@@ -11,9 +11,13 @@ class ResultsView extends View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_generateMarkupPreview(data) {
|
_generateMarkupPreview(data) {
|
||||||
|
const id = window.location.hash.slice(1);
|
||||||
|
|
||||||
return `
|
return `
|
||||||
<li class="preview">
|
<li class="preview">
|
||||||
<a class="preview__link preview__link" href="#${data.id}">
|
<a class="preview__link ${
|
||||||
|
data.id === id ? 'preview__link--active' : ''
|
||||||
|
}" href="#${data.id}">
|
||||||
<figure class="preview__fig">
|
<figure class="preview__fig">
|
||||||
<img src="${data.image}" alt="${data.title}" />
|
<img src="${data.image}" alt="${data.title}" />
|
||||||
</figure>
|
</figure>
|
||||||
|
|||||||
Reference in New Issue
Block a user