Google Books API: Cannot read property 'thumbnail' of undefined - javascript

I'm doing a simple project, using Google Books API and encountered with the following error:Cannot read property 'thumbnail' of undefined. It seems, that for some queries there is no image or path to it. For example, for books query it works OK and shows related books.
While for great query, it throws an error.
As far as I understood there are not book cover in Google Books API for sme books, and that is why it throws the error. I am trying to fix it with help of lodash library, namely _set function.
document.querySelector('.search-book').addEventListener('click', getBook);
var titleHolder = document.querySelector('.title');
var columns = document.querySelector('.is-parent');
var total = '';
const apiKey = 'AIzaSyCu0GO52L8knIMQ7P_gmazBf_7wlngXqyc';
function getBook() {
var search = document.querySelector('#input').value;
console.log(search);
fetch(
`https://www.googleapis.com/books/v1/volumes?q=${search}:keyes&key=${apiKey}`
)
.then(function(res) {
return res.json();
})
.then(function(data) {
//console.log(data.items);
let items = data.items;
for (var i = 0; i < items.length; i++) {
// Volume info
let item = items[i].volumeInfo;
// Author
let author = item.authors;
// Image link
var imgLink = item.imageLinks.thumbnail;
// Title
let title = item.title;
// Description
let desc = item.description;
if (typeof desc === 'undefined') {
desc = 'No description available';
}
if (typeof author === 'undefined') {
author = 'No author';
}
if (!item.imageLinks.firstChild) {
_.set(
item,
'item.imageLinks',
'thumbnail:https://bulma.io/images/placeholders/128x128.png'
);
console.log(data);
}
total += `
<div class=" card tile is-child is-3 box">
<div class="card-image">
<figure class="image is-4by3">
<img src="${imgLink}" alt="Placeholder image">
</figure>
</div>
<div class="card-content">
<p class="title is-6 has-text-primary has-text-centered is-capitalized">${title}</p>
<p class="title is-6 has-text-primary has-text-centered is-capitalized">${author}</p>
<p class="has-text-black-ter has-text-weight-normal">${desc.slice(
0,
150
) + '...'}</p>
</div>
</div>
`;
console.log(item);
}
columns.innerHTML = total;
});
}
First of all I check if there is a thumbnail property in object, if there is not such property, I use lodash _set function to substitute absent image with placeholder, but does not work. Please could help me with this issue. Either with loadsh function or suggest me another way out.
if (!item.imageLinks.firstChild) {
_.set(
item,
'item.imageLinks',
'thumbnail:https://bulma.io/images/placeholders/128x128.png'
);
console.log(data);
}

i've spent way too much time trying to fix this.
this is how i did it, now it doesn't throw any errors, finally
src={
book.volumeInfo.imageLinks === undefined
? ""
: `${book.volumeInfo.imageLinks.thumbnail}`
}

Related

window.onload doesn't run the whole function (javascript)

I tried to make this function getWeekly() run by default when the site first loads but it only runs this part of the code:
dailyBtn.classList.add("active");
weeklyBtn.classList.remove("active");
monthlyBtn.classList.remove("active");
but not the loop under. But it'll show data when I click on the tags. Any ideas? Thanks.
Git link: https://github.com/thusmiley/time-tracking-dashboard.git
Live site link: https://thusmiley.github.io/time-tracking-dashboard
index.html
<div class="report-bottom">
Daily
Weekly
Monthly
</div>
</div>
<div class="stat-wrapper">
<div class="work-bg bg"></div>
<div class="stat" id="work">
<div class="category">
<h2>Work</h2>
<img src="./images/icon-ellipsis.svg" alt="" />
</div>
<div class="card">
<h3 class="work-current"></h3>
<p class="work-previous"></p>
</div>
</div>
</div>
script.js
let Data = [];
fetch("./data.json")
.then((response) => response.json())
.then((data) => Data.push(...data));
let card = document.querySelectorAll(".card");
let dailyBtn = document.getElementById("daily");
let weeklyBtn = document.getElementById("weekly");
let monthlyBtn = document.getElementById("monthly");
function getDaily() {... }
function getWeekly() {
dailyBtn.classList.remove("active");
weeklyBtn.classList.add("active");
monthlyBtn.classList.remove("active");
for (let i = 0; i < Data.length; i++) {
let splitTitle = Data[i].title.split("");
splitTitle = splitTitle.filter((e) => String(e).trim());
let joinTitle = splitTitle.join("");
let current = document.querySelector(`.${joinTitle.toLowerCase()}-current`);
let previous = document.querySelector(
`.${joinTitle.toLowerCase()}-previous`
);
current.innerHTML = `${Data[i].timeframes.weekly.current + "hrs"}`;
previous.innerHTML = `${
"Last Week - " + Data[i].timeframes.weekly.previous + "hrs"
}`;
}
}
function getMonthly() {... }
window.onload = getWeekly();
The very first time that you load the page Data.length is equal to 0, and that's why the loop doesn't iterate. You are using an asynchronous call to load Data, and when getWeekly() is called for the first time, Data is not ready with the info yet (and it only works after when its ready).
You should wait until Data is completely load first, you can try a callback function or even try $.when() using jquery.

