Clean way to show data from an API grouped? - javascript

I am using an API which gives me 70-ish projects. From these projects I need to show the title, image and tagline on a website. I managed to get the data on the website, but right now it shows the data as follows:
Titles
Images
Taglines
Which means that right now I'm seeing 70 titles, then 70 images, then 70 taglines. I, of course, want this to be shown per project, so one title, one image, one tagline. This then should repeat for every project.
My code:
function fetchProjects(url) {
fetch(url)
.then(response => {
return response.json();
})
.then(data => {
appendData(data.data);
})
.catch(error => {
console.log(error);
});
}
function appendData(data) {
for (let i = 0; i < data.length; i++) {
let h3 = document.createElement('h3');
h3.innerHTML = data[i].project.title;
document.getElementById('project__header').appendChild(h3);
let img = new Image();
img.src = data[i].project.header_image;
document.getElementById('project__image').appendChild(img);
let p = document.createElement('p');
p.innerHTML = data[i].project.tagline;
document.getElementById('project__tagline').appendChild(p);
}
}
fetchProjects('api_url');
I tried reformatting my code but that didn't work. I also tried Googling this but I didn't get any results that could help me, so I probably didn't use the right keywords. My apologies.
Can anyone point me to a clean way I can achieve this in plain Javascript and HTML? Thanks!
EDIT: Of course I don't want to have to make 70 unique ID's and solving it like that, I know it's possible but it's definitely not the clean solution I'd presume

Inside your for loop, you're appending the items to the same element, so if you have:
<div id="project__header"></div>
<div id="project__image"></div>
<div id="project__tagline"></div>
after two cycles you'll have
<div id="project__header">
<h3>...</h3>
<h3>...</h3>
</div>
<div id="project__image">
<img ... />
<img ... />
</div>
<div id="project__tagline">
<p>...</p>
<p>...</p>
</div>
You should append them to the same element, or create a div and then append the entire div, something like:
const div = document.createElement("div");
const h3 = document.createElement('h3');
h3.innerHTML = data[i].project.title;
div.appendChild(h3);
const img = new Image();
img.src = data[i].project.header_image;
div.appendChild(img);
const p = document.createElement('p');
p.innerHTML = data[i].project.tagline;
div.appendChild(p);
document.getElementById("...").appendChild(div);

you append all the titles to the 'project__header' div, images to the 'project__image' div, and tag-lines to the 'project__tagline' div. If I do this, it could be
for (let i = 0; i < data.length; i++) {
// create a div container for each project
const dataDiv = document.createElement('div');
const projectTitle = document.createElement('h3');
projectTitle.innerHTML = data[i].project.title;
dataDiv.appendChild(projectTitle);
const img = new Image();
img.src = data[i].project.header_image;
dataDiv.appendChild(img);
const projectTag = document.createElement('p');
projectTag.innerHTML = data[i].project.tagline;
dataDiv.appendChild(projectTag);
// append the dataDiv to the parent container (use your own DOM element) of the projects
parentDiv.append(dataDiv);
}

Related

Render an element inside literal templates with a EventListener

I'm trying to insert an element with an EventListener at a certain point of an element. I can achieve this via appendChild but I want to insert it at a certain point. Like this:
const divT = () => {
const div = document.createElement("div");
const div_inside = document.createElement("div");
div_inside.addEventListener("click", () => {console.log("div_inside")});
div_inside.innerHTML = "INSIDE";
div.innerHTML = ` TEST ${div_inside.outerHTML} TEST `;
return div;
};
document.body.appendChild(divT());
The main problem is the outerHTML does not contain the information of the listener. There is a method to render the HTML with the listener still active?
Thanks in advance
Find a workaround using insertAdjacentElement and attaching an empty div:
const divT = () => {
const div = document.createElement("div");
const div_inside = document.createElement("div");
div_inside.innerHTML = "Hello World";
div_inside.addEventListener("click", () => {console.log("div_inside")});
div.innerHTML = ` TEST <div id="inside"></div> TEST `;
const div_s = div.getElementsByTagName('div')
div_s[0].insertAdjacentElement('beforebegin', div_inside)
return div;
};

