Reputation: 3724
I have a card with class name contact-card when i click this card it should toggle another div's visibility. The card is laid out after getting data from a database.
function buildCard(doc) {
let ulList = document.createElement('ul');
let listItem = document.createElement('li');
let link = document.createElement('a');
let icon = document.createElement('i');
let icon2=document.createElement('i');
ulList.className = "collection";
listItem.className = "collection-item avatar card-contact";
link.className = "secondary-content";
icon.className = "material-icons";
icon2.className = "material-icons";
listItem.setAttribute('id', 'card-contact');
icon.setAttribute('id','mail');
icon2.setAttribute('id','fav');
if(doc.read==true){
icon2.textContent='drafts';
}else{
icon2.textContent='mail_outline';
}
if(doc.favourite==true){
icon.textContent = 'grade';
}else{
icon.textContent='star_border';
}
link.appendChild(icon2);
link.appendChild(icon);
listItem.appendChild(link);
ulList.appendChild(listItem);
mainContactCard.appendChild(ulList);
}
MRef = db.collection("user").doc("3454").collection('contact').orderBy("time", "desc").limit(10);
MRef.get().then(function (querySnapshot) {
querySnapshot.forEach(function (doc) {
buildCard(doc.data());
});
})
.catch(function (error) {
console.log("Error getting documents: ", error);
});
var contactCard = document.querySelector('.card-contact');
setTimeout(ert, 2000);
function ert(){
contactCard.addEventListener('click', toggleThis);
}
function toggleThis() {
$("#chat-card").toggle("fast");
//chat-card is supposed to be the div that fades in and out
}
I tried to add a timeout of ten seconds thinking that the dom is being loaded fast and failing to get the value of the variable contactCard but this is not the case. After ten seconds I still get the error Uncaught TypeError: Cannot read property 'addEventListener' of null
What am I doing wrong. I need there to be many cards which if i click any it toggles visibility of the div. That is why I am selecting by className
(more added code)
lastMessageRef.get().then(function (querySnapshot) {
querySnapshot.forEach(function (doc) {
buildContactCard(doc),str();
});
})
.catch(function (error) {
console.log("Error getting documents: ", error);
});
function str() {
var contactCard=document.getElementsByClassName('card-contact')
for (var i = 0; i < contactCard.length; i++) {
//Distribute(contactCard.item(i));
contactCard.item(i).addEventListener('click', function (e) {
$("#chat-card").toggle("fast");
if (e.target && e.target.nodeName == "LI") {
// List item found! Output the ID!
console.log("List item ", e.target.id.replace("", ""), " was clicked!");
}
});
}
}
Upvotes: 0
Views: 675
Reputation: 61
The element does not exist yet when you try to access it.
Adding a 10 seconds delay after document.querySelector('.card-contact');
does not help since you try to find the element before waiting the 10 seconds.
To make this works:
var contactCard = document.querySelector('.card-contact');
setTimeout(ert, 10000);
function ert(){
contactCard.addEventListener('click', toggleThis);
}
You should change it to:
setTimeout(ert, 10000);
function ert(){
var contactCard = document.querySelector('.card-contact');
contactCard.addEventListener('click', toggleThis);
}
Note that this is still not a good solution. You have many ways to solve your issue, depending on the coupling between the creation of the element, and its use. Without more of your code, we cannot give you a proper solution.
EDIT (I just refreshed and you added some code): The above code only gets the first element with the class .card-contact
. If you want to keep this solution, you should probably use querySelectorAll and iterate over the elements found instead of using querySelector. Anyway, keep on reading.
One solution could be to return your element from the function that creates it, and add the listener on the returned element.
const card = buildCard(doc.data());
card.addEventListener(/* ... */);
Another one could be using callbacks, when you call the function to create the element, you pass a callback as parameter and call the callback inside the function, with the created element as parameter. Since what you're doing is not asynchronous, I don't really see an advantage to using a callback here.
function callback(card) {
card.addEventListener(/* ... */);
}
buildCard(doc.data(), callback);
Another one could be using events, a custom emitter. When you're done creating your element, you dispatch an event to notify listeners that the element is created. You assign a listener on this event, to do all post-creation things.
myEmitter.addEventListener('card-created', /* ... */);
buildCard(doc.data()); // This function should fire 'card-created' on myEmitter
Another one... is to add a MutationObserver. I don't recommend it as it is overkill for your simple use-case. This would let you "observe" for changes in an element's child list, so you could detect when the card is added to its parent, then execute your code.
Hope this helps you.
(Note: This is my first activity on SO, so I cannot comment)
Upvotes: 1