数据未正确显示在屏幕上
Data not populating on screen properly
我正在构建名为 Forkify 的示例食谱应用程序,其中我正在使用 javascript、npm、babel、webpack 并且我正在使用自定义 API获取数据。
API URL
https://forkify-api.herokuapp.com/
搜索示例
https://forkify-api.herokuapp.com/api/search?q=pizza
获取示例
https://forkify-api.herokuapp.com/api/get?rId=47746
它在屏幕上显示配方项目以及该特定配方所需的成分,还有两个按钮 + 和 - 用于添加份量,并在此基础上改变份量和所需成分。
下面是截图和代码文件,以便更好地理解:
index.js
/*
Global state of the app
- search object
- current recipe object
- shopping list object
- liked recipe
*/
import Search from "./models/Search";
import Recipe from "./models/Recipe";
import * as searchView from "./views/searchView";
import * as recipeView from "./views/recipeView";
import { elements, renderLoader, clearLoader } from "./views/base";
const state = {};
/* SEARCH CONTROLLER */
const controlSearch = async () => {
// 1. Get query from the view.
const query = searchView.getInput(); //TODO
if (query) {
// 2. New search object and add it to state.
state.search = new Search(query);
// 3. Prepare UI for results.
searchView.clearInput();
searchView.clearResults();
renderLoader(elements.searchRes);
try {
// 4. Search for recipes.
await state.search.getResults();
// 5. Render results on UI.
clearLoader();
searchView.renderResults(state.search.result);
} catch (error) {
alert("Something wrong with the search...");
clearLoader();
}
}
}
elements.searchForm.addEventListener("submit", e => {
e.preventDefault();
controlSearch();
});
elements.searchResPages.addEventListener("click", e => {
const btn = e.target.closest(".btn-inline");
if (btn) {
const goToPage = parseInt(btn.dataset.goto, 10);
searchView.clearResults();
searchView.renderResults(state.search.result, goToPage);
}
});
/*
RECIPE CONTROLLER
*/
const controlRecipe = async () => {
// Get ID from URL
const id = window.location.hash.replace("#", "");
console.log(id);
if (id) {
// Prepare UI for changes
recipeView.clearRecipe();
renderLoader(elements.recipe); // passing parent
// highlight selected search item
if (state.search) searchView.highlightSelected(id);
// Create new recipe object
state.recipe = new Recipe(id);
try {
// Get recipe data and parse ingredients
await state.recipe.getRecipe();
state.recipe.parseIngredients();
// Calculate servings and time
state.recipe.calcTime();
console.log(state.recipe.ingredients);
state.recipe.calcServings();
// Render recipe
clearLoader();
recipeView.renderRecipe(state.recipe); // to put recipe
} catch (error) {
// console.log(error);
alert("Error processing recipe !");
}
}
};
["hashchange", "load"].forEach(event => window.addEventListener(event, controlRecipe));
// CODE FOR "+" "-" BUTTON IN RECIPE
// handling recipe button clicks
elements.recipe.addEventListener("click", e => {
// btn-decrease * means button decrease "-" with any child
// it will be true if there is button decrease or button decrease with any child
if (e.target.matches(".btn-decrease, .btn-decrease *")) {
// decrease button "-" is clicked
if(state.recipe.servings>1) {
state.recipe.updateServings("dec");
recipeView.updateServingsIngredients(state.recipe);
}
}
else if (e.target.matches(".btn-increase, .btn-increase *")) {
// increase button is clicked
state.recipe.updateServings("inc");
recipeView.updateServingsIngredients(state.recipe);
}
console.log(state.recipe);
});
recipeView.js
import { elements } from "./base";
import { Fraction } from "fractional";
export const clearRecipe = () => {
elements.recipe.innerHTML = "";
};
// To format the decimal number.
const formatCount = count => {
if (count) {
// count = 2.5 --> 5/2 or 2 1/2
// count = 0.5 --> 1/2
const [int, dec] = count.toString().split(".").map(el => parseInt(el, 10));
if (!dec) return count;
if (int === 0) {
const fr = new Fraction(count);
return `${fr.numerator}/${fr.denominator}`;
}
else {
// to show fraction of integer part and decimal part separately ex : 2.5 = 2 1/2
const fr = new Fraction(count - int);
return `${int} ${fr.numerator}/${fr.denominator}`;
}
}
return "?";
};
const createIngredient = ingredient => `
<li class="recipe__item">
<svg class="recipe__icon">
<use href="img/icons.svg#icon-check"></use>
</svg>
<div class="recipe__count">${formatCount(ingredient.count)}</div>
<div class="recipe__ingredient">
<span class="recipe__unit">${ingredient.unit}</span>
${ingredient.ingredient}
</div>
</li>
`;
export const renderRecipe = recipe => {
const markup = `
<figure class="recipe__fig">
<img src="${recipe.img}" alt="${recipe.title}" class="recipe__img">
<h1 class="recipe__title">
<span>${recipe.title}</span>
</h1>
</figure>
<div class="recipe__details">
<div class="recipe__info">
<svg class="recipe__info-icon">
<use href="img/icons.svg#icon-stopwatch"></use>
</svg>
<span class="recipe__info-data recipe__info-data--minutes">${recipe.time}</span>
<span class="recipe__info-text"> minutes</span>
</div>
<div class="recipe__info">
<svg class="recipe__info-icon">
<use href="img/icons.svg#icon-man"></use>
</svg>
<span class="recipe__info-data recipe__info-data--people">${recipe.servings}</span>
<span class="recipe__info-text"> servings</span>
<div class="recipe__info-buttons">
<button class="btn-tiny btn-decrease">
<svg>
<use href="img/icons.svg#icon-circle-with-minus"></use>
</svg>
</button>
<button class="btn-tiny btn-increase">
<svg>
<use href="img/icons.svg#icon-circle-with-plus"></use>
</svg>
</button>
</div>
</div>
<button class="recipe__love">
<svg class="header__likes">
<use href="img/icons.svg#icon-heart-outlined"></use>
</svg>
</button>
</div>
<div class="recipe__ingredients">
<ul class="recipe__ingredient-list">
${recipe.ingredients.map(el => createIngredient(el)).join("")}
</ul>
<button class="btn-small recipe__btn">
<svg class="search__icon">
<use href="img/icons.svg#icon-shopping-cart"></use>
</svg>
<span>Add to shopping list</span>
</button>
</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__by">${recipe.author}</span>. Please check out directions at their website.
</p>
<a class="btn-small recipe__btn" href="${recipe.url}" target="_blank">
<span>Directions</span>
<svg class="search__icon">
<use href="img/icons.svg#icon-triangle-right"></use>
</svg>
</a>
</div>
`;
elements.recipe.insertAdjacentHTML("afterbegin", markup);
}
export const updateServingsIngredients = recipe => {
// update counts
document.querySelector(".recipe__info-data--people").textContent = recipe.servings;
// update ingredients
const countElements = Array.from(document.querySelectorAll(".recipe__count"));
countElements.forEach((el, i) => {
el.textContent = formatCount(recipe.ingredients[i].count);
});
};
Recipe.js
import axios from "axios";
export default class Recipe {
constructor(id) {
this.id = id;
}
async getRecipe() {
try {
// const res = await axios(`https://forkify-api.herokuapp.com/api/search?q=${this.query}`);
const res = await axios(`https://forkify-api.herokuapp.com/api/get?rId=${this.id}`);
this.title = res.data.recipe.title;
this.author = res.data.recipe.publisher;
this.img = res.data.recipe.image_url;
this.url = res.data.recipe.source_url;
this.ingredients = res.data.recipe.ingredients;
} catch (error) {
console.log(error);
alert("Something went wrong :(");
}
}
calcTime() {
// Assuming that we need 15 minutes for each 3 ingredients
const numIng = this.ingredients.length;
const periods = Math.ceil(numIng / 3);
this.time = periods * 15;
}
calcServings() {
this.servings = 4;
}
parseIngredients() {
const unitsLong = ["tablespoons", "tablespoon", "ounces", "ounce", "teaspoons", "teaspoon", "cups", "pounds"];
const unitsShort = ["tbsp", "tbsp", "oz", "oz", "tsp", "tsp", "cup", "pound"];
const units = [...unitsShort, "kg", "g"];
const newIngredients = this.ingredients.map(el => {
// 1. Uniform units
let ingredient = el.toLowerCase();
unitsLong.forEach((unit, i) => {
ingredient = ingredient.replace(unit, unitsShort[i]);
});
// 2. Remove Parenthesis
ingredient = ingredient.replace(/ *\([^)]*\) */g, " ");
// 3. Parse Ingredients into count, unit and ingredients
const arrIng = ingredient.split(" ");
const unitIndex = arrIng.findIndex(el2 => units.includes(el2));
let objIng;
if (unitIndex > -1) {
// there is a unit
// Example 4 1/2 cups, arrCount is [4 , 1/2] --> eval("4+1/2") = 4.5
// Example 4 cups \, arrCount is [4]
const arrCount = arrIng.slice(0, unitIndex);
let count;
if (arrCount.length === 1) {
count = eval(arrIng[0].replace("-", "+"));
}
else {
count = eval(arrIng.slice(0, unitIndex).join("+"));
}
objIng = {
count,
unit: arrIng[unitIndex],
ingredient: arrIng.slice(unitIndex + 1).join(" ")
};
}
else if (parseInt(arrIng[0], 10)) {
// there is no unit but 1st element is number
objIng = {
count: parseInt(arrIng[0], 10),
unit: "",
ingredient: arrIng.slice(1).join(" ")
};
}
else if (unitIndex === -1) {
// there is no unit and no numberin 1st position
objIng = {
count: 1,
unit: "",
ingredient
}
}
// return ingredient;
return objIng;
});
this.ingredients = newIngredients;
}
updateServings(type) {
// servings
const newServings = type === "dec" ? this.servings - 1 : this.servings + 1;
// ingredients
this.ingredients.forEach(ing => {
ing.count *= (newServings.count / this.servings);
});
this.servings = newServings;
}
};
searchView.js
/*
export const add = (a, b) => a + b;
export const multiply = (a, b) => a * b;
export const ID = 23;
*/
import { elements } from "./base";
export const getInput = () => elements.searchInput.value;
export const clearInput = () => {
elements.searchInput.value = "";
};
export const clearResults = () => {
elements.searchResList.innerHTML = "";
elements.searchResPages.innerHTML = "";
};
export const highlightSelected = id => {
const resultsArr = Array.from(document.querySelectorAll(".results__link"));
resultsArr.forEach(el => {
el.classList.remove("results__link--active");
});
document.querySelector(`a[href="#${id}"]`).classList.add("results__link--active");
}
/* EXAMPLE CODE
"pasta with tomato and spinach"
acc:0/acc+curr.length=5 /newTitle =['pasta']
acc:5/acc+curr.length=9 /newTitle =['pasta','with']
acc:9/acc+curr.length=15 /newTitle =['pasta','with','tomato']
acc:15/acc+curr.length=18 /newTitle =['pasta','with','tomato']
acc:18/acc+curr.length=25 /newTitle =['pasta','with','tomato']
*/
const limitRecipeTitle = (title, limit = 17) => {
const newTitle = [];
if (title.length > limit) {
title.split(" ").reduce((acc, curr) => {
if (acc + curr.length <= limit) {
newTitle.push(curr);
}
return acc + curr.length;
}, 0);
// return the results
return `${newTitle.join(' ')}...`;
}
return title;
};
const renderRecipe = recipe => {
const markup = `
<li>
<a class="results__link" href="#${recipe.recipe_id}">
<figure class="results__fig">
<img src="${recipe.image_url}" alt="${recipe.title}">
</figure>
<div class="results__data">
<h4 class="results__name">${limitRecipeTitle(recipe.title)}</h4>
<p class="results__author">${recipe.publisher}</p>
</div>
</a>
</li>
`;
elements.searchResList.insertAdjacentHTML("beforeend", markup);
};
// type: "prev" or "next"
const createButton = (page, type) => `
<button class="btn-inline results__btn--${type}" data-goto=${type === "prev" ? page - 1 : page + 1}>
<span>Page ${ type === "prev" ? page - 1 : page + 1}</span>
<svg class="search__icon">
<use href="img/icons.svg#icon-triangle-${ type === "prev" ? "left" : "right"}"></use>
</svg>
</button>
`
const renderButtons = (page, numResults, resPerPage) => {
const pages = Math.ceil(numResults / resPerPage);
let button;
if (page === 1 && pages > 1) {
// Only button to go to next page.
button = createButton(page, "next");
}
else if (page < pages) {
// Both buttons
button = `
${createButton(page, "prev")}
${createButton(page, "next")}
`;
}
else if (page === pages && pages > 1) {
// Only button to go to previous page.
button = createButton(page, "prev");
}
elements.searchResPages.insertAdjacentHTML("afterbegin", button);
}
export const renderResults = (recipes = [], page = 1, resPerPage = 10) => {
// render results of current page
const start = (page - 1) * resPerPage;
const end = page * resPerPage;
// recipes.slice(start,end).forEach(renderRecipe);
recipes.slice(start, end).forEach(renderRecipe);
// render pagination buttons
renderButtons(page, recipes.length, resPerPage);
};
Search.js
import axios from "axios";
// import {proxy} from "../config";
export default class Search{
constructor(query){
this.query=query;
}
async getResults() {
try{
const res = await axios(`https://forkify-api.herokuapp.com/api/search?q=${this.query}`);
this.result = res.data.recipes;
}
catch(error){
alert(error);
}
};
}
base.js
export const elements = {
searchForm: document.querySelector(".search"),
searchInput: document.querySelector(".search__field"),
searchRes: document.querySelector(".results"),
searchResList: document.querySelector(".results__list"),
searchResPages: document.querySelector(".results__pages"),
recipe:document.querySelector(".recipe")
};
export const elementStrings = {
loader: "loader"
};
export const renderLoader = parent => {
const loader = `
<div class="${elementStrings.loader}">
<svg>
<use href="img/icons.svg#icon-cw">
</use>
</svg>
</div>
`;
parent.insertAdjacentHTML("afterbegin", loader);
};
export const clearLoader = () => {
const loader = document.querySelector(`.${elementStrings.loader}`);
if (loader) loader.parentElement.removeChild(loader);
};
截图
着陆页
正在输入搜索查询作为
披萨
搜索查询的结果
从列表中选择食谱并显示配料和份量的默认值
它显示份量和配料的默认值,但是 + 和 - 按钮应该增加或减少配料和份量,虽然它根据选择增加和减少份量,但在配料中它正在显示“?”
有什么解决办法吗?
我找到了解决方案,问题出在 Recipe.js 文件中:最后一部分是 updateServings(type) 方法宣布。以前在那个方法中我写了
ing.count *= (newServings.count / this.servings);
我把它改成:
ing.count *= (newServings / this.servings);
成功了。
我正在构建名为 Forkify 的示例食谱应用程序,其中我正在使用 javascript、npm、babel、webpack 并且我正在使用自定义 API获取数据。
API URL
https://forkify-api.herokuapp.com/
搜索示例
https://forkify-api.herokuapp.com/api/search?q=pizza
获取示例
https://forkify-api.herokuapp.com/api/get?rId=47746
它在屏幕上显示配方项目以及该特定配方所需的成分,还有两个按钮 + 和 - 用于添加份量,并在此基础上改变份量和所需成分。
下面是截图和代码文件,以便更好地理解:
index.js
/*
Global state of the app
- search object
- current recipe object
- shopping list object
- liked recipe
*/
import Search from "./models/Search";
import Recipe from "./models/Recipe";
import * as searchView from "./views/searchView";
import * as recipeView from "./views/recipeView";
import { elements, renderLoader, clearLoader } from "./views/base";
const state = {};
/* SEARCH CONTROLLER */
const controlSearch = async () => {
// 1. Get query from the view.
const query = searchView.getInput(); //TODO
if (query) {
// 2. New search object and add it to state.
state.search = new Search(query);
// 3. Prepare UI for results.
searchView.clearInput();
searchView.clearResults();
renderLoader(elements.searchRes);
try {
// 4. Search for recipes.
await state.search.getResults();
// 5. Render results on UI.
clearLoader();
searchView.renderResults(state.search.result);
} catch (error) {
alert("Something wrong with the search...");
clearLoader();
}
}
}
elements.searchForm.addEventListener("submit", e => {
e.preventDefault();
controlSearch();
});
elements.searchResPages.addEventListener("click", e => {
const btn = e.target.closest(".btn-inline");
if (btn) {
const goToPage = parseInt(btn.dataset.goto, 10);
searchView.clearResults();
searchView.renderResults(state.search.result, goToPage);
}
});
/*
RECIPE CONTROLLER
*/
const controlRecipe = async () => {
// Get ID from URL
const id = window.location.hash.replace("#", "");
console.log(id);
if (id) {
// Prepare UI for changes
recipeView.clearRecipe();
renderLoader(elements.recipe); // passing parent
// highlight selected search item
if (state.search) searchView.highlightSelected(id);
// Create new recipe object
state.recipe = new Recipe(id);
try {
// Get recipe data and parse ingredients
await state.recipe.getRecipe();
state.recipe.parseIngredients();
// Calculate servings and time
state.recipe.calcTime();
console.log(state.recipe.ingredients);
state.recipe.calcServings();
// Render recipe
clearLoader();
recipeView.renderRecipe(state.recipe); // to put recipe
} catch (error) {
// console.log(error);
alert("Error processing recipe !");
}
}
};
["hashchange", "load"].forEach(event => window.addEventListener(event, controlRecipe));
// CODE FOR "+" "-" BUTTON IN RECIPE
// handling recipe button clicks
elements.recipe.addEventListener("click", e => {
// btn-decrease * means button decrease "-" with any child
// it will be true if there is button decrease or button decrease with any child
if (e.target.matches(".btn-decrease, .btn-decrease *")) {
// decrease button "-" is clicked
if(state.recipe.servings>1) {
state.recipe.updateServings("dec");
recipeView.updateServingsIngredients(state.recipe);
}
}
else if (e.target.matches(".btn-increase, .btn-increase *")) {
// increase button is clicked
state.recipe.updateServings("inc");
recipeView.updateServingsIngredients(state.recipe);
}
console.log(state.recipe);
});
recipeView.js
import { elements } from "./base";
import { Fraction } from "fractional";
export const clearRecipe = () => {
elements.recipe.innerHTML = "";
};
// To format the decimal number.
const formatCount = count => {
if (count) {
// count = 2.5 --> 5/2 or 2 1/2
// count = 0.5 --> 1/2
const [int, dec] = count.toString().split(".").map(el => parseInt(el, 10));
if (!dec) return count;
if (int === 0) {
const fr = new Fraction(count);
return `${fr.numerator}/${fr.denominator}`;
}
else {
// to show fraction of integer part and decimal part separately ex : 2.5 = 2 1/2
const fr = new Fraction(count - int);
return `${int} ${fr.numerator}/${fr.denominator}`;
}
}
return "?";
};
const createIngredient = ingredient => `
<li class="recipe__item">
<svg class="recipe__icon">
<use href="img/icons.svg#icon-check"></use>
</svg>
<div class="recipe__count">${formatCount(ingredient.count)}</div>
<div class="recipe__ingredient">
<span class="recipe__unit">${ingredient.unit}</span>
${ingredient.ingredient}
</div>
</li>
`;
export const renderRecipe = recipe => {
const markup = `
<figure class="recipe__fig">
<img src="${recipe.img}" alt="${recipe.title}" class="recipe__img">
<h1 class="recipe__title">
<span>${recipe.title}</span>
</h1>
</figure>
<div class="recipe__details">
<div class="recipe__info">
<svg class="recipe__info-icon">
<use href="img/icons.svg#icon-stopwatch"></use>
</svg>
<span class="recipe__info-data recipe__info-data--minutes">${recipe.time}</span>
<span class="recipe__info-text"> minutes</span>
</div>
<div class="recipe__info">
<svg class="recipe__info-icon">
<use href="img/icons.svg#icon-man"></use>
</svg>
<span class="recipe__info-data recipe__info-data--people">${recipe.servings}</span>
<span class="recipe__info-text"> servings</span>
<div class="recipe__info-buttons">
<button class="btn-tiny btn-decrease">
<svg>
<use href="img/icons.svg#icon-circle-with-minus"></use>
</svg>
</button>
<button class="btn-tiny btn-increase">
<svg>
<use href="img/icons.svg#icon-circle-with-plus"></use>
</svg>
</button>
</div>
</div>
<button class="recipe__love">
<svg class="header__likes">
<use href="img/icons.svg#icon-heart-outlined"></use>
</svg>
</button>
</div>
<div class="recipe__ingredients">
<ul class="recipe__ingredient-list">
${recipe.ingredients.map(el => createIngredient(el)).join("")}
</ul>
<button class="btn-small recipe__btn">
<svg class="search__icon">
<use href="img/icons.svg#icon-shopping-cart"></use>
</svg>
<span>Add to shopping list</span>
</button>
</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__by">${recipe.author}</span>. Please check out directions at their website.
</p>
<a class="btn-small recipe__btn" href="${recipe.url}" target="_blank">
<span>Directions</span>
<svg class="search__icon">
<use href="img/icons.svg#icon-triangle-right"></use>
</svg>
</a>
</div>
`;
elements.recipe.insertAdjacentHTML("afterbegin", markup);
}
export const updateServingsIngredients = recipe => {
// update counts
document.querySelector(".recipe__info-data--people").textContent = recipe.servings;
// update ingredients
const countElements = Array.from(document.querySelectorAll(".recipe__count"));
countElements.forEach((el, i) => {
el.textContent = formatCount(recipe.ingredients[i].count);
});
};
Recipe.js
import axios from "axios";
export default class Recipe {
constructor(id) {
this.id = id;
}
async getRecipe() {
try {
// const res = await axios(`https://forkify-api.herokuapp.com/api/search?q=${this.query}`);
const res = await axios(`https://forkify-api.herokuapp.com/api/get?rId=${this.id}`);
this.title = res.data.recipe.title;
this.author = res.data.recipe.publisher;
this.img = res.data.recipe.image_url;
this.url = res.data.recipe.source_url;
this.ingredients = res.data.recipe.ingredients;
} catch (error) {
console.log(error);
alert("Something went wrong :(");
}
}
calcTime() {
// Assuming that we need 15 minutes for each 3 ingredients
const numIng = this.ingredients.length;
const periods = Math.ceil(numIng / 3);
this.time = periods * 15;
}
calcServings() {
this.servings = 4;
}
parseIngredients() {
const unitsLong = ["tablespoons", "tablespoon", "ounces", "ounce", "teaspoons", "teaspoon", "cups", "pounds"];
const unitsShort = ["tbsp", "tbsp", "oz", "oz", "tsp", "tsp", "cup", "pound"];
const units = [...unitsShort, "kg", "g"];
const newIngredients = this.ingredients.map(el => {
// 1. Uniform units
let ingredient = el.toLowerCase();
unitsLong.forEach((unit, i) => {
ingredient = ingredient.replace(unit, unitsShort[i]);
});
// 2. Remove Parenthesis
ingredient = ingredient.replace(/ *\([^)]*\) */g, " ");
// 3. Parse Ingredients into count, unit and ingredients
const arrIng = ingredient.split(" ");
const unitIndex = arrIng.findIndex(el2 => units.includes(el2));
let objIng;
if (unitIndex > -1) {
// there is a unit
// Example 4 1/2 cups, arrCount is [4 , 1/2] --> eval("4+1/2") = 4.5
// Example 4 cups \, arrCount is [4]
const arrCount = arrIng.slice(0, unitIndex);
let count;
if (arrCount.length === 1) {
count = eval(arrIng[0].replace("-", "+"));
}
else {
count = eval(arrIng.slice(0, unitIndex).join("+"));
}
objIng = {
count,
unit: arrIng[unitIndex],
ingredient: arrIng.slice(unitIndex + 1).join(" ")
};
}
else if (parseInt(arrIng[0], 10)) {
// there is no unit but 1st element is number
objIng = {
count: parseInt(arrIng[0], 10),
unit: "",
ingredient: arrIng.slice(1).join(" ")
};
}
else if (unitIndex === -1) {
// there is no unit and no numberin 1st position
objIng = {
count: 1,
unit: "",
ingredient
}
}
// return ingredient;
return objIng;
});
this.ingredients = newIngredients;
}
updateServings(type) {
// servings
const newServings = type === "dec" ? this.servings - 1 : this.servings + 1;
// ingredients
this.ingredients.forEach(ing => {
ing.count *= (newServings.count / this.servings);
});
this.servings = newServings;
}
};
searchView.js
/*
export const add = (a, b) => a + b;
export const multiply = (a, b) => a * b;
export const ID = 23;
*/
import { elements } from "./base";
export const getInput = () => elements.searchInput.value;
export const clearInput = () => {
elements.searchInput.value = "";
};
export const clearResults = () => {
elements.searchResList.innerHTML = "";
elements.searchResPages.innerHTML = "";
};
export const highlightSelected = id => {
const resultsArr = Array.from(document.querySelectorAll(".results__link"));
resultsArr.forEach(el => {
el.classList.remove("results__link--active");
});
document.querySelector(`a[href="#${id}"]`).classList.add("results__link--active");
}
/* EXAMPLE CODE
"pasta with tomato and spinach"
acc:0/acc+curr.length=5 /newTitle =['pasta']
acc:5/acc+curr.length=9 /newTitle =['pasta','with']
acc:9/acc+curr.length=15 /newTitle =['pasta','with','tomato']
acc:15/acc+curr.length=18 /newTitle =['pasta','with','tomato']
acc:18/acc+curr.length=25 /newTitle =['pasta','with','tomato']
*/
const limitRecipeTitle = (title, limit = 17) => {
const newTitle = [];
if (title.length > limit) {
title.split(" ").reduce((acc, curr) => {
if (acc + curr.length <= limit) {
newTitle.push(curr);
}
return acc + curr.length;
}, 0);
// return the results
return `${newTitle.join(' ')}...`;
}
return title;
};
const renderRecipe = recipe => {
const markup = `
<li>
<a class="results__link" href="#${recipe.recipe_id}">
<figure class="results__fig">
<img src="${recipe.image_url}" alt="${recipe.title}">
</figure>
<div class="results__data">
<h4 class="results__name">${limitRecipeTitle(recipe.title)}</h4>
<p class="results__author">${recipe.publisher}</p>
</div>
</a>
</li>
`;
elements.searchResList.insertAdjacentHTML("beforeend", markup);
};
// type: "prev" or "next"
const createButton = (page, type) => `
<button class="btn-inline results__btn--${type}" data-goto=${type === "prev" ? page - 1 : page + 1}>
<span>Page ${ type === "prev" ? page - 1 : page + 1}</span>
<svg class="search__icon">
<use href="img/icons.svg#icon-triangle-${ type === "prev" ? "left" : "right"}"></use>
</svg>
</button>
`
const renderButtons = (page, numResults, resPerPage) => {
const pages = Math.ceil(numResults / resPerPage);
let button;
if (page === 1 && pages > 1) {
// Only button to go to next page.
button = createButton(page, "next");
}
else if (page < pages) {
// Both buttons
button = `
${createButton(page, "prev")}
${createButton(page, "next")}
`;
}
else if (page === pages && pages > 1) {
// Only button to go to previous page.
button = createButton(page, "prev");
}
elements.searchResPages.insertAdjacentHTML("afterbegin", button);
}
export const renderResults = (recipes = [], page = 1, resPerPage = 10) => {
// render results of current page
const start = (page - 1) * resPerPage;
const end = page * resPerPage;
// recipes.slice(start,end).forEach(renderRecipe);
recipes.slice(start, end).forEach(renderRecipe);
// render pagination buttons
renderButtons(page, recipes.length, resPerPage);
};
Search.js
import axios from "axios";
// import {proxy} from "../config";
export default class Search{
constructor(query){
this.query=query;
}
async getResults() {
try{
const res = await axios(`https://forkify-api.herokuapp.com/api/search?q=${this.query}`);
this.result = res.data.recipes;
}
catch(error){
alert(error);
}
};
}
base.js
export const elements = {
searchForm: document.querySelector(".search"),
searchInput: document.querySelector(".search__field"),
searchRes: document.querySelector(".results"),
searchResList: document.querySelector(".results__list"),
searchResPages: document.querySelector(".results__pages"),
recipe:document.querySelector(".recipe")
};
export const elementStrings = {
loader: "loader"
};
export const renderLoader = parent => {
const loader = `
<div class="${elementStrings.loader}">
<svg>
<use href="img/icons.svg#icon-cw">
</use>
</svg>
</div>
`;
parent.insertAdjacentHTML("afterbegin", loader);
};
export const clearLoader = () => {
const loader = document.querySelector(`.${elementStrings.loader}`);
if (loader) loader.parentElement.removeChild(loader);
};
截图
着陆页
正在输入搜索查询作为 披萨
搜索查询的结果
从列表中选择食谱并显示配料和份量的默认值
它显示份量和配料的默认值,但是 + 和 - 按钮应该增加或减少配料和份量,虽然它根据选择增加和减少份量,但在配料中它正在显示“?”
有什么解决办法吗?
我找到了解决方案,问题出在 Recipe.js 文件中:最后一部分是 updateServings(type) 方法宣布。以前在那个方法中我写了
ing.count *= (newServings.count / this.servings);
我把它改成:
ing.count *= (newServings / this.servings);
成功了。