Rendering Info Cards and modal box with forLoop only outs the last result in one of the fields

I am practising vainilla JS and DOM manipulation. I am rendering a group of cards populated with data from an Object (that I fetch from an API, but this happens also if i do it in local).
I get to render the cards,all good, and create a button in each of them, that triggers a Modal box made with Bootstrap, with some additional information about that item.
The problem I have is that when I click on the button, the modal box only displays the information from the last element of the Object.
The function i wrote is as follows :
function createCards(data) {
let cardContainer = document.getElementById('cardContainer');
let modalTitle = document.getElementById('modalTitle');
let modalBody= document.getElementById('modalBody');
for (let i = 0; i < data.length; i++) {
let divCard = document.createElement('div');
divCard.setAttribute('class', 'card');
let imgFish = document.createElement('img');
imgFish.setAttribute('class', 'card-img-top');
let imgUrl = data[i]['Species Illustration Photo'].src
// console.log(imgUrl)
imgFish.setAttribute('src', imgUrl);
let divCardBody = document.createElement('div');
divCardBody.setAttribute('class', 'card-body');
let hCardTitle = document.createElement('h5')
hCardTitle.innerHTML = data[i]['Species Name'];
let pCardText = document.createElement('p');
pCardText.setAttribute('class', 'card-text');
pCardText.innerHTML = 'Kcal = ' + data[i].Calories
let aInfoButton = document.createElement('a');
aInfoButton.setAttribute('class', 'btn btn-primary');
aInfoButton.setAttribute('href', '#');
aInfoButton.setAttribute('data-toggle', 'modal');
aInfoButton.setAttribute('data-target', '#myModal');
aInfoButton.innerHTML = 'Nutrional Info'
cardContainer.appendChild(divCard);
divCard.appendChild(imgFish);
divCard.appendChild(divCardBody);
divCardBody.appendChild(hCardTitle);
divCardBody.appendChild(pCardText);
divCardBody.appendChild(aInfoButton);
//fill in Modal box
aInfoButton.onclick = function () {
createModal(data);
}
}
console.log("createCards() run")
}
and then, the function to populate the modal box :
function createModal(data) {
let modalTitle = document.getElementById('modalTitle');
let modalBody= document.getElementById('modalBody');
for (let i = 0; i < data.length; i++) {
modalTitle.innerHTML = data[i]['Species Name'];
modalBody.innerHTML = data[i]['Cholesterol'];
}
}
I tried also wrapping the creation of those two innerHTML from the modal box inside a different function, thinking it was a problem of time needed to render each item, but it seems that the problem is different, and I can't see which one.
It looks like, at the bottom of your for-loop, you are overwriting the innerHTML every time.
modalTitle.innerHTML = data[i]['Species Name'];
modalBody.innerHTML = data[i]['Cholesterol'];
So when you exit the for loop, what ever iteration of data is last is the one that will be in the modal.
2 ways to go about fixing:
1.) create a modal specific to each card.
2.) add an eventListener to the button that will populate the correct info onclick

Appending <a> and <img> to document.body removes href and src

