mirror of
https://github.com/Nezumi-2711/practice-js.git
synced 2026-09-22 05:32:05 +00:00
Update function
- Implement error and success message. - Implement search results.
This commit is contained in:
-11
@@ -86,8 +86,6 @@
|
||||
|
||||
<div class="search-results">
|
||||
<ul class="results">
|
||||
<a href="#5ed6604591c37cdc054bcd09">Receipt 1</a>
|
||||
<a href="#5ed6604591c37cdc054bca5d">Receipt 2</a>
|
||||
<!--
|
||||
<li class="preview">
|
||||
<a class="preview__link preview__link--active" href="#23456">
|
||||
@@ -151,15 +149,6 @@
|
||||
</svg>
|
||||
</div> -->
|
||||
|
||||
<!-- <div class="error">
|
||||
<div>
|
||||
<svg>
|
||||
<use href="src/img/icons.svg#icon-alert-triangle"></use>
|
||||
</svg>
|
||||
</div>
|
||||
<p>No recipes found for your query. Please try again!</p>
|
||||
</div> -->
|
||||
|
||||
<!--
|
||||
<figure class="recipe__fig">
|
||||
<img src="src/img/test-1.jpg" alt="Tomato" class="recipe__img" />
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
export const API_URL = 'https://forkify-api.herokuapp.com/api/v2/recipes';
|
||||
export const TIMEOUT_SEC = 10;
|
||||
+30
-15
@@ -1,20 +1,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 'core-js/stable';
|
||||
import 'regenerator-runtime/runtime';
|
||||
|
||||
const timeout = function (s) {
|
||||
return new Promise(function (_, reject) {
|
||||
setTimeout(function () {
|
||||
reject(new Error(`Request took too long! Timeout after ${s} second`));
|
||||
}, s * 1000);
|
||||
});
|
||||
};
|
||||
|
||||
// https://forkify-api.herokuapp.com/v2
|
||||
|
||||
///////////////////////////////////////
|
||||
if (module.hot) {
|
||||
module.hot.accept();
|
||||
}
|
||||
|
||||
const controlRecipes = async function () {
|
||||
try {
|
||||
@@ -29,10 +23,31 @@ const controlRecipes = async function () {
|
||||
// 2) Rendering recipe
|
||||
recipeView.render(model.state.recipe);
|
||||
} catch (error) {
|
||||
alert(error);
|
||||
recipeView.renderError(error);
|
||||
}
|
||||
};
|
||||
|
||||
['hashchange', 'load'].forEach(ev =>
|
||||
window.addEventListener(ev, controlRecipes)
|
||||
);
|
||||
const controlSearchResults = async () => {
|
||||
try {
|
||||
resultsView.renderSpinner();
|
||||
|
||||
// 1) Get search query
|
||||
const query = searchView.getQuery();
|
||||
if (!query) return;
|
||||
|
||||
// 2) Load search results
|
||||
await model.loadSearchResults(query);
|
||||
|
||||
// 3) Render results
|
||||
resultsView.render(model.state.search.results);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
|
||||
const init = () => {
|
||||
recipeView.addHandleRender(controlRecipes);
|
||||
searchView.addHandlerSearch(controlSearchResults);
|
||||
};
|
||||
|
||||
init();
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
import { async } from 'regenerator-runtime';
|
||||
import { TIMEOUT_SEC } from './config';
|
||||
|
||||
const timeout = function (s) {
|
||||
return new Promise(function (_, reject) {
|
||||
setTimeout(function () {
|
||||
reject(new Error(`Request took too long! Timeout after ${s} second`));
|
||||
}, s * 1000);
|
||||
});
|
||||
};
|
||||
|
||||
export const getJSON = async function (url) {
|
||||
try {
|
||||
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;
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
+30
-7
@@ -1,15 +1,18 @@
|
||||
import { async } from 'regenerator-runtime';
|
||||
import { API_URL } from './config';
|
||||
import { getJSON } from './helpers';
|
||||
|
||||
export const state = {
|
||||
recipe: {},
|
||||
search: {
|
||||
query: '',
|
||||
results: [],
|
||||
},
|
||||
};
|
||||
|
||||
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 data = await getJSON(`${API_URL}/${id}`);
|
||||
|
||||
const { recipe } = data.data;
|
||||
|
||||
@@ -26,6 +29,26 @@ export const loadRecipe = async function (id) {
|
||||
|
||||
console.log(state.recipe);
|
||||
} catch (error) {
|
||||
alert(error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export const loadSearchResults = async function (query) {
|
||||
try {
|
||||
state.search.query = query;
|
||||
|
||||
const data = await getJSON(`${API_URL}?search=${query}`);
|
||||
|
||||
state.search.results = data.data.recipes.map(rec => {
|
||||
return {
|
||||
id: rec.id,
|
||||
title: rec.title,
|
||||
publisher: rec.publisher,
|
||||
image: rec.image_url,
|
||||
};
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(`${error} 💥💥💥`);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
import icons from '../../img/icons.svg';
|
||||
|
||||
export default class View {
|
||||
_data;
|
||||
|
||||
render(data) {
|
||||
if (!data || (Array.isArray(data) && data.length === 0))
|
||||
return this.renderError();
|
||||
|
||||
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._clear();
|
||||
this._parentElement.insertAdjacentHTML('afterbegin', markup);
|
||||
};
|
||||
|
||||
renderError(message = this._errorMessage) {
|
||||
const markup = `
|
||||
<div class="message">
|
||||
<div>
|
||||
<svg>
|
||||
<use href="${icons}#icon-alert-triangle"></use>
|
||||
</svg>
|
||||
</div>
|
||||
<p>${message}</p>
|
||||
</div>
|
||||
`;
|
||||
|
||||
this._clear();
|
||||
this._parentElement.insertAdjacentHTML('afterbegin', markup);
|
||||
}
|
||||
}
|
||||
+18
-38
@@ -1,41 +1,24 @@
|
||||
import View from './View';
|
||||
import icons from '../../img/icons.svg';
|
||||
import fractional from 'fractional';
|
||||
|
||||
class RecipeView {
|
||||
#parentElement = document.querySelector('.recipe');
|
||||
#data;
|
||||
class RecipeView extends View {
|
||||
_parentElement = document.querySelector('.recipe');
|
||||
_errorMessage = 'We could not find that recipe. Please try another one!';
|
||||
_message = '';
|
||||
|
||||
render(data) {
|
||||
this.#data = data;
|
||||
const markup = this.#generateMarkup();
|
||||
this.#clear();
|
||||
this.#parentElement.insertAdjacentHTML('afterbegin', markup);
|
||||
addHandleRender(handler) {
|
||||
['hashchange', 'load'].forEach(ev => window.addEventListener(ev, handler));
|
||||
}
|
||||
|
||||
#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() {
|
||||
_generateMarkup() {
|
||||
return `
|
||||
<figure class="recipe__fig">
|
||||
<img src="${this.#data.image}" alt="${
|
||||
this.#data.title
|
||||
<img src="${this._data.image}" alt="${
|
||||
this._data.title
|
||||
}" class="recipe__img" />
|
||||
<h1 class="recipe__title">
|
||||
<span>${this.#data.title}</span>
|
||||
<span>${this._data.title}</span>
|
||||
</h1>
|
||||
</figure>
|
||||
|
||||
@@ -45,7 +28,7 @@ class RecipeView {
|
||||
<use href="${icons}#icon-clock"></use>
|
||||
</svg>
|
||||
<span class="recipe__info-data recipe__info-data--minutes">${
|
||||
this.#data.cookingTime
|
||||
this._data.cookingTime
|
||||
}</span>
|
||||
<span class="recipe__info-text">minutes</span>
|
||||
</div>
|
||||
@@ -54,7 +37,7 @@ class RecipeView {
|
||||
<use href="${icons}#icon-users"></use>
|
||||
</svg>
|
||||
<span class="recipe__info-data recipe__info-data--people">${
|
||||
this.#data.servings
|
||||
this._data.servings
|
||||
}</span>
|
||||
<span class="recipe__info-text">servings</span>
|
||||
|
||||
@@ -73,9 +56,6 @@ class RecipeView {
|
||||
</div>
|
||||
|
||||
<div class="recipe__user-generated">
|
||||
<svg>
|
||||
<use href="${icons}#icon-user"></use>
|
||||
</svg>
|
||||
</div>
|
||||
<button class="btn--round">
|
||||
<svg class="">
|
||||
@@ -87,7 +67,7 @@ class RecipeView {
|
||||
<div class="recipe__ingredients">
|
||||
<h2 class="heading--2">Recipe ingredients</h2>
|
||||
<ul class="recipe__ingredient-list">
|
||||
${this.#data.ingredients.map(this.#generateMarkupIngredient).join('')}
|
||||
${this._data.ingredients.map(this._generateMarkupIngredient).join('')}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@@ -96,25 +76,25 @@ class RecipeView {
|
||||
<p class="recipe__directions-text">
|
||||
This recipe was carefully designed and tested by
|
||||
<span class="recipe__publisher">${
|
||||
this.#data.publisher
|
||||
this._data.publisher
|
||||
}</span>. Please check out
|
||||
directions at their website.
|
||||
</p>
|
||||
<a
|
||||
class="btn--small recipe__btn"
|
||||
href="h${this.#data.sourceUrl}"
|
||||
href="h${this._data.sourceUrl}"
|
||||
target="_blank"
|
||||
>
|
||||
<span>Directions</span>
|
||||
<svg class="search__icon">
|
||||
<use href="src/img/icons.svg#icon-arrow-right"></use>
|
||||
<use href="${icons}#icon-arrow-right"></use>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
#generateMarkupIngredient(ing) {
|
||||
_generateMarkupIngredient(ing) {
|
||||
return `
|
||||
<li class="recipe__ingredient">
|
||||
<svg class="recipe__icon">
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
import View from './View.js';
|
||||
import icons from '../../img/icons.svg';
|
||||
|
||||
class ResultsView extends View {
|
||||
_parentElement = document.querySelector('.results');
|
||||
_errorMessage = 'No recipes found for your query! Please try again!';
|
||||
_message = '';
|
||||
|
||||
_generateMarkup() {
|
||||
return this._data.map(this._generateMarkupPreview).join('');
|
||||
}
|
||||
|
||||
_generateMarkupPreview(data) {
|
||||
return `
|
||||
<li class="preview">
|
||||
<a class="preview__link preview__link" href="#${data.id}">
|
||||
<figure class="preview__fig">
|
||||
<img src="${data.image}" alt="${data.title}" />
|
||||
</figure>
|
||||
<div class="preview__data">
|
||||
<h4 class="preview__title">${data.title}</h4>
|
||||
<p class="preview__publisher">${data.publisher}</p>
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
export default new ResultsView();
|
||||
@@ -0,0 +1,22 @@
|
||||
class SearchView {
|
||||
_parentEl = document.querySelector('.search');
|
||||
|
||||
getQuery() {
|
||||
const query = this._parentEl.querySelector('.search__field').value;
|
||||
this._clearInput();
|
||||
return query;
|
||||
}
|
||||
|
||||
_clearInput() {
|
||||
this._parentEl.querySelector('.search__field').value = '';
|
||||
}
|
||||
|
||||
addHandlerSearch(handler) {
|
||||
this._parentEl.addEventListener('submit', function (e) {
|
||||
e.preventDefault();
|
||||
handler();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default new SearchView();
|
||||
Reference in New Issue
Block a user