textContent on created element in JS not working? - javascript

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);

Related

Firestore forEach returns one item

I'm creating a project / to-do appliction with firestore.
I want to return all the current projects where the active user has been assigned to.
In a console.log(doc.id, doc.data()), I get the two projects where he has been signed up for.
But when I want to show them both on the home screen, I only see one project.
I don't know what I'm doing wrong
Anyone that can help me?
My function:
const returnProjects = async () => {
const list = document.querySelector<HTMLDivElement>('#projectList');
const projects = query(collectionGroup(db, 'projects'));
const querySnapshot = await getDocs(projects);
querySnapshot.forEach((doc) => {
const deadline = doc.data().Deadline;
const fireBaseTime = new Date(
deadline.seconds * 1000 + deadline.nanoseconds / 1000000,
);
const formatOptions = {
format: 'dd MMM yy',
};
console.log(doc.id, '>', doc.data());
const newElemement = document.createElement('div');
if (list) list.appendChild(newElemement).setAttribute('class', 'projectCard');
const card = document.querySelector<HTMLDivElement>('.projectCard');
if (card) { card.innerHTML = `
<h4>${doc.id, doc.data().Name}</h4>
<p>${fireBaseTime.toLocaleDateString('eng-BE', formatOptions)}</p>
<span>3</span>
`; }
});
};
my Html:
<main>
<div id="dashboard" class="dashboard">
<div class='dashboardUtils'>
<h3 id="dashboardName"></h3>
<span id="currentDate"></span>
</div>
<button id="editDashboard" class="secondary-button"></button>
</div>
<div id='dashboardEdits-form' class="editOpen">
<form id='dashboardEdits' class='edit-form'>
<div id='practicalDisplayname'>
<label for='displayname' class='form-label'>Username</label>
<input type='text' class='form-input' id="displaynameInput" name='displayname'></input>
</div>
</form>
<button id="confirmEdits" class="secondary-button">Save edits</button>
</div>
<div id='amountMessage'>
<h1 id='amountProjects'></h1>
</div>
<div id='projectList'></div>
</main>
A screenshot:
The problem is that for each document in the results you do:
const card = document.querySelector<HTMLDivElement>('.projectCard');
if (card) { card.innerHTML = `
<h4>${doc.id, doc.data().Name}</h4>
<p>${fireBaseTime.toLocaleDateString('eng-BE', formatOptions)}</p>
<span>3</span>
`; }
While you're creating a new project card for each result, the querySelector always returns the first card for the HTML. From the MDN documentation on querySelector:
An Element object representing the first element in the document that matches
So for the second document, you're replacing the innerHTML that you set for the first document.
To solve the problem, since you already have a reference to the element you just generated, add the innerHTML to that instead of looking it up with a query selector:
querySnapshot.forEach((doc) => {
...
const newElement = document.createElement('div');
if (list) list.appendChild(newElemement).setAttribute('class', 'projectCard');
newElement.innerHTML = `
<h4>${doc.id, doc.data().Name}</h4>
<p>${fireBaseTime.toLocaleDateString('eng-BE', formatOptions)}</p>
<span>3</span>
`;
})
The problem you are facing here is caused by the following behavior:
https://developer.mozilla.org/en-US/docs/Web/API/Node/appendChild#:~:text=The%20appendChild()%20method%20of%20the%20Node%20interface%20adds%20a%20node%20to%20the%20end%20of%20the%20list%20of%20children%20of%20a%20specified%20parent%20node.%20If%20the%20given%20child%20is%20a%20reference%20to%20an%20existing%20node%20in%20the%20document%2C%20appendChild()%20moves%20it%20from%20its%20current%20position%20to%20the%20new%20position.
What this means is that appendChild will remove the node if already present in the DOM, which is what we are seeing here. This can be easily solved by creating a deep clone of the node first using cloneNode, before appending it to the target div.

How can I change the href of a anchor in javascript using query selector

I cannot change an anchor's href when I do it returns saying that there was a null pointer
I tried using
fetch("https://jwapi.herokuapp.com/ck/results")
.then(res => res.json())
.then(data => {
users = data.map(user => {
const card = userCardTemplate.content.cloneNode(true).children[0]
const header = card.querySelector("[data-header]")
const description = card.querySelector("[data-description]")
const link = card.querySelector("a[data-link]")
header.textContent = user.name
description.textContent = user.description
link.href = user.link <-- ERROR HERE
userCardContainer.append(card)
return { name: user.name, description: user.description, element: card }
})
const value2 = searchInput.value
if (value2 == "") {
users.forEach(user => {
user.element.classList.toggle("hide", true)
})
}
})
But it gave a error saying
Uncaught (in promise) TypeError: Cannot set properties of null (setting 'href')
at script.js:45:23
at Array.map (<anonymous>)
at script.js:38:22
if needed here is my html
<div class="user-cards" data-user-cards-container></div>
<template data-user-template>
<a href="#" data-link>
<div class="card">
<div class="header" data-header></div>
<div class="description" data-description></div>
</div>
</a>
I tried console.log but and it returned null for card.herf but for user.link it returned the correct one
The issue is that you are trying to find a link inside of card but card does not have a link inside of it. Actually the link contains a card which contains a header and a description.
What is the parent of the link element? Assume it is parent then this should work:
// I used `parent` to get the link, not `card`
const link = parent.querySelector("a[data-link]");
// now link should not be null.
// The reason you get an error is that you try to set href on a null object
link.href = 'http://www.google.com';
You can try using
document.querySelector("a").href = "{{url}}";

JavaScript stops functioning after adding type=module to src tag in HTML (Using Flask)

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
})

How to append Button inside a container that is being cloned

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>

How to wrap multiple HTML strings with tags?

I'm a new to coding and trying to wrap a large list of strings with HTML tags. After searching, I already found a method here but I've tried it in VS Code and it seems to be not working for me. Probably I'm doing something wrong.
To clarify it further, I'm trying to wrap a large list of strings with HTML tags.
Elina Arnwine
Freddie Cane
Melia Pittenger
Nobuko Grove
.....
to
<li class="nav-item">Elina Arnwine</li>
....
Any assistance you can provide would be greatly appreciated.
I think you're using javascript which makes it really simple to do this by mapping over the list and creating the elemnts:
const names = [
'Elina Arnwine',
'Freddie Cane',
'Melia Pittenger',
'Nobuko Grove'
];
function createLI(name) {
const el = document.createElement('li');
const a = document.createElement('a');
el.className = 'nav-item';
a.className = 'nav-link text-small pb-0';
a.appendChild(document.createTextNode(name));
el.appendChild(a);
document.getElementById('ul').appendChild(el);
}
names.map(createLI);
<ul id='ul'></ul>
Or you could use a template
const names = [
'Elina Arnwine',
'Freddie Cane',
'Melia Pittenger',
'Nobuko Grove'
];
function manTemplate(name) {
const tmp = document.getElementById('li-template');
const clone = document.importNode(tmp.content, true);
clone.querySelector('a').innerText = name;
document.getElementById('ul').appendChild(clone);
}
names.map(manTemplate);
<template id="li-template">
<li class="nav-item"></li>
</template>
<ul id='ul'></ul>

Categories

Resources