Ok I'm trying to consume a simple API and loop through all the data I received and display it on html. I'm stumbling on something simple and I cannot figure out where I'm making the mistake.
Currently I get the data with fetch, however when I try to display that data on html I'm just getting the very first object in the array not all the objects.
I will like to get a list of all the posts in my html.
Thanks in advance
<!DOCTYPE html>
<html lang='en'>
<head>
<meta charset='UTF-8'>
<meta http-equiv='X-UA-Compatible' content='IE=edge'>
<meta name='viewport' content='width=device-width, initial-scale=1.0'>
<title>Document</title>
</head>
<body>
<div class="d-flex justify-content-center">
<div class="spinner-border" role="status" id="loading">
<span class="sr-only">Loading...</span>
</div>
</div>
<h1>List of Posts</h1>
<section id="section">
<ul id='posts'></ul>
</section>
</body>
<script>
const API_URL = `https://jsonplaceholder.typicode.com`
async function fetchPosts() {
const response = await fetch(`${API_URL}/posts`)
let data = await response.json()
// console.log(data)
if (response) {
hideloader();
}
show(data)
}
function hideloader() {
document.getElementById('loading').style.display = 'none';
}
function show(data) {
console.log(data, ' inside show')
const ul = document.getElementById('posts')
const list = document.createDocumentFragment();
let li = document.createElement('li');
let title = document.createElement('h1');
let body = document.createElement('p')
data.map(function (post) {
title.innerHTML = `${post.title}`;
body.innerHTML = `${post.body}`;
li.appendChild(title);
li.appendChild(body);
list.appendChild(li);
// console.log(list)
// console.log(li)
})
ul.appendChild(list);
}
fetchPosts()
</script>
</html>
In the show(data) function, when you map the data, the title.innerHTML and body.innerHTML are reassigned constantly.
You should create the list title and body elements in the iteration.
function show(data) {
const ul = document.getElementById('posts');
const list = document.createDocumentFragment();
data.map(function (post) {
let li = document.createElement('li');
let title = document.createElement('h1');
let body = document.createElement('p');
title.innerHTML = `${post.title}`;
body.innerHTML = `${post.body}`;
li.appendChild(title);
li.appendChild(body);
list.appendChild(li);
});
ul.appendChild(list);
}
Related
I'm trying to fetch data from an api.
So far I have tried to fetch data using an api. I have been using this website: https://www.geeksforgeeks.org/how-to-use-the-javascript-fetch-api-to-get-data/
It currently console.logs my array but it does not show up in the table rows that i have created.
I think i might have created the rows wrong in my html, but i cant figure how they should have been set up otherwise. Please show a small example or what to google if that is what is wrong.
The current error message i get is
Uncaught (in promise) TypeError: data.list is not iterable
at show (news.js:37:21)
at getapi (news.js:17:2)
My javascript looks like this:
// api url
const api_url =
"https:newsapi.org/v2/top-headlines?sources=techcrunch&apiKey=xxxxxxxxx";
// Defining async function
async function getapi(url) {
// Storing response
const response = await fetch(url);
// Storing data in form of JSON
var data = await response.json();
console.log(data);
if (response) {
hideloader();
}
show(data);
}
// Calling that async function
getapi(api_url);
// Function to hide the loader
function hideloader() {
document.getElementById('loading').style.display = 'none';
}
// Function to define innerHTML for HTML table
function show(data) {
let tab =
`<tr>
<th>Author</th>
<th>Title</th>
<th>Description</th>
<th>Url</th>
</tr>`;
// Loop to access all rows
for (let r of data.list) {
tab += `<tr>
<td>${r.author}</td>
<td>${r.title}</td>
<td>${r.description}</td>
<td>${r.url}</td>
</tr>`;
}
// Setting innerHTML as tab variable
document.getElementById("news").innerHTML = tab;
}
<!DOCTYPE html>
<html lang="en">
<head>
<script src="news.js"></script>
<link rel="stylesheet" href="test.css" />
<meta charset="UTF-8" />
<meta name="viewport"
content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<!-- Here a loader is created which
loads till response comes -->
<div class="d-flex justify-content-center">
<div class="spinner-border"
role="status" id="loading">
<span class="sr-only">Loading...</span>
</div>
</div>
<h1>News</h1>
<!-- table for showing data -->
<table id="news"></table>
</body>
</html>
You need to handle the response correctly.
Working example:
// api url
const api_url = 'https://jsonplaceholder.typicode.com/comments';
// Defining async function
async function getapi(url) {
await fetch(url)
.then((response) => response.json())
.then((data) => {
console.log(data)
hideloader();
show(data)
});
}
// Calling that async function
getapi(api_url);
// Function to hide the loader
function hideloader() {
document.getElementById('loading').style.display = 'none';
}
// Function to define innerHTML for HTML table
function show(data) {
let tab =
`<tr>
<th>id</th>
<th>Name</th>
<th>Body</th>
<th>Email</th>
</tr>`;
// Loop to access all rows
for (let r of data) {
tab += `<tr>
<td>${r.id}</td>
<td>${r.name}</td>
<td>${r.body}</td>
<td>${r.email}</td>
</tr>`;
}
// Setting innerHTML as tab variable
document.getElementById("news").innerHTML = tab;
}
<!DOCTYPE html>
<html lang="en">
<head>
<script src="news.js"></script>
<link rel="stylesheet" href="test.css" />
<meta charset="UTF-8" />
<meta name="viewport"
content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<!-- Here a loader is created which
loads till response comes -->
<div class="d-flex justify-content-center">
<div class="spinner-border"
role="status" id="loading">
<span class="sr-only">Loading...</span>
</div>
</div>
<h1>News</h1>
<!-- table for showing data -->
<table id="news"></table>
</body>
</html>
This is what i ended up doing
const url = "xxxxxxx"
fetch(url)
.then((response) => response.json())
.then((data) => {
console.log(data)
let title = document.getElementById("title");
title.innerText = data.articles[0].title;
let title1 = document.getElementById("title1");
title1.innerText = data.articles[1].title;
let title2 = document.getElementById("title2");
title2.innerText = data.articles[2].title;
let title3 = document.getElementById("title3");
title3.innerText = data.articles[3].title;
let title4 = document.getElementById("title4");
title4.innerText = data.articles[4].title;
let title5 = document.getElementById("title5");
title5.innerText = data.articles[5].title;
});
And then just a p tag looking like this:
<p id="title1"></p>
And so on
Im still relatively new to JS. I know i probably shouldnt write my code the way i have done here in the real world, but im only doing this to test my knowledge on for loops and pulling JSON data.
My question is, with the way i have structured my code, is it possible for me to add classnames/Id's to the elements i have made using doc.createElement? for example if i wanted to add custom icons or buttons to each element? I cant seem to think of a way to add them other than having to write out all the HTML and do it that way. Here's my code :
HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="./styles.css">
<title>Document</title>
</head>
<body>
<section>
</section>
<script src="./app.js"></script>
</body>
</html>
JS
const allCustomers = document.querySelector("section");
let custName = "";
let username = "";
let email = "";
let id = "";
const requestURL = "https://jsonplaceholder.typicode.com/users";
fetch(requestURL)
.then((response) => response.text())
.then((text) => DisplayUserInfo(text));
function DisplayUserInfo(userData) {
const userArray = JSON.parse(userData);
for (i = 0; i < userArray.length; i++) {
let listContainer = document.createElement("div");
let myList = document.createElement("p");
let myListItems = document.createElement("span");
myList.textContent = `Customer : ${userArray[i].name}`;
myListItems.innerHTML =`<br>ID: ${userArray[i].id} <br>Email: ${userArray[i].email} <br>Username: ${userArray[i].username}`;
myListItems.appendChild(myList);
listContainer.appendChild(myListItems);
allCustomers.appendChild(listContainer);
}
}
DisplayUserInfo();
Any pointers would be greatly appreciated as well as any constructive feedback. Thanks
Yes, for sure you can add any attribute for a created element. element.classList.add('class-name-here') for adding class, element.id = 'id-name-here' for adding id.
const allCustomers = document.querySelector("section");
let custName = "";
let username = "";
let email = "";
let id = "";
const requestURL = "https://jsonplaceholder.typicode.com/users";
fetch(requestURL)
.then((response) => response.text())
.then((text) => DisplayUserInfo(text));
function DisplayUserInfo(userData) {
const userArray = JSON.parse(userData);
for (i = 0; i < userArray.length; i++) {
let listContainer = document.createElement("div");
let myList = document.createElement("p");
myList.classList.add('active');
myList.id = 'paragraph'
let myListItems = document.createElement("span");
myList.textContent = `Customer : ${userArray[i].name}`;
myListItems.innerHTML =`<br>ID: ${userArray[i].id} <br>Email: ${userArray[i].email} <br>Username: ${userArray[i].username}`;
myListItems.appendChild(myList);
listContainer.appendChild(myListItems);
allCustomers.appendChild(listContainer);
}
}
DisplayUserInfo();
.active {
color: red;
}
#paragraph {
font-size: 24px;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="./styles.css">
<title>Document</title>
</head>
<body>
<section>
</section>
<script src="./app.js"></script>
</body>
</html>
is it possible for me to add classnames/Id's to the elements i have
made using doc.createElement
Yes possible with classList for adding class and setAttribute to add id
let listContainer = document.createElement("div");
// To add class
listContainer.className = 'your-class'; //if you have just one
listContainer.classList.add("my-class");//if you want to add multiple
// To add id
listContainer.setAttribute("id", "your_id");
When you use document.createElement it returns an Element. You can use Element attributes and methods to reach what you need. There are some docs for this class on MDN.
This means you can:
> myDiv = document.createElement("div")
<div></div>
> myDiv.id = "test"
'test'
> myDiv
<div id="test"></div>
For classes you can use the attributes className or classList.
I was unsure how exactly to phrase this in the title. I've made a todo list and I'm working on making the todo items editable. Through the displayTodo function, I've been able to make the li items editable in the DOM, but I would like this change to be reflected in the todoList array as well when I hit the save button. I'm unsure of how exactly I would be able to make this work. I was thinking of the splice method, but I don't know how that would work in this situation since I would need to pass in the index.
// Global Variables
const input = document.querySelector('.input');
const addBtn = document.querySelector('.add-btn');
const removeBtn = document.querySelector('.remove-btn');
const todos = document.querySelector('.todos');
// Event Listeners
addBtn.addEventListener('click', addTodo);
removeBtn.addEventListener('click', removeTodo);
const todoList = [
]
function addTodo() {
// Push user input to array
let inputValue = input.value;
todoList.push(inputValue);
displayTodo();
input.value = '';
console.log(todoList);
}
function removeTodo() {
let listItems = document.querySelectorAll('.todos li');
// Remove last todo from array
todoList.splice(-1, 1);
// Remove last todo from ul
todos.removeChild(listItems[listItems.length - 1]);
//console.log(todoList);
}
function displayTodo() {
// Create li and display it
let newTodo = document.createElement('li');
newTodo.textContent = input.value;
todos.appendChild(newTodo);
// Create edit button and display it
let editButton = document.createElement('button');
editButton.textContent = 'Edit';
newTodo.appendChild(editButton);
editButton.addEventListener('click', function() {
// Create edit input and save button
editButton.style.opacity = 0;
editButton.style.visibility = 'hidden';
let editInput = document.createElement('input');
newTodo.appendChild(editInput);
let saveButton = document.createElement('button');
newTodo.appendChild(saveButton);
saveButton.textContent = 'Save';
saveButton.addEventListener('click', function() {
newTodo.textContent = editInput.value;
console.log(todoList);
})
});
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" type="text/css" href="style.css">
<title>Todo List</title>
</head>
<body>
<div class="container">
<h1>Todo List</h1>
<input class="input" type="text" placeholder="Add A Task" autocomplete="off" required>
<button class="add-btn">Add Task</button>
<button class="remove-btn">Remove Task</button>
<ul class="todos">
</ul>
</div>
</body>
</html>
First of all, In editButton.addEventListener('click', function() {}, get the text content of the <li> element that has to be edited. The text content has the word 'Edit' appended to the list element and hence removing it in the second line. Get the index of the array element whole value is liValue using indexOf property.
let liValue = newTodo.textContent;
liValue = liValue.replace('Edit', '');
let liIndex = todoList.indexOf(liValue);
In saveButton.addEventListener('click', function () {}, after updating the DOM, use splice() to update the array list.
todoList.splice(liIndex, 1, editInput.value);
I have added the function in which the changes are done. The parts of the code that has to be added are commented down below.
editButton.addEventListener('click', function () {
// Create edit input and save button
editButton.style.opacity = 0;
editButton.style.visibility = 'hidden';
/* first part of the code starts here */
let liValue = newTodo.textContent;
liValue = liValue.replace('Edit', '');
let liIndex = todoList.indexOf(liValue);
/* first part of the code ends here */
let editInput = document.createElement('input');
newTodo.appendChild(editInput);
let saveButton = document.createElement('button');
newTodo.appendChild(saveButton);
saveButton.textContent = 'Save';
saveButton.addEventListener('click', function () {
newTodo.textContent = editInput.value;
/* second part of the code starts here */
todoList.splice(liIndex, 1, editInput.value);
/* second part of the code ends here */
})
});
Link to codepen: https://codepen.io/geekyquentin/pen/LYQdjXw
I'm a beginner when it comes to coding and the biggest issue I have is to understand WHY something doesn't work (how to diagnose an error). I tried to combine what I learned from Colt Steele on Udemy with fetch API and so far, I've managed to make it work to list the NAMES of the movies when you search, but when I try to display the IMAGES, they seem to not work and it seems like it's trying to load them from my PC rather than from the TVMaze API. Here's my code:
function searchShow(query) {
const url = `https://api.tvmaze.com/search/shows?q=${query}`;
fetch(url)
.then(response => response.json())
.then((jsonData) => {
const resultsNames = jsonData.map(element => element.show.name);
const resultsImages = jsonData.map(e => e.show.image);
console.log(resultsNames);
renderResults(resultsNames);
console.log(resultsImages);
renderImages(resultsImages);
document.getElementById("errorMessage").innerHTML = "";
})
.catch((error) => {
document.getElementById("errorMessage").innerHTML = error;
})
}
function renderResults(resultsNames) {
const list = document.getElementById("resultsList");
list.innerHTML = "";
resultsNames.forEach(result => {
const element = document.createElement("li");
element.innerText = result;
list.appendChild(element);
});
}
function renderImages(resultsImages) {
const list2 = document.getElementById("imagesDisplay");
list2.innerHTML = "";
resultsImages.forEach(result => {
const imgShow = document.createElement("IMG");
imgShow.src = result;
list2.appendChild(imgShow);
})
}
let searchTimeoutToken = 0;
window.onload = () => {
const searchFieldElement = document.getElementById("searchField")
searchFieldElement.onkeyup = (event) => {
clearTimeout(searchTimeoutToken);
searchTimeoutToken = setTimeout(() => {
searchShow(searchFieldElement.value);
}, 250);
};
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>TV Show</title>
</head>
<body>
<h1>TV Search</h1>
<input type="text" id="searchField" placeholder="Search a TV Show...">
<ul id="resultsList"></ul>
<ul id="imagesDisplay"></ul>
<div id=" errorMessage">
</div>
<script src="script.js"></script>
</body>
</html>
Can you please help me understand why is this not working and also, how can I make it display in a list like this:
-Name of the show
-Image of the show
-2nd name of the 2nd show
-2nd image of the 2nd show
etc.
Thank you in advance!
If you look at your images, you will see the src as [object Object] instead of the url to your image. You need to access the property of your object, in this case there's a few to choose from that represent different sized images.
I've modified your snippet to get what you want.
function searchShow(query) {
const url = `https://api.tvmaze.com/search/shows?q=${query}`;
fetch(url)
.then(response => response.json())
.then((jsonData) => {
let shows = jsonData.map(element => element.show);
renderShows(shows);
document.getElementById("errorMessage").innerHTML = "";
})
.catch((error) => {
document.getElementById("errorMessage").innerHTML = error;
})
}
function renderShows(shows) {
const list = document.getElementById("resultsList");
list.innerHTML = "";
shows.forEach(show => {
const element = document.createElement("li");
const img = document.createElement("img");
const text = document.createElement("span");
img.src = show.image.original;
text.innerText = show.name;
element.appendChild(text);
element.appendChild(img);
list.appendChild(element);
});
}
let searchTimeoutToken = 0;
window.onload = () => {
const searchFieldElement = document.getElementById("searchField")
searchFieldElement.onkeyup = (event) => {
clearTimeout(searchTimeoutToken);
searchTimeoutToken = setTimeout(() => {
searchShow(searchFieldElement.value);
}, 250);
};
}
img {
max-width: 100px;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>TV Show</title>
</head>
<body>
<h1>TV Search</h1>
<input type="text" id="searchField" placeholder="Search a TV Show...">
<ul id="resultsList"></ul>
<ul id="imagesDisplay"></ul>
<div id="errorMessage">
</div>
<script src="script.js"></script>
</body>
</html>
it was a long time ago that I didn’t program in javascript so I decided to make a project of a "bookcase" to manage read books and that I want to read more I have difficulty with how to separate the elements to personalize the style because it selects all the results of the api in one just div.
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="script.js"></script>
<link rel="stylesheet" href="./css/bookcase.css">
<title>project</title>
</head>
<body>
<div id="content">
</div>
<script src="https://www.googleapis.com/books/v1/volumes?q=clean+code&callback=handleResponse></script>
</body>
</html>
js
function handleResponse(response) {
for (var i = 0; i < response.items.length; i++) {
var item = response.items[i];
var book = document.getElementById('content')
book.innerHTML += "<br>" + '<img src=' + response.items[i].volumeInfo.imageLinks.thumbnail + '>';
book..innerHTML += "<br>" + item.volumeInfo.title;
book..innerHTML += "<br>" + item.volumeInfo.authors;
Clean answer - you should use document.appendChild(child) instead of innerHTML method.
Also, there are few recently added js methods that can help you operate large JSON objects - map, reduce, filter.
I added example, how you can clean original object to smaller array, and insert items from that array into html-page.
function demo (obj) {
// getting all items from object
const book = Object.keys(obj).map(item => obj['items']).reduce(
(acc,rec, id, array) => {
// getting Cover, Title, Author from each item
let singleBookCover = rec[id].volumeInfo.imageLinks.thumbnail;
let singleBookTitle = rec[id].volumeInfo.title;
let singleBookAuthor = rec[id].volumeInfo.authors[0];
// Creating new array only with Cover, Title, Author
return [...acc, {singleBookCover, singleBookTitle, singleBookAuthor}]
},
[]
).forEach( item => {
// For each item on our array, we creating h1
let title = document.createElement('h1');
title.textContent = `${item.singleBookTitle} by ${item.singleBookAuthor}`;
// img
let img = document.createElement('img');
img.src = item.singleBookCover;
img.alt = `${item.singleBookTitle} by ${item.singleBookAuthor}`;
// and div wrapper
let container = document.createElement('div');
// adding our child elements to wrapper
container.appendChild(title).appendChild(img);
// adding our wrapper to body
document.body.appendChild(container);
})
return book
}
Hope my answer will help you)
function handleResponse(obj) {
const container = document.getElementById("container")
obj.items.forEach((rec, index) => {
let singleBookCover =
rec.volumeInfo.imageLinks && rec.volumeInfo.imageLinks.smallThumbnail;
let singleBookTitle = rec.volumeInfo.title;
let singleBookAuthor = rec.volumeInfo.authors[0];
let book = document.createElement("div");
book.className = "book"
book.innerHTML = `
<div><h1>${singleBookTitle}<h1>
<p>${singleBookAuthor}</p>
<img src="${singleBookCover}"></img>
</div>
`
content.appendChild(book)
});
}
<div id="content" class="books">
</div>
<script src="https://www.googleapis.com/books/v1/volumes?q=clean+code&callback=handleResponse"></script>