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 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 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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
+20
-3
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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 = '';
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
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 `
|
||||
<figure class="recipe__fig">
|
||||
@@ -42,12 +51,16 @@ class RecipeView extends View {
|
||||
<span class="recipe__info-text">servings</span>
|
||||
|
||||
<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>
|
||||
<use href="${icons}#icon-minus-circle"></use>
|
||||
</svg>
|
||||
</button>
|
||||
<button class="btn--tiny btn--increase-servings">
|
||||
<button class="btn--tiny btn--update-servings" data-update-to="${
|
||||
this._data.servings + 1
|
||||
}">
|
||||
<svg>
|
||||
<use href="${icons}#icon-plus-circle"></use>
|
||||
</svg>
|
||||
|
||||
@@ -11,9 +11,13 @@ class ResultsView extends View {
|
||||
}
|
||||
|
||||
_generateMarkupPreview(data) {
|
||||
const id = window.location.hash.slice(1);
|
||||
|
||||
return `
|
||||
<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">
|
||||
<img src="${data.image}" alt="${data.title}" />
|
||||
</figure>
|
||||
|
||||
Reference in New Issue
Block a user