Problems getting data from Firestore to display in DOM - javascript

I'm creating a project with the use of Firebase and I'm having some issues with getting the data inside of my database to display in the DOM due to error "setupFoodGroup is not defined"...This is likely due to a rookie coding error, but I can't seem to work out where I've gone wrong.
Current code as per below:
// Get data from database
db.collection('foodgroups').get().then(snapshot => {
setupFoodGroup(snapshot.docs);
});
// Setting Up Food Lists
const foodList = document.querySelector('.foodGroups');
const setupFoodGroup = (data) => {
let html = '';
data.forEach(doc => {
const group = doc.data();
const li = `
<li>
<div class="card bg-light mb-3" style="max-width: 20rem;">
<div class="card-body">
<h4 class="card-title">${group.title}</h4>
<p class="card-text">${group.content}</p>
</>
</div>
</li>
`;
html += li
})
foodGroups.innerHTML = html;
};
I'm then wanting to get them displaying inside the following HTML on a display page
<div class="card-body">
<ul class="foodGroups">
</ul>
</div>
Any points would be very much appreciated.
Cheers!

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 to add html data to local storage and have it appear on a seperate html page

I have had trouble with a movie watch list project where i have two main pages (movie search page and watch list page) and one JS file they both share. I would like to be able to save a movie result after having searched for a movie on the first HTML page, and have it save to local storage and appear on the second HTML page.
I was able to create a watch list that lives on the same page as the movie search page and save selections temporarily, but when I refresh they disappeared. When I have tried saving to a separate page, I am unable to first fetch the ID where I want to insert the HTML data. I kind of understand this is because I am only able to load one page at at time, yet I have been unable to figure out what to do about it. I have also tried to use preventDefault to stop my second HTML page from refreshing in order to see if my data is saving, but i have been unsuccessful in this as well.
Here is my java script document which both pages share
function run(){
let ID
let movArr
document.querySelector("#search").addEventListener('click',async function(){
const movSearch = document.querySelector("#movieSearch").value
const res = await fetch(`https://www.omdbapi.com/?s=${movSearch}&type=Movie&apikey=3c746e2`)
const data = await res.json()
movArr = data.Search
html=""
for(let movie of movArr)
{
const rez = await fetch(`https://www.omdbapi.com/?i=${movie.imdbID}&type=Movie&apikey=3c746e2`)
const datatwo = await rez.json()
ID = datatwo.imdbID
html +=
` <div class="movieInput" style="display:flex;align-items:center">
<div>
<img src="${movie.Poster}" style="width:99px;margin:42px 0 0 44px">
</div>
<div></div>
<div style="margin:3px 0 0 21px">
<text style="font-weight:bold; font-size:18px">${movie.Title}</text>
<p>${datatwo.imdbRating}</p>
<div style="display:flex; gap:36px; font-size:12px">
<p>${datatwo.Runtime}</p> <p>${datatwo.Genre}</p> <p><i class="fa-solid fa-circle-plus" data-add="${datatwo.imdbID}"></i></p>
</div>
<div style="margin:-20px 0 0 0">
<p>${datatwo.Plot}</p>
</div>
</div>
</div>
<hr />`}
document.querySelector("#movies").innerHTML = html
}) }
function watchlist(){
localStorage.setItem( 'film' , JSON.stringify(html) )
document.querySelector('#moviesToo').innerHTML = JSON.parse(localStorage.getItem('film'))
}
function watch(){
let html
document.addEventListener('click', async function(e){
if(e.target.dataset.add){
console.log('clicked!')
const movie = e.target.dataset.add
const dez = await fetch(`https://www.omdbapi.com/?i=${movie}&type=Movie&apikey=3c746e2`)
const datathree = await dez.json()
ID = datathree.imdbID
html =
`
<div class="movieInput" style="display:flex;align-items:center">
<div>
<img src="${datathree.Poster}" style="width:99px;margin:42px 0 0 44px">
</div>
<div></div>
<div style="margin:3px 0 0 21px">
<text style="font-weight:bold; font-size:18px">${datathree.Title}</text>
<p>${datathree.imdbRating}</p>
<div style="display:flex; gap:36px; font-size:12px">
<p>${datathree.Runtime}</p> <p>${datathree.Genre}</p> <p><i class="fa-solid fa-circle-minus" data-add="${datathree.imdbID}"></i></p>
</div>
<div style="margin:-20px 0 0 0">
<p>${datathree.Plot}</p>
</div>
</div>
</div>
<hr />`}
return watchlist()
})
}
run()
watch()

Firebase Paginate