How to add multiple objects to local storage with the same key

I have this piece of code that reads data from an excel sheet, turns them into objects and then display their details in a neat product card
let allHoodies = [
['Hoodie', 'Purple', 'Cotton', '$39.99', 'image/items/hoodies/hoodie(1).jpg'],
['Hoodie', 'Blue', 'Cotton', '$39.99', 'image/items/hoodies/hoodie(2).jpg'],
['Hoodie', 'Green', 'Cotton', '$39.99', 'image/items/hoodies/hoodie(3).jpg']
]
allHoodies.forEach((element, index) => {
let obj = {}
obj.id = index
obj.type = element[0]
obj.color = element[1]
obj.material = element[2]
obj.price = element[3]
obj.imagesrc = element[4]
allHoodies[index] = obj
})
//Evaluating each hoodie and displaying its information in HTML
allHoodies.forEach(function(hoodie) {
let card = `
<div class="card">
<img class="product-image" src="${hoodie.imagesrc}">
<h1 class="product-type">${hoodie.type}</h1>
<p>Color: ${hoodie.color}</p>
<p>${hoodie.material} Read more </p>
<p class="price">${hoodie.price}</p>
<p><button>Buy</button></p>
</div>
`;
// Add the card to the page
document.getElementById('product-container').innerHTML += card;
});
What I'm trying to do is, upon clicking "Buy", it adds multiple items to the local storage although I'm struggling to do it and add multiple ones, it keeps on adding only 1 of them and overwriting the previous one (I'm assuming due to the fact that they have the same key)
Here's what I've tried (which works, but its not my goal):
function addToCart(id){
let hoodie = hoodies[id];
localStorage.setItem('item', JSON.stringify(hoodie));
}
and then I simply add the addToCart() function to the button, would someone guide me and help me figure out how I could actually add multiple ones to the local storage and not just keep overwriting?
Expected result:
Runnable JSFiddle snippet
You can use localStorage#getItem to get the current list, and JSON#parse to convert it to an array of objects. Then, use Array#push to add the current item, and finally, use localStorage#set and JSON#stringify to save the updated list:
function addToCart(id) {
try {
const hoodie = allHoodies[id];
if(hoodie) {
const items = JSON.parse(localStorage.getItem('items') || "[]");
items.push(hoodie);
localStorage.setItem('items', JSON.stringify(items));
}
} catch(e) {
console.log('error adding item');
}
}
Function to show the saved list:
function displayProductsinCart() {
const products = JSON.parse(localStorage.getItem("item") || "[]");
document.getElementById("item-container").innerHTML = products.reduce((cards, product) =>
cards + `<div class="card">
<img class="item-image" src="${product.image}">
<h1 class="product-type">${product.type}</h1>
<p>Color: ${product.color}</p>
<p>${product.description}</p>
<p class="price">${product.price} </p>
<p><button>Buy</button></p>
</div>
`, '');
}

How do I use For Loop in JavaScript to show the list?

I am a beginner in JavaScript and I can't figure out the following problem: I am trying to create a simple JavaScript Movie List. I have 10 lists on the Movie List. I tried to show all of the lists with for loop, but it doesn't work.
Here's the code:
function renderModal() {
for (let i = 0; i < listMovies.length; i++) {
let movieData = listMovies[i];
document.getElementById("poster").src = movieData.img;
document.getElementById("title").innerHTML = movieData.name;
document.getElementById("genre").innerHTML = movieData.genre;
document.getElementById("rating-num").innerHTML = "Rating: "+ movieData.rating + "/10";
document.getElementById("movie-desc").innerHTML = movieData.desc;
document.getElementById("imdb-page").href = movieData.link;
return movieData;
}
}
What do I have to do?
Help me to fix it!.
You can use template tag for list and render it into target element.I am showing an example.
Movie list
<div id="movieList"></div>
template for list
<template id="movieListTemplate">
<div class="movie">
<img src="" class="poster" alt="">
<div class="title"></div>
<div class="genre"></div>
<div class="rating-num"></div>
<div class="movie-desc"></div>
<div class="imdb-page"></div>
</div>
</template>
Javascript code:
if (listMovies.length > 0) {
const movileListTemplate = document.getElementById('movieListTemplate')
const movieRenederElement = document.getElementById('movieList')
for(const movie of listMovies) {
const movieEl = document.importNode(movileListTemplate.content, true)
movieEl.querySelector('.poster').src = movie.img
movieEl.querySelector('.title').textContent = movie.name
//use all queryselector like above
}
}
Your return movieData; will stop the loop dead. Not that running it more than once will change anything since you change the same elements over and over. IDs must be unique.
Here is a useful way to render an array
document.getElementById("container").innerHTML = listMovies.map(movieData => `<img src="${movieData.img}" />
<h3>${movieData.name}</h3>
<p>${movieData.genre}</p>
<p>Rating: ${movieData.rating}/10</p>
<p>${movieData.desc}
IMDB
</p>`).join("<hr/>");
With return movieData, the for loop will ends in advance.You should put it outside the for loop.

How is it possible that piece of code that was working is now ignored?

I have coded a ajax based "JS TABS" containing .JSON file like 10 months ago, now wanted to reuse it, and can't find out why it's not working. I haven't touched it since and don't know where is the bug.
When i click the button to render products nothing prints out - except console telling me: items is undefined = so i moved it inside function changeCategoryItems(categoryId) { } well no errors but nothing renders...can someone help me ?
Here is a codepen reference of what i mean: https://codepen.io/Contemplator191/pen/WNwgypY
And this is JSON : https://api.jsonbin.io/b/5f634e0c302a837e95680846
If codepen is not suitable/allowed here is whole JS for that
let items = [];
const buttons = document.querySelectorAll('button');
const wrapper = document.querySelector('section.products');
buttons.forEach(function (button) {
button.addEventListener('click',event => {
changeCategoryItems(event.target.dataset.category);
});
});
function changeCategoryItems(categoryId) {
let items = [];
const buttons = document.querySelectorAll('button');
const wrapper = document.querySelector('section.products');
const viewItems = (categoryId == 0 ) ? items : items.filter(item => item.category == categoryId);
wrapper.innerHTML = "";
viewItems.forEach(item => {
const div = document.createElement('div');
div.setAttribute("class", "product");
div.innerHTML = createItem(item);
wrapper.appendChild(div);
});
};
function createItem(item) {
return `
<div class="product__img">
<img src="${item.img}" class="">
</div>
<div class="product__name _tc">
<h4 class="">${item.heading}</h4>
</div>
<div class="text-desc product__desc">
<p class="">${item.description}</p>
</div>
<div class="product__bottom-content">
<span class="product__info">${item.info}</span>
${item.btn}
</div>
`
}
fetch('https://api.jsonbin.io/b/5f634e0c302a837e95680846')
.then(function (res) { return res.json() })
.then(function (data) {
items = data.items;
changeCategoryItems(1);
});`
In your fetch you're trying to assign data.items to the items variable but the api doesn't return data with an items node so items is undefined. It's possible the api changed their return format since the last time you used it which would explain why it worked previously.
this seems to fix it
.then(function (data) {
items = data;
changeCategoryItems(1);
});
Your issue is in this line:
items = data.items;
Now, the returned value is an array, hence you can use it as it is.
The updated codepen

How to create multiple HTML elements from an Array?

I have a simple site that is getting a list of books from the Google Books API.
I have a separate file called scripts.js that is getting all the book information (title, author, ISBN, link to the image).
I want to create a div for each book in a gallery style page, where there is a picture of the book and on top of the book is the Title, Author, and ISBN.
I've tried creating the DIV's in Javascript but I want there to be an h3, p, and img inside of each DIV and I can't seem to wrap my head around how I could do that in Javascript.
My HTML code for the gallery:
<div id="content">
<h2>My Bookshelf</h2>
<div class="book">
<!-- The book image is the background of the div -->
<h3 class="book-title">Title</h3>
<p class="book-isbn">ISBN: 000000</p>
<p class="book-author">Authors: ABC</p>
</div>
</div>
My Javascript code that cycles through the JSON file and returns the needed information.
// Returns an array with the book title, ISBN, author, bookmark icon, description, image
apiRequest.onreadystatechange = () => {
if (apiRequest.readyState === 4) {
const response = JSON.parse(apiRequest.response);
var bookList = response.items;
// Removes old search results before display new ones
bookSection.innerHTML = "";
for (let i = 0; i < bookList.length; i++) {
console.log(i);
var title = (bookList[i]["volumeInfo"]["title"]);
try {
var isbn = (bookList[i]["volumeInfo"]["industryIdentifiers"][0]["identifier"]);
} catch (TypeError) {
var isbn = "ISBN Not Available";
}
var author = (bookList[i]["volumeInfo"]["authors"]);
var description = (bookList[i]["description"]);
try {
var image = (bookList[i]["volumeInfo"]["imageLinks"]["thumbnail"]);
} catch (TypeError) {
var image = "img/unavailable.png";
}
}
}
}
You can use template literals to make your job easier.
You can do it like this:
var bookSection = `<div id="content">
<h2>My Bookshelf</h2>
<div class="book">
<!-- The book image is the background of the div -->
<h3 class="book-title">${titleVar}</h3>
<p class="book-isbn">ISBN: ${ISBNVar}</p>
<p class="book-author">Authors: ${AuthorsVar}</p>
</div>
</div>`;
Learn more about template literals from here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals
Your code should look something like this
apiRequest.onreadystatechange = () => {
if (apiRequest.readyState === 4) {
const response = JSON.parse(apiRequest.response);
var bookList = response.items;
// Removes old search results before display new ones
bookSection.innerHTML = "";
let bookListHtmlMarkup = '';
for (let i = 0; i < bookList.length; i++) {
console.log(i);
// Declaring book object
const book = {};
const bookListHtmlMarkup = '';
book['title'] = (bookList[i]["volumeInfo"]["title"]);
try {
book['isbn'] = (bookList[i]["volumeInfo"]["industryIdentifiers"][0]["identifier"]);
} catch (TypeError) {
book['isbn'] = "ISBN Not Available";
}
book['author'] = (bookList[i]["volumeInfo"]["authors"]);
book['description'] = (bookList[i]["description"]);
try {
book['image'] = (bookList[i]["volumeInfo"]["imageLinks"]["thumbnail"]);
} catch (TypeError) {
book['image'] = "img/unavailable.png";
}
bookListHtmlMarkup += `
<div class="book">
<div class="book-image">
<img src="${book.image}" alt="Image unavailable" />
</div>
<div class="book-info">
<h3 class="book-title">${book.title}</h3>
<p class="book-isbn">ISBN: ${book.isbn}</p>
<p class="book-author">Author: ${book.author}</p>
<p class="book-description">Author: ${book.description}</p>
</div>
</div>
`;
}
// Assigning generated markup to innerHTML of bookSection
bookSection.innerHTML = bookListHtmlMarkup;
}
}

Categories