So, I'm creating an article block like this:
<article>
<figure>
<img src="source" />
</figure>
<div>
<a href="link"/>
</div>
</article>
This is my js code:
var linkElement = document.createElement('a');
var linkText = document.createTextNode(*some text*);
linkElement.appendChild(linkText);
linkElement.href = *link*;
var imgElement = document.createElement('img');
imgElement.setAttribute("src", *some path*);
imgElement.setAttribute("width", "304");
imgElement.setAttribute("height", "228");
imgElement.setAttribute("alt", temp["name"]);
var article = document.createElement("article"),
figure = document.createElement("figure"),
div = document.createElement("div");
div.appendChild(linkElement);
figure.appendChild(imgElement);
article.appendChild(figure);
article.appendChild(div);
document.querySelector('article').appendChild(article);
This is my html code:
<body>
<header></header>
<section>
<article></article>
</section>
<footer></footer>
<script src = *src of my js*></script>
</body>
If I create one article block, everything is fine. The problem arises when I create an array of article blocks. Every block except the last one loses its href and src for the 'a' and 'img'tags. An empty image box appears and the text without the link appears. Can anyone explain why it happens and how I can change that?
Output
I printing a list of FB movie pages with their picture and links to the page.
My original code:
// data has a list of FB movie pages, each containing the name of t he movie and page id
function print(data)
{
//iterates through the object passed to print movie names in console.
var target = document.querySelector('article');
var docFrag = document.createDocumentFragment();
for (var i = 0; i < data.length; i++)
{
var temp = data[i];
var linkElement = document.createElement('a');
var linkText = document.createTextNode(temp["name"]);
linkElement.appendChild(linkText);
//getting the link to the movie's FB page
getLink(function(response){linkElement.href = response;},temp["id"]);
var imgElement = document.createElement('img');
//getting the src of the picture of the movie's page
getPic(function(response){imgElement.setAttribute("src", response);},temp["id"]);
imgElement.setAttribute("width", "304");
imgElement.setAttribute("height", "228");
imgElement.setAttribute("alt", temp["name"]);
var article = document.createElement("article"),
figure = document.createElement("figure"),
div = document.createElement("div");
div.appendChild(linkElement);
figure.appendChild(imgElement);
article.appendChild(figure);
article.appendChild(div);
console.log(article);
docFrag.appendChild(article);
}
target.appendChild(docFrag);
}
function getLink(callback,id)
{
FB.api('/'+id+'?fields=link', function(response)
{
callback(response.link);
});
}
function getPic(callback,id)
{
FB.api('/'+id+'?fields=cover{source}', function(response)
{
callback(response.cover.source);
});
}
If just going by the OP code, it never appends anything to the DOM. The <article> is created and everything is appended to it but it never finds its way to the DOM.
Once that is taken care of you must be mindful of the urls assigned to your images. They will fail should you mix secured content with unsecured content.
Secured Content:
<img src='https://....>
Unsecured Content:
<img src='http://....>?
BTW, this Demo is reusable and easily expandable. The simple requirement is that for each <article> you must add an appropriate url to the url[] and posters[] arrays. Also the layout is streamlined: <img> is nested with <a> and <a> is nested within <fig>, and <fig> is nested within <article>. This arrangement makes the entire <img> a link.
Demo
Details commented in Demo and references are located below the Demo
// Array of urls to sites for lnk.href
var urls = ['https://www.starwars.com', 'https://www.marvel.com'];
// Array of urls to images for img.src
var posters = ["https://imgc.allpostersimages.com/img/print/u-g-F93H7K0.jpg?w=900&h=900&p=0", "https://imgc.allpostersimages.com/img/print/u-g-F90CQI0.jpg?w=900&h=900&p=0"];
// Reference the DOM target
var target = document.querySelector('.action');
/* Create a DocumentFragment Object to add all of your articles
|| to. The only thing you should ever append to the DOM is the
|| docFrag. It's costly to add to DOM so do it only once.
*/
var docFrag = document.createDocumentFragment();
/* One loop will:
|| - create the 4 components: article, figure, a, img
|| - get the urls from the 2 arrays and assign them to
|| lnk.href and img.src
|| - set the additional attributes to img
|| - append components: img to a--a to fig--fig to art--art to
|| docFrag--docFrag to target(a section attached to the DOM)
*/
/* This `for` loop will go 2 times because the .length of urls[]
|| array is 2 (note: i < urls.length)
*/
for (let i = 0; i < urls.length; i++) {
var art = document.createElement('article');
var fig = document.createElement('figure');
var lnk = document.createElement('a');
lnk.href = urls[i];
var img = document.createElement('img');
img.src = posters[i];
img.width = '200';
img.height = '300';
lnk.appendChild(img);
fig.appendChild(lnk);
art.appendChild(fig);
docFrag.appendChild(art);
}
target.appendChild(docFrag);
<!doctype html>
<html>
<head>
<meta charset='utf-8'>
</head>
<body>
<header></header>
<main>
<section class="action"></section>
</main>
<footer></footer>
</body>
</html>
Reference
What is the DOM?
DocumentFragment