I made the code below referring to the pagination document of FIREBASE.
( https://firebase.google.com/docs/firestore/query-data/query-cursors#web-v8_3 )
I know that 'limit(3)' prints 3 documents, but I don't know how to use the 'next' and 'last' variables.
What I want to implement is to show three of my posts per page and move to the next page when the button is pressed.
Since I just started web development, everything is difficult. please help me
var first = db.collection('product').orderBy('date').limit(3);
var paginate = first.get().then((snapshot)=>{
snapshot.forEach((doc) => {
console.log(doc.id);
var summary = doc.data().content.slice(0,50);
var template = `<div class="product">
<div class="thumbnail" style="background-image: url('${doc.data().image}')"></div>
<div class="flex-grow-1 p-4">
<h5 class="title">${doc.data().title}</h5>
<p class="date">${doc.data().date.toDate()}</p>
<p class = "summary">${summary}</p>
</div>
</div>
<hr>`;
$('.container').append(template);
})
You can try this function:
let lastDocSnap = null
async function getNextPage(lastDoc) {
let ref = db.collection('product').orderBy('date')
// Check if there is previos snapshot available
if (lastDoc) ref = ref.startAfter(lastDoc)
const snapshot = await ref.limit(3).get()
// Set new last snapshot
lastDocSnapshot = snapshot.docs[snapshot.size - 1]
// return data
return snapshot.docs.map(d => d.data())
}
While calling this function, pass the lastDocSnap as a param:
getNextPage().then((docs) => {
docs.forEach((doc) => {
// doc itself is the data of document here
// Hence .data() is removed from original code
console.log(doc.id);
var summary = doc.content.slice(0,50);
var template = `<div class="product">
<div class="thumbnail" style="background-image: url('${doc.image}')"></div>
<div class="flex-grow-1 p-4">
<h5 class="title">${doc.title}</h5>
<p class="date">${doc.date.toDate()}</p>
<p class = "summary">${summary}</p>
</div>
</div>
<hr>`;
$('.container').append(template);
})
})
Call this function at page load (as lastDocSnap will be null, it'll fetch first 3 docs). Call the same function when user clicks 'next' but this time as we have lastDocSnap, the startAfter method will be added to the query. This essentially means the query will first order the document by date and then fetch 3 documents after the document you pass in startAfter

Working with html template tag and can't seem to display whole array of information

This is my first time working with the html template tag. I don't know if I am doing it wrong but the output of the template is only showing the last item in the array. I've tried to loop through it and the foreach method but still only the last item shows. I know I am probably overriding the items as they are stacked on top of each other but I can't seem to figure it out Can someone point me in the right direction?
<div class="products-container">
<template>
<div class="product">
<img class="img">
<h2 class="item-title"></h2>
<h3 class="price"></h3>
</div>
</template>
</div>
const template = document.querySelector('template').content
const copyTemplate = document.importNode(template, true)
let shoppingList = [];
async function getData(data) {
const products = data.items.map(product => {
copyTemplate.querySelector('.item-title').textContent = product.title;
copyTemplate.querySelector('.price').textContent = product.price;
copyTemplate.querySelector('.img').src = product.image;
});
document.querySelector('.products-container').appendChild(copyTemplate);
shoppingList.push(products);
return products
}
I figured it out. I had to put the template and the copytemplate within the map function for it to work.

Vue JS - Apply certain classes to elements in a v-for loop according to data from previous elements in the loop

Sorry for the lengthy title, I have a feeling this is an oddly specific edge case not many people have to deal with.
Some background, I'm working on a webapp to track PC repairs for our shop. We have one currently, which we purchased and have access to the source code thanks to the author's method of distribution. Each repair is signified by a Work Order, all of which have notes. In the old app, the notes have the users name, the date it was posted, and the edit and delete buttons (if you are either the admin or the author) on the left, and the note on the right. If the user that posted the note changes when going down the list, it swaps them around, so the note is on the left and user is on the right, i.e.
user1 - note text
note text - user2
user3 - note text
user3 - note text
note text - user1
The old app did this with plain PHP, in a php file filled to the brim with echo statements. The new app I was working on has a laravel backend (to make use of things like Eloquent) with a Vue JS frontend (to assist with live updates in websockets). So on the Work Order page, there is a component for the list of notes, which takes the list of notes assigned to that Work Order as a prop, and iterates over all the notes with a v-for. I wanted to mimic the orientation switching feature from the previous setup. I can acheive the switch by setting up each note as a grid container, and applying either order-first or order-last to the column containing the user info. What I'm struggling with is trying to find a way to toggle which class is applied when the user changes.
At first I had a data attribute keeping track of the current class, so when the user changed I could check what the class was currently and switch it to the opposite. However, this caused an infinite render loop, as the entire list of notes would re-render whenever that attribute was changed. It did accomplish what I wanted to do visually, but it caused severe performance issues. Then I tried using refs, so when the user changed I could get the previous entry in the list from the refs array and examine its classes to see what order class it had to set the next elements order class appropriately. However this didn't work because the refs array would not be populated until the list was done rendering, and I needed to set the class as it rendered. I tried using a computed property, but it can't take arguments (i.e. the index of the array I was currently on to compare with index-1) and even if it could there is no way I could find to check the current cached value of that property while calculating the new one.
Here is the code I am working with for reference, currently with any of the previous approaches I tried removed, so currently no user switching happens.
<template>
<ul class="list-unstyled">
<li class="row no-gutters mb-2" v-bind:key="index" v-for="(note, index) in this.notes">
<note-form-modal :modal-id="'note'+note.noteid+'editModal'" :populate-with="note"></note-form-modal>
<div class="col-md-1 d-flex flex-column mx-md-3">
<div class="text-center p-0 m-0">{{note.noteuser}}</div>
<div class="text-muted text-small text-center p-0 m-0">{{getHRDate(note.notetime)}}</div>
<div class="btn-group justify-content-center p-0 m-0">
<template v-if="authusername === note.noteuser || authusername === 'admin'">
<button class="btn btn-sm btn-primary m-1" data-toggle="modal" :data-target="'#note'+note.noteid+'editModal'"><i class="fas fa-fw fa-edit"></i></button>
<button class="btn btn-sm btn-danger m-1"><i class="fas fa-fw fa-trash-alt"></i></button>
</template>
</div>
</div>
<div class="col-md-10">
<div class="card m-2">
<div class="card-body p-2">
{{ note.thenote }}
</div>
</div>
</div>
</li>
</ul>
</template>
<script>
import dateMixin from '../mixins/dateMixin'
export default {
mixins:[dateMixin],
props: ['initialnotes', 'authusername', 'noteType', 'woid'],
data () {
return {
notes: Object.values(this.initialnotes),
currentOrder: 'order-first',
newNote: {
notetype: this.noteType,
thenote: '',
noteuser: this.authusername,
woid: this.woid
}
}
},
mounted () {
Echo.channel('wonotes.'+this.noteType+'.'+this.woid)
.listen('WorkOrderNoteAdded', (e) => {
this.notes.push(e.note)
})
.listen('WorkOrderNoteEdited', (e) => {
let index = this.notes.findIndex((note) => {
return note.noteid === e.note.noteid
})
this.notes[index] = e.note
})
.listen('WorkOrderNoteDeleted', (e) => {
let index = this.notes.findIndex((note) => {
return note.noteid === e.noteid
})
this.notes.splice(index, 1)
})
},
methods: {
createNote () {
axios.post('/api/workorders/notes', this.newNote)
.then((response) => {
$('#note'+this.noteType+'add').collapse('hide')
this.newNote.thenote = ''
})
}
}
}
</script>
noteType is there because we have two different types of notes, one that a customer can see, and one that only techs can see.
Is there something obvious I'm missing, have I just architected this thing wrong, am I trying to do something impossible? Any assistance would be greatly appreciated, I'm at the end of my rope here with this one.
I ended up figuring out a solution to this, not sure if its the best one but here goes.
Basically, I maintain an array parallel to the notes array that determines which order- class each array index should have. As it is dependent on the notes attribute, I make it a computed property, so that I can use the notes attribute and it automatically updates when the notes list changes. The file now looks like this (with some code related to posting new notes and editing existing ones removed for clarity)
<template>
<ul class="list-unstyled">
<li class="row no-gutters mb-2" v-bind:key="index" v-for="(note, index) in this.notes">
<div class="col-md-1 d-flex flex-column mx-md-3" :class="noteOrders[index]">
<div class="text-center p-0 m-0">{{note.noteuser}}</div>
</div>
<div class="col-md-10">
<div class="card m-2">
<div class="card-body p-2">
{{ note.thenote }}
</div>
</div>
</div>
</li>
</ul>
</template>
<script>
export default {
props: ['initialnotes'],
data () {
return {
notes: this.initialnotes,
}
},
computed: {
noteOrders () {
return this.getNoteOrders(this.notes)
}
},
methods: {
getNoteOrders(notes) {
let noteOrders = []
notes.forEach((note,index) =>{
if (index === 0) {
noteOrders[index] = 'order-first'
} else if (note.noteuser !== notes[index-1].noteuser) {
if (noteOrders[index-1] === 'order-first') {
noteOrders[index] = 'order-last'
} else {
noteOrders[index] = 'order-first'
}
} else {
noteOrders[index] = noteOrders[index-1]
}
})
return noteOrders
}
}
}
</script>
There may very well be better solutions and I encourage anyone who happens upon this to post theirs if they have one, but as I found a solution that works for me I decided to post it for anyone else running into the same issue.

Categories

Resources