I have created a <template> tag and i have given it has a few children inside
I'm fetching a json file that has a few objects inside and running a forEach loop after i get the data in order to change the <h1> to be equal and display all the names that are inside the json file. Im also cloning the template and then appending the cloned template to a main tag.
The problem starts when i try to also create a button and append it inside the <div class="content"> that is located inside the template. I tried to get the <div class="content"> inside the loop and later create and append a button to the div i specified above (class = 'content') but whenever i try append it throws this error Uncaught (in promise) TypeError: Cannot read property 'appendChild' of null this is the full code inside the loop.
if I try to change this line contentdiv.appendChild(btn); to this this clone.appendChild(btn); then it works but the button is not inside the <div class="content"> which i want it to be.
Anyone could help me understand what I am doing wrong here ?
const data = [{
fullname: "George Clooney"
}, {
fullname: "Fred Astaire"
}]
data.forEach(actor => {
//clone the template
const clone = template.cloneNode(true);
//get actor name and change the content to be = to the full name inside the json obj
const actorName = clone.querySelector('.actor-name').textContent = actor.fullname;
const contentdiv = document.querySelector('.content');
//create button
const btn = document.createElement('button');
btn.appendChild(document.createTextNode('Read More'));
btn.classList = 'btn'
contentdiv.appendChild(btn);
//apend the the clone to the main tag
main.appendChild(clone);
});
<template id='template'>
<div class="content">
<h1 class="actor-name">
</h1>
</div>
</template>
You need to clone the content
const data = [{
fullname: "George Clooney"
}, {
fullname: "Fred Astaire"
}]
const main = document.getElementById("main");
data.forEach(actor => {
//clone the template
const clone = document.getElementById("template").content.cloneNode(true);
clone.querySelector('.actor-name').textContent = actor.fullname;
const contentdiv = clone.querySelector('.content');
const btn = document.createElement('button');
btn.appendChild(document.createTextNode('Read More'));
btn.classList = 'btn'
contentdiv.appendChild(btn);
main.appendChild(clone);
});
<template id='template'>
<div class="content">
<h1 class="actor-name"></h1>
</div>
</template>
<div id="main"></div>
Related
i'm trying create a counter function on object compilated with handlebars.js. I'm trying to make a project site for a moving company, and I'm trying to create a cost estimation system in it, depending on what things and how many they have to move in their new house
handlebars compilated divs
problem
So I created objects in js which contain things to move such as fridge, bed, table etc... their are compilated right into HTML with handlebars template.
I want to be able to increase and decrease the numbers of things separately, so I was able to create one function that does this, but the problem is that it works only on the first compilated object and all other objects are not affected with this code, i don't even know if it is possible to do such thing with handlebars template and vanilla js, I know I can do this for each objects individually but there will be way too much duplicated codes in HTML and JS files..
here is the handlebars template on HTML file:
<script src="https://cdn.jsdelivr.net/npm/handlebars#latest/dist/handlebars.js"></script>
<script id="templateHB" type="text/x-handlebars-template">
{{#each lesMeublesSalon}}
<div class="meuble"><img src="{{this.image}}">
<p>{{this.element}}</p>
<div class="plus-moin">
<button id="increase-{{this.index}}">+</button>
<p>{{this.quantity}}</p>
<button id="decrease-{{this.index}}">-</button>
</div>
</div>
{{/each}}
here is the js file code:
const source = document.getElementById('templateHB').innerHTML;
const template = Handlebars.compile(source);
const contextSalon = {
lesMeublesSalon: [
{
image: 'images/canape.png',
element: 'Canapé',
quantity: 0,
index: 0
},
{
image: 'images/canape.png',
element: 'Lit',
quantity: 0,
index: 0
}
]
};
let compiledHtmlSalon = template(contextSalon);
const injectionObjetSalon = document.getElementById('meuble-salon');
injectionObjetSalon.innerHTML = compiledHtmlSalon;
// here start the function
let quantity = contextSalon.lesMeublesSalon[0].quantity;
let addOneBtn = document.getElementById("increase-0");
let removeOneBtn = document.getElementById("decrease-0");
function updateQuantity(quantity) {
contextSalon.lesMeublesSalon[0].quantity = quantity;
compiledHtmlSalon = template(contextSalon);
injectionObjetSalon.innerHTML = compiledHtmlSalon;
addOneBtn = document.getElementById("increase-0");
removeOneBtn = document.getElementById("decrease-0");
addOneBtn.addEventListener("click", function() {
updateQuantity(quantity + 1);
});
removeOneBtn.addEventListener("click", function() {
updateQuantity(quantity - 1);
});
}
updateQuantity(0);
if the thing i'm trying to do is impossible with js and handlebars.js, what other tech can you suggest me? any js framework such as node.js and express.js?
I just created function to increase and decrease number based on ID of the template inside html file with handlebars, I was expecting it to work with all others compilated objects to work the same way.
It is possible that a rendering framework like React or Vue could be used for this, but they come with learning curves. The conditions as you have outlined them are fairly simple and so I think you can get by with Handlebars.
But we will need to make some modifications to your code.
The first issue I see is that you have index: 0 set on all objects in your lesMeublesSalon array. You could correct those to be sequential (0, 1...), but I think a better option would be to use Handlebars' built-in #index variable to set the index value for each item.
The next problem I would address is that you are trying to re-render the Handlebars template with each click of the increment/decrement button. The problem I see with this is that it is that you will have to re-attach your event listeners each time you re-render the HTML because the DOM will have new button elements that need to be listened to.
I think a better approach would be to render the initial HTML with Handlebars, but then to use JavaScript to directly update the DOM when your counts change.
Here is how I would implement this:
{{#each lesMeublesSalon}}
<div class="meuble"><img src="{{this.image}}">
<p>{{this.element}}</p>
<div class="plus-moin">
<button data-increase="{{#index}}">+</button>
<p data-quantity="{{#index}}">{{this.quantity}}</p>
<button data-decrease="{{#index}}">-</button>
</div>
</div>
{{/each}}
const source = document.getElementById('templateHB').innerHTML;
const template = Handlebars.compile(source);
const contextSalon = {
lesMeublesSalon: [
{
image: 'images/canape.png',
element: 'Canapé',
quantity: 0,
index: 0
},
{
image: 'images/canape.png',
element: 'Lit',
quantity: 0,
index: 0
}
]
};
// Initially render our HTML with Handlebars.
const compiledHtmlSalon = template(contextSalon);
const injectionObjetSalon = document.getElementById('meuble-salon');
injectionObjetSalon.innerHTML = compiledHtmlSalon;
// Note the use of data-* attributes in the template.
// This allows us to query for ALL increment/decrement buttons
// and to attach a listener to each.
// Getting them by ID allowed us to get only one increment
// and one decrement button.
const addOneBtns = document.querySelectorAll("[data-increase]");
const removeOneBtns = document.querySelectorAll("[data-decrease]");
const quantityDisplays = document.querySelectorAll("[data-quantity]");
// This is the function that will directly manipulate the displayed
// quantity value for each item.
// It relies on the indexes of the elements to match the indexes in
// our lesMeublesSalon so that the correct quantity will be set.
function renderQuantities () {
quantityDisplays.forEach((quantityDisplay, index) => {
const quantity = contextSalon.lesMeublesSalon[index].quantity;
quantityDisplay.textContent = String(quantity);
})
};
// We loop through EACH increment button and attach a click listener.
addOneBtns.forEach(addOneBtn => {
addOneBtn.addEventListener('click', function (event) {
// We get the index from the `[data-increase]` attribute.
const index = Number(event.target.dataset.increase);
// We use that index to increment the quantity on the
// corresponding item in our array.
contextSalon.lesMeublesSalon[index].quantity += 1;
// We re-render the quantities because we know there is a change.
renderQuantities();
});
});
// This is basically the same as above, except for decrementing.
removeOneBtns.forEach(removeOneBtn => {
removeOneBtn.addEventListener('click', function (event) {
const index = Number(event.target.dataset.decrease);
// We use Math.max() so the quantity can't become less than zero.
contextSalon.lesMeublesSalon[index].quantity = Math.max(contextSalon.lesMeublesSalon[index].quantity - 1, 0);
renderQuantities();
});
});
I have created a Codepen for reference.
I've tesed your code and it works well , thanks you! however I forgot to mention that I have 4 categories of objects , as you can see on the picture , I got (Salon, Chambre, Cuisine, Bain) , how can we make so that it works on all of these categories ?
So it works on the "Salon" categories but not in the others,
here you have all others object :
const contextSalon = {
lesMeublesSalon: [
{
image: 'images/canape.png',
element: 'Canapé',
quantity: 0,
index: 0
},
{
image: 'images/canape.png',
element: 'Lit',
quantity: 0,
index: 0
}
]
};
// Initially render our HTML with Handlebars.
const compiledHtmlSalon = template(contextSalon);
const injectionObjetSalon = document.getElementById('meuble-salon');
injectionObjetSalon.innerHTML = compiledHtmlSalon;
const contextChambre = {
lesMeublesChambre: [
{
image: 'images/bed.svg.png',
element: 'Lit double',
quantity: 0,
index: 0
}
]
}
const compiledHtmlChambre = template(contextChambre);
const injectionObjetChambre = document.getElementById('meuble-chambre');
injectionObjetChambre.innerHTML = compiledHtmlChambre;
const contextCuisine = {
lesMeublesCuisine: [
{
image: 'images/frigo.svg',
element: 'Frigo',
quantity: 0,
index: 0
}
]
}
const compiledHtmlCuisine = template(contextCuisine);
const injectionObjetCuisine = document.getElementById('meuble-cuisine');
injectionObjetCuisine.innerHTML = compiledHtmlCuisine;
const contextBain = {
lesMeublesBain: [
{
image: 'images/machine-a-laver.svg',
element: 'Machine à laver',
quantity: 0,
index: 0
}
]
}
const compiledHtmlBain = template(contextBain);
const injectionObjetBain = document.getElementById('meuble-bain');
injectionObjetBain.innerHTML = compiledHtmlBain;
{{#each lesMeublesSalon}}
<div class="meuble"><img src="{{this.image}}">
<p>{{this.element}}</p>
<div class="plus-moin">
<button data-increase="{{#index}}">+</button>
<p data-quantity="{{#index}}">{{this.quantity}}</p>
<button data-decrease="{{#index}}">-</button>
</div>
</div>
{{/each}}
{{#each lesMeublesChambre}}
<div class="meuble"><img src="{{this.image}}">
<p>{{this.element}}</p>
<div class="plus-moin">
<button>+</button>
<p>{{this.quantity}}</p>
<button>-</button>
</div>
</div>
{{/each}}
{{#each lesMeublesCuisine}}
<div class="meuble"><img src="{{this.image}}">
<p>{{this.element}}</p>
<div class="plus-moin">
<button>+</button>
<p>{{this.quantity}}</p>
<button>-</button>
</div>
</div>
{{/each}}
{{#each lesMeublesBain}}
<div class="meuble"><img src="{{this.image}}">
<p>{{this.element}}</p>
<div class="plus-moin">
<button>+</button>
<p>{{this.quantity}}</p>
<button>-</button>
</div>
</div>
{{/each}}
What I thought would be the easiest part of my project has turned into a Herculean effort. All I wanted to do was get data from a JSON file to then display on my website. Prior to using a JSON file, I hard coded some data to test my filter/search functionality, all of which I wrote in JavaScript. The code worked perfectly, so I decided to move the data to a JSON file as I am expecting to have a lot more data in the future and can't have it hardcoded. However, I have been unable to get data from the JSON file successfully. I tried using require('./data.json'), but apparently I can't just use require like that. I then tried importing the file, which only works if I go back to the html and add type="module" to the src tag. This then allows all of the data to display on the webpage, however, the function that allows me to filter by category no longer works. When I click on the buttons, I get no response. I used Inspect to get the console to find the error, and the output is:
Uncaught ReferenceError: filterProject is not defined
The search functionality still works, and I suspect this is because that code isn't inside a function. Thus, I don't know why filterProject is supposedly not defined when the other JS code works. Here is all of my code:
import projects from './data.json' assert { type: "json" };
const path = "http://localhost/static/images/";
//ADDING THE HTML, IGNORE
for(let i of projects){
let card = document.createElement("div");
card.classList.add("card", i["category"], "hide");
let imgContainer = document.createElement("div");
imgContainer.classList.add("image-container");
let imageOne = document.createElement("img");
imageOne.setAttribute("src", path.concat(i["imageOne"]));
imgContainer.appendChild(imageOne);
card.appendChild(imgContainer);
let container = document.createElement("div");
container.classList.add("container");
let name = document.createElement("h3");
name.classList.add("project-name");
name.innerText = i["projectName"].toUpperCase();
container.appendChild(name);
let student = document.createElement("h4");
student.classList.add("student-name");
student.innerText = i["studentName"].toUpperCase() + " mentored by " + i["mentor"].toUpperCase();
container.appendChild(student);
let category = document.createElement("h6");
category.innerText = i["category"].toUpperCase().replace("_", " ");
container.appendChild(category);
card.appendChild(container);
document.getElementById("projects").appendChild(card);
}
//FILTERING (DOESNT WORK)
function filterProject(value){
let buttons = document.querySelectorAll(".button-value");
buttons.forEach((button) => {
if(value.toUpperCase() == button.innerText.toUpperCase()){
button.classList.add("active");
}else{
button.classList.remove("active");
}
});
let elements = document.querySelectorAll(".card");
elements.forEach((element) => {
if(value == "all"){
element.classList.remove("hide");
}
else{
//having a space messes it up, make it _
if(element.classList.contains(value.replace(" ", "_"))){
element.classList.remove("hide");
}
else{
element.classList.add("hide");
}
}
});
}
//SEARCH (WORKS)
document.getElementById("search").addEventListener
("click", () => {
let searchInput = document.getElementById("search-input").value;
let elements = document.querySelectorAll(".student-name");
let cards = document.querySelectorAll(".card");
elements.forEach((element, index) =>{
if(element.innerText.includes(searchInput.toUpperCase())){
cards[index].classList.remove("hide");
}
else{
cards[index].classList.add("hide");
}
});
});
//INTIAL STATE
window.onload = () =>{
filterProject("all");
};
Here is the HTML just in case as well:
<div class ="wrapper">
<div id="search-container">
<input type="search" id="search-input" placeholder="Search student name here..."/>
<button id = "search">Search</button>
</div>
<div id ="buttons">
<button class = "button-value" onclick="filterProject('all')">All</button>
<button class = "button-value" onclick="filterProject('Creative Project')">Creative Project</button>
<button class = "button-value" onclick="filterProject('Developing Voice')">Developing Voice</button>
<button class = "button-value" onclick="filterProject('Interdisciplinary Fusion')">Interdisciplinary Fusion</button>
<button class = "button-value" onclick="filterProject('Personal Writing')">Personal Writing</button>
<button class = "button-value" onclick="filterProject('Curriculum Designer')">Curriculum Designer</button>
<button class = "button-value" onclick="filterProject('Internship')">Internship</button>
</div>
<div id = projects></div>
</div>
<script type = "module" src = "{{ url_for('static',filename='javascript/script.js') }}"></script>
If it matters, I am using Flask as my web framework. I'm not sure if that has any impact on anything, but it has created some obstacles when I've tried to create a live server to solve this issue. Thanks in advance for any replies!
What you're looking for is how to load json files locally.
One solution is
Start a local server e.g. http://localhost:8080
Then use fetch() to retrieve the json file
For example, if your data.json file was located within the same folder where you have your html file and where you started your server, then your code could be something like
fetch("http://localhost:8080/data.json")
.then((response) => {
return response.json();
})
.then((data) => {
// Add code to process your data
})
I am trying to display two buttons with .innerHTML but it doesn't display the content put in the string.
The console is not showing any error and I've checked for typos but I haven't found anything.
Here's my HTML :
<div class="buttons">
<div class="buttons_inner" id="buttonShiny"></div>
</div>
And here's my JS :
const shiny = document.getElementById('buttonShiny');
const displayButtonShiny = (pokemon) => {
const shinyHTMLString = `
<button class="buttons_shiny" onclick="document.getElementById('pokemonSprite').src='https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/shiny/${pokemon.id}.png'">Shiny</button>
<button class="buttons_normal" onclick="document.getElementById('pokemonSprite').src='https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/${pokemon.id}.png'">Normal</button>
`;
shiny.innerHTML = shinyHTMLString;
};
The content in the shinyHTMLString should be displayed in the #buttonShiny div but it doesn't work
The script tag to call the JS file is right before the closing body tag
Yes you declare the function but never use it. Add only displayButtonShiny("x") to your js code.
const shiny = document.getElementById('buttonShiny');
const displayButtonShiny = (pokemon) => {
const shinyHTMLString = `
<button class="buttons_shiny" onclick="document.getElementById('pokemonSprite').src='https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/shiny/${pokemon.id}.png'">Shiny</button>
<button class="buttons_normal" onclick="document.getElementById('pokemonSprite').src='https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/${pokemon.id}.png'">Normal</button>
`;
shiny.innerHTML = shinyHTMLString;
};
displayButtonShiny("x"); // x = your pokemon
<div class="buttons">
<div class="buttons_inner" id="buttonShiny">btn1</div>
</div>
I'd appreciate some help with iteratively generating the beneath div based on the amount of items in a Javascript dictionary.
<div class="container" style="padding-bottom: 10px;">
<div class="dropdown" style="padding: 10px;">
<a href="#">TOP 3 PS5 HEADSETS<i class="fa fa-chevron-down"></i>
</a>
<ul>
<div id="links">
<center>
<p>3: ↓ INSERT TITLE FOR STEELSERIES ↓</p>
</center>
<div class="product">
<img src="img/products/h-steelseries.png">
<a class="link" href="INSERT LINK HERE">Read More</a>
</div>
<center>
<p>3: ↓ INSERT TITLE FOR OTHER↓</p>
</center>
<div class="product">
<img src="img/products/h-other.png">
<a class="link" href="INSERT LINK HERE">Read More</a>
</div>
</div>
</ul>
</div>
</div>
Beneath is the read.js file that contains the items in which I wish to generate the div class "product" for.
I'd really apprecaite any help with this.
var prod_obj = {
"headphone_products" : {
"title": "Steelseries",
"IMAGE": "h-steelseries.png",
"HREF" : "steelseries.html"
},
"other_products" : {
"title": "Other product",
"IMAGE": "h-other.png",
"HREF" : "other.html"
}
};
I have looked at other answers and couldn't find an example of a dictionary of object that was used to automatically generate divs. I intend on using this to list items on a website and would like to append objects to the dictionary and them to automatically generate a new div for each object once the script is executed.
Thank you for your time.
You can simply loop over the object and create the desired nodes inside the loop.
Here's a simpler version of the same.
var prod_obj = {
"headphone_products": {
"title": "Steelseries",
},
"other_products": {
"title": "Other product",
}
};
for (let keys in prod_obj) {
const div = document.createElement("div");
div.innerText = prod_obj[keys].title
document.body.appendChild(div)
}
You can use for-in loops and template literals to achieve what you want to achieve here.
const prod_obj = {
"headphone_products": {
"title": "Steelseries",
"image": "h-steelseries.png",
"href": "steelseries.html"
},
"other_products": {
"title": "Other product",
"image": "h-other.png",
"href": "other.html"
}
};
const div = document.getElementById('insertHere');
for (let products_key in prod_obj) {
let {title, image, href} = prod_obj[products_key];
let html = `<p>Title: ${title}, Image: ${image}, href: ${href}</p>`;
div.insertAdjacentHTML('beforeend', html);
}
<div id="insertHere">
</div>
What you describe sounds like a suitable candidate for a template which, according to the documentation on MDN says:
The HTML Content Template () element is a mechanism for
holding HTML that is not to be rendered immediately when a page is
loaded but may be instantiated subsequently during runtime using
JavaScript.
The following uses a simple class to load a new instance of the designated template for each product found within the source data ( what you refer to as a dictionary ). Once the template has been loaded from the shadows you can manipulate the contents as you wish. If you change the design of the template you change the design of the final layout. In the original HTML there is no span element surrounding the individual products but the way I wrote the template loader( for a specific job ) clones the first child element entirely - so a span will not affect layout unless styled specifically to do so.
class TemplateLoader{
constructor( id ){
this.id=id;
return this.create();
};
gettemplate(){
return document.querySelector( 'template[ data-id="'+this.id+'" ]' ) || false
};
clone(){
let tmpl=this.gettemplate();
return tmpl ? tmpl.content.firstElementChild.cloneNode( true ) : false;
};
gettarget(){
return document.querySelector( 'div[ id="'+this.id+'" ]' ) || false;
};
create(){
let tmpl=this.clone();
if( tmpl ){
let target=this.gettarget();
target.appendChild( tmpl );
return tmpl;
}
return false;
};
};
var prod_obj = {
'headphone_products' : {
'title': 'steelseries',
'image': 'h-steelseries.png',
'href' : 'steelseries.html'
},
'other_products' : {
'title': 'other product',
'image': 'h-other.png',
'href' : 'other.html'
},
'banana':{
'title':'curvy & yellow',
'image':'b-a-nana.png',
'href':'banana.html'
}
};
let id='links';
Object.keys( prod_obj ).forEach( cat => {
let data=prod_obj[ cat ];
let oTmpl=new TemplateLoader( id );
oTmpl.querySelector('center > p').textContent=data.title;
oTmpl.querySelector('div.product > img').src=['img/products',data.image].join('/');
oTmpl.querySelector('div.product > a.link').href=data.href;
});
<!-- regular HTML -->
<div class='container'>
<div class='dropdown'>
<a href='#'>TOP 3 PS5 HEADSETS<i class='fa fa-chevron-down'></i></a>
<ul>
<div id='links'>
<!-- items will be populated here -->
</div>
</ul>
</div>
</div>
<!-- our template that will be used to generate new content within the above, regular HTML' -->
<template data-id='links'>
<span>
<center>
<p></p>
</center>
<div class='product'>
<img />
<a class='link'>Read More</a>
</div>
</span>
</template>
The traditional way to add content to the DOM on-the-fly is to use a series of calls to createElmenent and appendChild (which is less error-prone than just inserting HTML strings). And you can loop through your data object's keys and extract the details you need to configure your new DOM elements. This script does both of these things in a function called updateDOM, which invokes the appendProductDetails function once per product.
I changed the hrefs to create functional (if arbitrary) links, and of course the images don't show up because they don't exist on StackOverflow's server. See the in-code comments for further explanation.
const currentProds = getProductsToShow();
updateDOM(currentProds);
function updateDOM(prod_obj) {
// Identifies parent div
const linksDiv = document.getElementById("links");
// Clears parent div
linksDiv.innerHTML = "";
// Loops through productName (keys) in prod_obj
const productNames = Object.keys(prod_obj);
for (let productName of productNames) {
// Gets details (inner object) for each product
const details_obj = prod_obj[productName];
// Creates, configures, and appends new elements for each product
appendProductDetails(linksDiv, details_obj);
}
}
function appendProductDetails(parentElement, detailsObject) {
const
// Gets local copies of values via "destructuring"
{ title, image, href } = detailsObject,
path = "img/products/", // Defines path to images
// Creates elements to add to the DOM
productDiv = document.createElement("div"),
titleP = document.createElement("p"),
img = document.createElement("img"),
anchor = document.createElement("a");
// Configures newly created elements
productDiv.classList.add("product");
titleP.textContent = title;
img.src = path + image;
img.alt = image;
anchor.classList.add("link");
anchor.href = href;
anchor.textContent = "Read More";
// Puts children into productDiv
productDiv.appendChild(titleP);
productDiv.appendChild(img);
productDiv.appendChild(anchor);
// Attaches everything to the DOM
parentElement.appendChild(productDiv);
}
// Provides demo data
function getProductsToShow() {
const productsObj = {
"headphone_products": {
"title": "Steelseries",
"image": "h-steelseries.png", // In img/products/
"href": "https://stackoverflow.com"
},
"other_products": {
"title": "Other product",
"image": "h-other.png",
"href": "https://eloquentjavascript.net/"
}
};
return productsObj;
}
.container{ width: 250px; text-align: center; }
.dropdown > a{ text-decoration: none; }
p{ margin: -0.1rem 0; font-size: 1.2rem; }
.product{ padding: 0.5rem ; }
.link{ margin-left: 1rem; }
<div class="container">
<div class="dropdown">
PS5 HEADSETS
<div id="links"></div>
</div>
</div>
(A more modern approach would be to repeatedly clone the contents of a template element and to use slot elements to insert corresponding product details into each new instance.)
I'm working on a school task, but I recently got stuck with a textContent issue. I import a JSON file and use the data as a foreach. There are no errors in the .js file, but i receive a typeError: cannot set property 'textContent' of undefined, even though i defined the properties with elements from the JSON file?
When I remove the two lines with textContent, I receive a similar error with the appendChild property: cannot read property 'appendChild' of null.
If i log coffee.name in my forEach, I do get the correct first name. I'm guessing i only get one name since the forEach can't loop further because of the errors further along.
My js code:
import './style.css';
import data from './assets/data/coffees.json';
const init = () => {
console.log(data);
createPriceList(data);
};
const createPriceList = coffees => {
const ul = document.querySelector('prices__list');
console.log(coffees);
coffees.coffees.forEach(coffee => {
if (coffee.plantbased === true) {
const price = document.createElement('li');
price.classList.add('price');
const a = document.createElement('a').classList.add('price__button');
const spanWrapper = document.createElement('span').classList.add('price__button__wrapper');
const spanName = document.createElement('span').classList.add('price__button__name');
const spanAmount = document.createElement('span').classList.add('price__button__amount');
const spanPlus = document.createElement('span').classList.add('price__button__plus');
spanName.textContent = coffee.name;
spanAmount.textContent = coffee.prices.medium;
ul.appendChild(price);
price.appendChild(a);
a.appendChild(spanWrapper);
spanWrapper.appendChild(spanName);
spanWrapper.appendChild(spanAmount);
a.appendChild(spanPlus);
}
});
};
init();
Here is the HTML I'm trying to create (the section in comment, the rest is defined):
<section class="prices highlight spaced">
<h2 class="visually-hidden">Price list</h2>
<ul class="prices__list">
<!--<li class="price">
<a class="price__button">
<span class="price__button__wrapper">
<span class="price__button__name">Oat Latte</span>
<span class="price__button__amount">€ 2</span>
</span>
<span class="price__button__plus">+</span>
</a>
</li> -->
</ul>
</section>
You are trying to chain methods together like this:
const test = document.createElement('span').classList.add('price__button__name');
console.log(test.classList);
But, you have to create the element first and then you can work with its classList or other properties:
const test = document.createElement('span');
test.classList.add('price__button__name');
console.log(test.classList);