mirror of
https://github.com/Nezumi-2711/practice-js.git
synced 2026-09-22 13:38:49 +00:00
Implementing Bookmarks Function
This commit is contained in:
+2
-150
@@ -63,20 +63,6 @@
|
||||
No bookmarks yet. Find a nice recipe and bookmark it :)
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- <li class="preview">
|
||||
<a class="preview__link" href="#23456">
|
||||
<figure class="preview__fig">
|
||||
<img src="src/img/test-1.jpg" alt="Test" />
|
||||
</figure>
|
||||
<div class="preview__data">
|
||||
<h4 class="preview__name">
|
||||
Pasta with Tomato Cream ...
|
||||
</h4>
|
||||
<p class="preview__publisher">The Pioneer Woman</p>
|
||||
</div>
|
||||
</a>
|
||||
</li> -->
|
||||
</ul>
|
||||
</div>
|
||||
</li>
|
||||
@@ -85,41 +71,9 @@
|
||||
</header>
|
||||
|
||||
<div class="search-results">
|
||||
<ul class="results">
|
||||
<!--
|
||||
<li class="preview">
|
||||
<a class="preview__link preview__link--active" href="#23456">
|
||||
<figure class="preview__fig">
|
||||
<img src="src/img/test-1.jpg" alt="Test" />
|
||||
</figure>
|
||||
<div class="preview__data">
|
||||
<h4 class="preview__title">Pasta with Tomato Cream ...</h4>
|
||||
<p class="preview__publisher">The Pioneer Woman</p>
|
||||
<div class="preview__user-generated">
|
||||
<svg>
|
||||
<use href="src/img/icons.svg#icon-user"></use>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
-->
|
||||
</ul>
|
||||
<ul class="results"></ul>
|
||||
|
||||
<div class="pagination">
|
||||
<!-- <button class="btn--inline pagination__btn--prev">
|
||||
<svg class="search__icon">
|
||||
<use href="src/img/icons.svg#icon-arrow-left"></use>
|
||||
</svg>
|
||||
<span>Page 1</span>
|
||||
</button>
|
||||
<button class="btn--inline pagination__btn--next">
|
||||
<span>Page 3</span>
|
||||
<svg class="search__icon">
|
||||
<use href="src/img/icons.svg#icon-arrow-right"></use>
|
||||
</svg>
|
||||
</button> -->
|
||||
</div>
|
||||
<div class="pagination"></div>
|
||||
|
||||
<p class="copyright">
|
||||
© Copyright by
|
||||
@@ -142,108 +96,6 @@
|
||||
</div>
|
||||
<p>Start by searching for a recipe or an ingredient. Have fun!</p>
|
||||
</div>
|
||||
|
||||
<!-- <div class="spinner">
|
||||
<svg>
|
||||
<use href="src/img/icons.svg#icon-loader"></use>
|
||||
</svg>
|
||||
</div> -->
|
||||
|
||||
<!--
|
||||
<figure class="recipe__fig">
|
||||
<img src="src/img/test-1.jpg" alt="Tomato" class="recipe__img" />
|
||||
<h1 class="recipe__title">
|
||||
<span>Pasta with tomato cream sauce</span>
|
||||
</h1>
|
||||
</figure>
|
||||
|
||||
<div class="recipe__details">
|
||||
<div class="recipe__info">
|
||||
<svg class="recipe__info-icon">
|
||||
<use href="src/img/icons.svg#icon-clock"></use>
|
||||
</svg>
|
||||
<span class="recipe__info-data recipe__info-data--minutes">45</span>
|
||||
<span class="recipe__info-text">minutes</span>
|
||||
</div>
|
||||
<div class="recipe__info">
|
||||
<svg class="recipe__info-icon">
|
||||
<use href="src/img/icons.svg#icon-users"></use>
|
||||
</svg>
|
||||
<span class="recipe__info-data recipe__info-data--people">4</span>
|
||||
<span class="recipe__info-text">servings</span>
|
||||
|
||||
<div class="recipe__info-buttons">
|
||||
<button class="btn--tiny btn--increase-servings">
|
||||
<svg>
|
||||
<use href="src/img/icons.svg#icon-minus-circle"></use>
|
||||
</svg>
|
||||
</button>
|
||||
<button class="btn--tiny btn--increase-servings">
|
||||
<svg>
|
||||
<use href="src/img/icons.svg#icon-plus-circle"></use>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="recipe__user-generated">
|
||||
<svg>
|
||||
<use href="src/img/icons.svg#icon-user"></use>
|
||||
</svg>
|
||||
</div>
|
||||
<button class="btn--round">
|
||||
<svg class="">
|
||||
<use href="src/img/icons.svg#icon-bookmark-fill"></use>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="recipe__ingredients">
|
||||
<h2 class="heading--2">Recipe ingredients</h2>
|
||||
<ul class="recipe__ingredient-list">
|
||||
<li class="recipe__ingredient">
|
||||
<svg class="recipe__icon">
|
||||
<use href="src/img/icons.svg#icon-check"></use>
|
||||
</svg>
|
||||
<div class="recipe__quantity">1000</div>
|
||||
<div class="recipe__description">
|
||||
<span class="recipe__unit">g</span>
|
||||
pasta
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="recipe__ingredient">
|
||||
<svg class="recipe__icon">
|
||||
<use href="src/img/icons.svg#icon-check"></use>
|
||||
</svg>
|
||||
<div class="recipe__quantity">0.5</div>
|
||||
<div class="recipe__description">
|
||||
<span class="recipe__unit">cup</span>
|
||||
ricotta cheese
|
||||
</div>
|
||||
</li>
|
||||
</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">The Pioneer Woman</span>. Please check out
|
||||
directions at their website.
|
||||
</p>
|
||||
<a
|
||||
class="btn--small recipe__btn"
|
||||
href="http://thepioneerwoman.com/cooking/pasta-with-tomato-cream-sauce/"
|
||||
target="_blank"
|
||||
>
|
||||
<span>Directions</span>
|
||||
<svg class="search__icon">
|
||||
<use href="src/img/icons.svg#icon-arrow-right"></use>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
-->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
+32
-2
@@ -2,7 +2,9 @@ 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 bookmarksView from './views/bookmarksView.js';
|
||||
import paginationView from './views/paginationView.js';
|
||||
import addRecipeView from './views/addRecipeView.js';
|
||||
|
||||
import 'core-js/stable';
|
||||
import 'regenerator-runtime/runtime';
|
||||
@@ -21,10 +23,13 @@ const controlRecipes = async function () {
|
||||
// 0) Update results view to mark selected search results
|
||||
resultsView.update(model.getSearchResultsPage());
|
||||
|
||||
// 1)Loading recipe
|
||||
// 1) Updating bookmarks view
|
||||
bookmarksView.update(model.state.bookmarks);
|
||||
|
||||
// 2)Loading recipe
|
||||
await model.loadRecipe(id);
|
||||
|
||||
// 2) Rendering recipe
|
||||
// 3) Rendering recipe
|
||||
recipeView.render(model.state.recipe);
|
||||
} catch (error) {
|
||||
recipeView.renderError(error);
|
||||
@@ -69,11 +74,36 @@ const controlServings = function (newServings) {
|
||||
recipeView.update(model.state.recipe);
|
||||
};
|
||||
|
||||
const controlAddBookmark = function () {
|
||||
// 1) Add/remove bookmark
|
||||
if (!model.state.recipe.bookmarked) model.addBookmark(model.state.recipe);
|
||||
else model.deleteBookmark(model.state.recipe.id);
|
||||
|
||||
// 2) Update recipe view
|
||||
recipeView.update(model.state.recipe);
|
||||
|
||||
// 3) Render bookmarks
|
||||
bookmarksView.render(model.state.bookmarks);
|
||||
};
|
||||
|
||||
const controlBookmarks = function () {
|
||||
bookmarksView.render(model.state.bookmarks);
|
||||
};
|
||||
|
||||
const controlAddRecipe = function (newRecipe) {
|
||||
console.log(newRecipe);
|
||||
|
||||
// Upload the new recipe data
|
||||
};
|
||||
|
||||
const init = function () {
|
||||
bookmarksView.addHandlerRender(controlBookmarks);
|
||||
recipeView.addHandleRender(controlRecipes);
|
||||
recipeView.addHandlerUpdateServings(controlServings);
|
||||
recipeView.addHandlerAddBookmark(controlAddBookmark);
|
||||
searchView.addHandlerSearch(controlSearchResults);
|
||||
paginationView.addHandlerClick(controlPagination);
|
||||
addRecipeView.addHandlerUpload(controlAddRecipe);
|
||||
};
|
||||
|
||||
init();
|
||||
|
||||
@@ -10,6 +10,7 @@ export const state = {
|
||||
page: 1,
|
||||
resultsPerPage: RES_PER_PAGE,
|
||||
},
|
||||
bookmarks: [],
|
||||
};
|
||||
|
||||
export const loadRecipe = async function (id) {
|
||||
@@ -28,6 +29,10 @@ export const loadRecipe = async function (id) {
|
||||
cookingTime: recipe.cooking_time,
|
||||
ingredients: recipe.ingredients,
|
||||
};
|
||||
|
||||
if (state.bookmarks.some(bookmark => bookmark.id === id))
|
||||
state.recipe.bookmarked = true;
|
||||
else state.recipe.bookmarked = false;
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
@@ -47,6 +52,7 @@ export const loadSearchResults = async function (query) {
|
||||
image: rec.image_url,
|
||||
};
|
||||
});
|
||||
state.search.page = 1;
|
||||
} catch (error) {
|
||||
console.error(`${error} 💥💥💥`);
|
||||
throw error;
|
||||
@@ -69,3 +75,36 @@ export const updateServings = function (newServings) {
|
||||
|
||||
state.recipe.servings = newServings;
|
||||
};
|
||||
|
||||
const persistBookmarks = function (recipe) {
|
||||
localStorage.setItem('bookmarks', JSON.stringify(state.bookmarks));
|
||||
};
|
||||
|
||||
export const addBookmark = function (recipe) {
|
||||
// Add bookmark
|
||||
state.bookmarks.push(recipe);
|
||||
|
||||
// Mark current recipe as bookmarks
|
||||
if (recipe.id === state.recipe.id) state.recipe.bookmarked = true;
|
||||
|
||||
persistBookmarks();
|
||||
};
|
||||
|
||||
export const deleteBookmark = function (id) {
|
||||
// Delete bookmark
|
||||
const index = state.bookmarks.findIndex(el => el.id === id);
|
||||
state.bookmarks.splice(index, 1);
|
||||
|
||||
// Mark current recipe as NOT bookmarked
|
||||
if (id === state.recipe.id) state.recipe.bookmarked = false;
|
||||
|
||||
persistBookmarks();
|
||||
};
|
||||
|
||||
const init = function () {
|
||||
const storage = localStorage.getItem('bookmarks');
|
||||
if (storage) state.bookmarks = JSON.parse(storage);
|
||||
};
|
||||
|
||||
init();
|
||||
console.log(state.bookmarks);
|
||||
|
||||
@@ -3,12 +3,15 @@ import icons from '../../img/icons.svg';
|
||||
export default class View {
|
||||
_data;
|
||||
|
||||
render(data) {
|
||||
render(data, render = true) {
|
||||
if (!data || (Array.isArray(data) && data.length === 0))
|
||||
return this.renderError();
|
||||
|
||||
this._data = data;
|
||||
const markup = this._generateMarkup();
|
||||
|
||||
if (!render) return markup;
|
||||
|
||||
this._clear();
|
||||
this._parentElement.insertAdjacentHTML('afterbegin', markup);
|
||||
}
|
||||
@@ -23,12 +26,12 @@ export default class View {
|
||||
|
||||
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() !== ''
|
||||
newEl.firstChild?.nodeValue.trim() !== '' &&
|
||||
newEl.firstChild?.nodeValue.trim() !== undefined
|
||||
) {
|
||||
curEl.textContent = newEl.textContent;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
import View from './View.js';
|
||||
import icons from '../../img/icons.svg';
|
||||
|
||||
class AddRecipeView extends View {
|
||||
_parentElement = document.querySelector('.upload');
|
||||
|
||||
_window = document.querySelector('.add-recipe-window');
|
||||
_overlay = document.querySelector('.overlay');
|
||||
_btnOpen = document.querySelector('.nav__btn--add-recipe');
|
||||
_btnClose = document.querySelector('.btn--close-modal');
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this._addHandlerShowWindow();
|
||||
this._addHandlerHideWindow();
|
||||
}
|
||||
|
||||
toogleWindow() {
|
||||
this._overlay.classList.toggle('hidden');
|
||||
this._window.classList.toggle('hidden');
|
||||
}
|
||||
|
||||
_addHandlerShowWindow() {
|
||||
this._btnOpen.addEventListener('click', () => this.toogleWindow());
|
||||
}
|
||||
|
||||
_addHandlerHideWindow() {
|
||||
this._overlay.addEventListener('click', this.toogleWindow.bind(this));
|
||||
this._btnClose.addEventListener('click', this.toogleWindow.bind(this));
|
||||
}
|
||||
|
||||
addHandlerUpload(handler) {
|
||||
this._parentElement.addEventListener('submit', function (e) {
|
||||
e.preventDefault();
|
||||
const dataArr = [...new FormData(this)];
|
||||
const data = Object.fromEntries(dataArr);
|
||||
handler(data);
|
||||
});
|
||||
}
|
||||
|
||||
_generateMarkup() {}
|
||||
}
|
||||
|
||||
export default new AddRecipeView();
|
||||
@@ -0,0 +1,21 @@
|
||||
import View from './View.js';
|
||||
import previewView from './previewView.js';
|
||||
import icons from '../../img/icons.svg';
|
||||
|
||||
class BookmarksView extends View {
|
||||
_parentElement = document.querySelector('.bookmarks__list');
|
||||
_errorMessage = 'No bookmarks yet. Find a nice recipe and bookmark it!';
|
||||
_message = '';
|
||||
|
||||
addHandlerRender(handler) {
|
||||
window.addEventListener('load', handler);
|
||||
}
|
||||
|
||||
_generateMarkup() {
|
||||
return this._data
|
||||
.map(bookmark => previewView.render(bookmark, false))
|
||||
.join('');
|
||||
}
|
||||
}
|
||||
|
||||
export default new BookmarksView();
|
||||
@@ -0,0 +1,28 @@
|
||||
import View from './View.js';
|
||||
import icons from '../../img/icons.svg';
|
||||
|
||||
class PreviewView extends View {
|
||||
_parentElement = '';
|
||||
|
||||
_generateMarkup() {
|
||||
const id = window.location.hash.slice(1);
|
||||
|
||||
return `
|
||||
<li class="preview">
|
||||
<a class="preview__link ${
|
||||
this._data.id === id ? 'preview__link--active' : ''
|
||||
}" href="#${this._data.id}">
|
||||
<figure class="preview__fig">
|
||||
<img src="${this._data.image}" alt="${this._data.title}" />
|
||||
</figure>
|
||||
<div class="preview__this._data">
|
||||
<h4 class="preview__title">${this._data.title}</h4>
|
||||
<p class="preview__publisher">${this._data.publisher}</p>
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
export default new PreviewView();
|
||||
@@ -20,6 +20,14 @@ class RecipeView extends View {
|
||||
});
|
||||
}
|
||||
|
||||
addHandlerAddBookmark(handler) {
|
||||
this._parentElement.addEventListener('click', function (e) {
|
||||
const btn = e.target.closest('.btn--bookmark');
|
||||
if (!btn) return;
|
||||
handler();
|
||||
});
|
||||
}
|
||||
|
||||
_generateMarkup() {
|
||||
return `
|
||||
<figure class="recipe__fig">
|
||||
@@ -70,9 +78,11 @@ class RecipeView extends View {
|
||||
|
||||
<div class="recipe__user-generated">
|
||||
</div>
|
||||
<button class="btn--round">
|
||||
<button class="btn--round btn--bookmark">
|
||||
<svg class="">
|
||||
<use href="${icons}#icon-bookmark-fill"></use>
|
||||
<use href="${icons}#icon-bookmark${
|
||||
this._data.bookmarked ? '-fill' : ''
|
||||
}"></use>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import View from './View.js';
|
||||
import previewView from './previewView.js';
|
||||
import icons from '../../img/icons.svg';
|
||||
|
||||
class ResultsView extends View {
|
||||
@@ -7,27 +8,7 @@ class ResultsView extends View {
|
||||
_message = '';
|
||||
|
||||
_generateMarkup() {
|
||||
return this._data.map(this._generateMarkupPreview).join('');
|
||||
}
|
||||
|
||||
_generateMarkupPreview(data) {
|
||||
const id = window.location.hash.slice(1);
|
||||
|
||||
return `
|
||||
<li class="preview">
|
||||
<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>
|
||||
<div class="preview__data">
|
||||
<h4 class="preview__title">${data.title}</h4>
|
||||
<p class="preview__publisher">${data.publisher}</p>
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
`;
|
||||
return this._data.map(result => previewView.render(result, false)).join('');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user