Generate html using Javascript

I have a gallery page that is updated often with new images. I use simple HTML to post the photos. My process currently is copy and paste the set of tags for a photo and change the number to correspond with the image file name. E.G. I change the number 047 to 048. Copy-Paste, change it to 049. This goes on until I have reached the number of additional photos. As you can see, this is very inefficient and there must be a better way of doing this. I was wondering if there is a simpler way to achieve this with Javascript? Perhaps generate additional tags by inputing a certain number or range?
Any ideas that would make this process efficient are welcomed please! Thank you!
<div class="cbp-item trim">
<a href="../assets/images/trim/img-trim-047.jpg" class="cbp-caption cbp-lightbox" data-title="">
<div class="cbp-caption-defaultWrap">
<img src="../assets/images/trim/img-trim-047.jpg" alt="">
</div>
</a>
</div>
You could use a templating solution. There are several libraries for that, but you can also implement it yourself.
Here is one way to do that:
Put the HTML for one image in a script tag that has a non-standard language property so the browser will just ignore it
Put some keywords in there that you'll want to replace, e.g. {url}. You can invent your own syntax.
Read that template into a variable
In the JS code, put all the images' URLs in an array of strings
For each element in that array, replace the keywords in the template string with that particular URL, and concatenate all these resulting HTML snippets.
Inject the resulting HTML into the appropriate place in the document.
Here is a snippet doing that:
// Add new images here:
var images = [
"https://upload.wikimedia.org/wikipedia/commons/thumb/e/e0/SNice.svg/330px-SNice.svg.png",
"https://nettemarie357.files.wordpress.com/2014/09/smiley-face.jpg?w=74&h=74",
];
// Load the template HTML
var template = document.querySelector('script[language="text/template"]').innerHTML;
// Use template to insert all the images:
container.innerHTML = images.map(url => template.replace(/{url}/g, url)).join('');
img { max-width: 50px }
<div id="container"></div>
<script language="text/template">
<div class="cbp-item trim">
<a href="{url}" class="cbp-caption cbp-lightbox" data-title="">
<div class="cbp-caption-defaultWrap">
<img src="{url}" alt="">
</div>
</a>
</div>
</script>
This would help you creating it programatically:
var new_row = document.createElement('div');
new_row.className = "cbp-item trim";
var a = document.createElement('a');
a.href = "../assets/images/trim/img-trim-047.jpg";
a.className= "cbp-caption cbp-lightbox";
document.body.appendChild(a);
var div = document.createElement('div');
div.className = "cbp-caption-defaultWrap";
var img = document.createElement('img');
img.src= "../assets/images/trim/img-trim-047.jpg";
div.appendChild(img);
a.appendChild(div);
new_row.appendChild(a);
If it is just about printing HTML, I suggest you to use plugins like Emmet for Sublime Text editor.
When you install this plugin and see how it works, you can simple create a complex html in a way that 'for' loop would do this. This will help you to change only the image/link number of every item.
Check the demo in the link, that I added.
Here's an example in Java Script that will generate the html you will need. Set the total to whatever number you need to generate the number of images you want.
var total = 47;
var hook = document.getElementById('hook');
// Main Node for SlideShow
var node = document.createElement('div');
node.classList = "cbp-item trim";
// Work out the correct number
var n = function(int) {
var length = int.toString().length;
return length === 1
? '00' + int
: length === 2
? '0' + int
: length
}
// Create the item
var createItem = function(int){
// Create Anchor
var a = document.createElement('a');
a.href = '../assets/images/trim/img-trim-' + ( n(int) ) + '.jpg" class="cbp-caption cbp-lightbox';
a.classList = 'cbp-caption cbp-lightbox';
// Create Div
var div = document.createElement('div');
div.classList = 'cbp-caption-defaultWrap';
// Create Image
var img = document.createElement('img');
img.src = '../assets/images/trim/img-trim-' + ( n(int) ) + '.jpg';
img.alt = 'gallery image';
// Finalise Dom Node
var container = div.appendChild(img)
a.appendChild(div);
// Return Final Item
return a
}
// Create Items
for (var i = 1; i < total + 1; i++) {
node.appendChild(createItem(i));
}
// Append Main Node to Hook
hook.appendChild(node);
<div id="hook"></div>

Add/Delete Button

I'm working on this project, and what it is supposed to do is show a series of "business cards" that contain the name of someone and their image. When you hover over the card an x button appears and clicking it will delete the card from the page. There are also to inputs one for the url of an image and another for the name of the person, under these 2 inputs are a submit button, and when it's submitted it's supposed to create the card.
Right now I have the code to create the card, but it does not appear to be working.
document.getElementById('btn').onclick = function() {
var addPhoto = document.getElementById('imagename').value,
src = addPhoto +'.png',
img = document.createElement('img');
img.src = src;
var addName =document.getElementById('actorname').value;
var card = document.createElement('div');
card.setAttribute('class','card');
var title = document.createElement('h1');
container.innerHTML = addName;
var cardImage = document.createElement('div');
cardImage.setAttribute('class','card-image');
var img = document.createElement('img');
image.setAttribute('src',addPhoto);
var button = document.createElement('input');
button.setAttribute('class','buttons');
button.setAttribute('type','image');
button.setAttribute('src','img/close.png');
card.appendChild(title);
card.appendChild(cardImage);
cardImage.appendChild(image);
cardImage.appendChild(button);
document.getElementById("lefty").appendChild(card);
};
To add a new card, you should set everything up from the ground. You should create the following in Javascript before adding it to the page:
<div class="card">
<h1>the_name</h1>
<div class="card-image">
<img src="the_link"/>
<input class="buttons" type="image" src = "img/close.png"/>
</div>
</div>
You already know how to get *'the_name'* and *'the_link'* (the variables for your cards). I'm gonna use those variables in my solution. First you have to create all the elements.
var card = document.createElement('div');
card.setAttribute('class','card');
var title = document.createElement('h1');
container.innerHTML = the_name;
var cardImage = document.createElement('div');
cardImage.setAttribute('class','card-image');
var image = document.createElement('img');
image.setAttribute('src',the_link);
var button = document.createElement('input');
button.setAttribute('class','buttons');
button.setAttribute('type','image');
button.setAttribute('src','img/close.png');
Now you want to paste all the elements to the webpage. First append the elements to each other, then append them to the place you want to get them to (in your case the body).
card.appendChild(title);
card.appendChild(cardImage);
cardImage.appendChild(image);
cardImage.appendChild(button);
document.body.appendChild(card);
You can delete an element in an odd way using javascript. First you have to get the domNode of the element you want to get rid of. For example one of the two nodes:
var unwanted = document.getElementsByTagName('a')[0];
var unwanted = document.getElementById('id_of_unwanted_item');
then you have to execute the following function:
unwanted.parentNode.removeChild(unwanted)
You have the basic idea, you just need to take it a few steps farther. For the delete you'll want to add an ID to each card and then call a JS function with that ID:
<div class="card" id="william">
<h1>William Finley (Phantom of the Paradise)</h1>
<div class="card-image">
<img src="img/avatars/william.png"/>
<input class="buttons" type="image" src = "img/close.png" onclick="removeCard('william')"/>
</div>
</div>
<script type="text/javascript">
var removeCard = function(cardId) {
var cardElement = document.getElementById(cardId);
cardElement.parentNode.removeChild(cardElement);
}
</script>
When you create a card (which Nicky explained well) make sure you add the ID and onclick event to your element generation.

Categories

Resources