Executing next task first when firebase forEach loop has finished - javascript

I have two tasks. They have to get values from Firebase Firestore. Here is my JavaScript:
if (doc.exists) {
console.log("Document data:", doc.data());
const data = doc.data();
const members = data.members;
var permission_table = document.getElementById("noten_tabelle_permission");
var permission_table_container = document.getElementById("main_padding");
members.forEach(el => {
console.log(el)
table_number++;
const html = fillTemplate("grade_table" + table_number, doc.id);
// Join the array of html and add it as the `innerHTML` of `main`
document.getElementById("main_padding").insertAdjacentHTML('beforeend', html);
db.collection("users").doc(el).collection("grades").get().then(function(querySnapshot) {
querySnapshot.forEach(function(doc) {
console.log(doc.id, " => ", doc.data());
const data = doc.data();
addToTable("grade_table" + table_number, doc.id, data.mdl, data.klu);
});
});
})
} else {
console.log("No such document!");
}
So first as you can see I am getting the "members" from Firestore. Then I create for each one a table with a different id. By the way. Here is the "fillTemplate" Function:
function fillTemplate({ table_name, id }) {
return `
<div class="noten_tabelle_permission" id="noten_tabelle_permission">
<h1 id="member_name">${id}</h1>
<table id="${table_name}" style="width:100%">
<tr>
<th>Fach</th>
<th>mündlich</th>
<th>Klausur</th>
</tr>
<!-- Make content with js code -->
</table>
</div>
`;
}
So after joining table to main view, I am searching for each user ( === or for each table) the values. I add them to the table by calling the "addToTable" function, you can see here:
function addToTable(table_name, subject, mdl, klu) {
var subject_name = getSubjectByNumber(subject);
var short_subject = getSubjectShortByNumber(subject);
var y = document.createElement([short_subject]);
y.setAttribute("id", [short_subject]);
document.getElementById([table_name]).appendChild(y);
var y = document.createElement("TR");
y.setAttribute("id", [short_subject]);
var cE = document.createElement("TD");
var tE = document.createTextNode([subject_name]);
cE.appendChild(tE);
y.appendChild(cE);
var a = document.createElement("TD");
var b = document.createTextNode([mdl]);
a.appendChild(b);
y.appendChild(a);
var c = document.createElement("TD");
var d = document.createTextNode([klu]);
c.appendChild(d);
y.appendChild(c);
document.getElementById(table_name).appendChild(y);
}
But now I am getting this Error: Uncaught (in promise) TypeError: Cannot read property 'appendChild' of null in this line: document.getElementById([table_name]).appendChild(y);. I think it is because Firebase is asynchronous. But how to fix this? How to code it that it strictly get the values for the member and start first than with the next member?
The id of the tables is the string "grade_table" and the number of the var table_number. The id is needed even for the second firebase task. But since the first Firebase task loads asynchronous the var table_number is updated instantly with this line: table_number++;. So the next task of firebase has the wrong table id.
~marcelo

Related

Error in assigning elements in array in JS

i am trying to store list data into an object received from axios response. the first set of data being headers i am storing in cols as array and the rest in rows. The data received is all defined and after parsing it perfectly logged in the console ie all defined. inside the loop when loading of rows elements starts, the first set of elements gets stored but for the next set ie for the value of i = 2 I am getting error saying cannot set property of undefined (setting 0).
For convenience I have changed the type of data received from the axios
let response = {data:'"A","B"\n"C","D"\n"E","F"'} //await axios(URL)
let raw = response.data.split(/\r?\n/);
let data = {
cols:[],
rows:[] // I have tried rows:[[]] or rows:[{v:[]}]
}
for (let i in raw) {
raw[i] = raw[i].split(",");
for(let j in raw[i]){
raw[i][j] = raw[i][j].replace(/"/g, '')
if (i==0)
data.cols[j]=raw[i][j]
else{
data.rows[i-1][j]=raw[i][j] // for rows as object => data.rows[i-1].v[j]
//console.log(i+'->'+data.rows[i-1])
}
}
}
return data // this is not matter of concern
}
I have tried declaring the row array as 2D array but error persists. hoving the mouse over the object gives rows (property) : never[] and same with cols.
You can do:
const response = { data: '"A","B"\n"C","D"\n"E","F"' }
const [cols, ...rows] = response.data.split(/\r?\n/).map(r => r.match(/[A-Z]+/g))
const data = { cols, rows }
console.log(data)
You can do something like this
let response = {data:'"A","B"\n"C","D"\n"E","F"'}
const [cols, ...rows] = response.data.split('\n').map(r => r.replace(/"/g, '').split(','))
const data = {
cols,
rows
}
console.log(data)

JavaScript: Catch errors in async function and stop the rest of the function if there are any

EDIT: ANSWER BELOW
I'm making my first JavaScript project and decided to make a simple weather app. It fetches weather data of a city you put in from the openweathermap.org api and displays it in a table. I firstly made it using fetch() and .then. I then learned about async functions and the await keyword. After converting the script to an asynchronous function, I came across a problem. If the first city you enter isn't a real city (an error is catched while fetching the api), the warning message appears, BUT the table also appears because the rest of the function still executes.
So my question is: how can I stop the async function if any errors are catched?
Here's the website: https://lorenzo3117.github.io/weather-app/
Here's the code:
// Launch weather() function and catch any errors with the api request and display the warning message if there are any errors
function main() {
weather().catch(error => {
document.querySelector("#warningMessage").style.display = "block";
console.log(error);
});
}
// Main function
async function weather() {
// Take city from input and reset input field
var city = document.querySelector("#cityInput").value;
document.querySelector("#cityInput").value = "";
// Get api response and make it into a Json
const apiResponse = await fetch("https://api.openweathermap.org/data/2.5/weather?q=" + city + "&appid=<apiKey>&units=metric");
const jsonData = await apiResponse.json();
// Removes warning message
document.querySelector("#warningMessage").style.display = "none";
// Puts the Json into an array and launches createTable function
var arrayJson = [jsonData];
createTable(document.querySelector("#table"), arrayJson);
// Function to create the table
function createTable(table, data) {
// Makes the table visible
document.querySelector("#table").style.display = "block";
// Goes through the array and makes the rows for the table
for (let i = 0; i < data.length; i++) {
let rowData = data[i];
var row = table.insertRow(table.rows.length);
// This var exists to make the first letter capitalized without making a gigantic line (see insertCell(3), line 53)
// Could be made into a function if needed
var weatherDescription = rowData.weather[0].description;
// Take latitude and longitude for google maps link
var lat = rowData.coord.lat;
var long = rowData.coord.lon;
// Make an a-tag for link to google maps
var mapLink = document.createElement("a");
mapLink.innerHTML = "Link";
mapLink.target = "_blank";
mapLink.href = "https://www.google.com/maps/search/?api=1&query=" + lat + "," + long;
// Making rows in table
row.insertCell(0).innerHTML = rowData.name + ", " + rowData.sys.country;
row.insertCell(1).innerHTML = rowData.main.temp + " °C";
row.insertCell(2).innerHTML = rowData.main.humidity + "%";
row.insertCell(3).innerHTML = weatherDescription.charAt(0).toUpperCase() + weatherDescription.slice(1);
row.insertCell(4).appendChild(mapLink); // appendChild for anchor tag because innerHTML only works with text
}
}
And the repo: https://github.com/lorenzo3117/weather-app
Thank you
you can do this :
async function weather() {
try {
const apiResponse = await fetch("https://api.openweathermap.org/data/2.5/weather?q=" + city + "&appid=02587cc48685af80ea225c1601e4f792&units=metric");
} catch(err) {
alert(err); // TypeError: failed to fetch
return;
}
}
weather();
Actually, the error catched isn't an error with the api itself because the api still sends a json, but the error is catched while trying to read a certain object from the json (which doesn't exist because the json isn't a normal one with weather data). Therefore the function stops far later than expected, after the table was made visible.
I just put the line that made the table visible after the function that creates the table (after where the real error occurs). Also thanks #Dadboz for the try catch method which made the code even more compact. I also added an if else to check if the json file is the correct one so unnecessary code doesn't get executed. Thanks #James for pointing this out to me.
Here's the final code:
// Main function
async function weather() {
try {
// Take city from input and reset input field
var city = document.querySelector("#cityInput").value;
document.querySelector("#cityInput").value = "";
// Get api response and make it into a Json
const apiResponse = await fetch("https://api.openweathermap.org/data/2.5/weather?q=" + city + "&appid=<apiKey>&units=metric");
const jsonData = await apiResponse.json();
if (jsonData.message == "city not found") {
document.querySelector("#warningMessage").style.display = "block";
} else {
// Removes warning message
document.querySelector("#warningMessage").style.display = "none";
// Puts the Json into an array and launches updateTable function
var arrayJson = [jsonData];
updateTable(document.querySelector("#table"), arrayJson);
}
}
catch (error) {
console.log(error);
}
}
// Function to update the table
function updateTable(table, data) {
// Goes through the array and makes the rows for the table
for (let i = 0; i < data.length; i++) {
let rowData = data[i];
var row = table.insertRow(table.rows.length);
// This var exists to make the first letter capitalized without making a gigantic line (see insertCell(3), line 53)
// Could be made into a function if needed
var weatherDescription = rowData.weather[0].description;
// Take latitude and longitude for google maps link
var lat = rowData.coord.lat;
var long = rowData.coord.lon;
// Make an a-tag for link to google maps
var mapLink = document.createElement("a");
mapLink.innerHTML = "Link";
mapLink.target = "_blank";
mapLink.href = "https://www.google.com/maps/search/?api=1&query=" + lat + "," + long;
// Making rows in table
row.insertCell(0).innerHTML = rowData.name + ", " + rowData.sys.country;
row.insertCell(1).innerHTML = rowData.main.temp + " °C";
row.insertCell(2).innerHTML = rowData.main.humidity + "%";
row.insertCell(3).innerHTML = weatherDescription.charAt(0).toUpperCase() + weatherDescription.slice(1);
row.insertCell(4).appendChild(mapLink); // appendChild for anchor tag because innerHTML only works with text
}
// Makes the table visible
document.querySelector("#table").style.display = "block";
}
Thanks everyone for your answers, have a good day!
Lorenzo

JavaScript multi-level promises skipped .then()

I was having some problem with multi-level of promises. What I tried to do is first get list of receipt items under certain category, then for each receipt item, I get its detail & receipt ID, after I get the receipt ID, I search for the account ID. Then, I get the account details based on account ID. Here is my code:
var query = // get receipt items under certain category
var outerPromise = query.once('value').then(data => {
var promises = [];
var datasetarr = [];
data.forEach(snapshot => {
var itemData = // get receipt item unique push ID
var query = // get details of receipt items
var promise = query.once('value').then(data => {
var itemDetail = // get receipt item detail
if(type == subtype){
var receiptID = itemDetail.receiptID;
var query = // query receipts table by receiptID
return query.once('value').then(data => {
data.forEach(snapshot => {
snapshot.forEach(childSnapshot => {
if(childSnapshot.key == receiptID){
var accountKey = // get accountID
var query = // query accounts table
return query.once('value').then(data => {
var accountDetail = data.val();
var age = accountDetail.age;
var gender = accountDetail.gender;
console.log(age + ' ' + gender);
datasetarr.push({age: age, gender: gender});
});
}
});
});
});
}
});
promises.push(promise);
});
return Promise.all(promises).then(()=> datasetarr);
});
I managed to print out the result from the console.log above. However, when I tried to print out here which is when the promise is done:
outerPromise.then((arr) => {
console.log('promise done');
for(var i = 0; i < arr.length; i++){
console.log(arr[i].age + ' ' + arr[i].gender);
}
});
I get nothing here. The console now is showing 'promise done' first before any other results I printed out above.
How can I do this correctly?
I will provide a more detailed explanation in a couple of hours, I have a prior engagement which means I can't provide details now
First step to a "easy" solution is to make a function to make an array out of a firebase snapshot, so we can use map/concat/filter etc
const snapshotToArray = snapshot => {
const ret = [];
snapshot.forEach(childSnapshot => {
ret.push(childSnapshot);
});
return ret;
};
Now, the code can be written as follows
// get list of receipt items under category
var query // = // get receipt items under certain category
var outerPromise = query.once('value').then(data => {
return Promise.all(snapshotToArray(data).map(snapshot => {
var itemData // = // get receipt item unique push ID
var query // = // get details of receipt items
return query.once('value').then(data => {
var itemDetail // = // get receipt item detail
if(type == subtype){
var receiptID = itemDetail.receiptID;
var query //= // query receipts table by receiptID
return query.once('value').then(data => {
return Promise.all([].concat(...snapshotToArray(data).map(snapshot => {
return snapshotToArray(snapshot).map(childSnapshot => {
if(childSnapshot.key == receiptID){
var accountKey //= // get accountID
var query //= // query accounts table
return query.once('value').then(data => {
var accountDetail = data.val();
var age = accountDetail.age;
var gender = accountDetail.gender;
console.log(age + ' ' + gender);
return({age, gender});
});
}
}).filter(result => !!result);
}).filter(result => !!result)));
});
}
});
})).then([].concat(...results => results.filter(result => !!result)));
});
To explain questions in the comments
[].concat used to add the content of multiple arrays to a new array, i.e
[].concat([1,2,3],[4,5,6]) => [1,2,3,4,5,6]
...snapshotToArray(data).map(etc
... is the spread operator, used as an argument to a function, it takes the iterable and "spreads" it to multiple arguments
console.log(...[1,2,3]) == console.log(1,2,3)
In this case snapshotToArray(data).map returns an array of arrays, to give a console log example
console.log(...[[1,2],[3,4]]) == console.log([1,2], [3,4])
adding the concat
[].concat(...[[1,2],[3,4]]) == [].concat([1,2],[3,4]) == [1,2,3,4]
so it flattens a two level array to a single level, i.e.
console.log(...[[1,2],[3,4]]) == console.log(1,2,3,4)
So in summary, what that code fragment does is flatten a two level array
filter(result => !!result)
simply "filters" out any array elements that are "falsey". As you have this condition
if(childSnapshot.key == receiptID){
if that is false, the result will be undefined for that map - all other results will be an array, and even empty arrays are truthy - that's why the filtering is done so often! There's probably a better way to do all that, but unless you're dealing with literally millions of items, there's no real issue with filtering empty results like this
End result is a flat array with only the Promises returned from the code within

How can I run an array of data through firebase?

i have a data structure as below
I created an array called new array with the IDs such as [19777873, 53399293]
var dbRef = firebase.database().ref().child('Agents').child(newarray[i]);
dbRef.on('value', snapshot => {
firebase.auth().onAuthStateChanged((user) => {
if (user) {
database = firebase.database();
console.log("Testing if the array values are here \n" + newarray);
// var dbRef = firebase.database().ref().child('Agents').child(newarray[i]);
dbRef.on('value', newAgents, errData);
}
})
}
New Agent function
function newAgents(data) {
var container = document.getElementById("team");
container.innerHTML = '';
data.forEach(function(AgentSnap) { // loop over all jobs
var key = AgentSnap.AgentID;
console.log(AgentSnap.key);
var Agents = AgentSnap.val();
var AgentCard = `
<div class= "profilepics" id="${key}">
<figure ><img src=${Agents.profilepicurl}><figcaption>${Agents.Fname}</figcaption></figure>
</div>
`;
container.innerHTML += AgentCard;
})
}
the problem I'm having now is that images(from {Agents.profilepicurl}) are being displayed and name (from {Agents.Fname}) are not being displayed. instead of name it shows "undefined" and no error is show in console. What am I doing wrong and how can I fix this?
There are some strange things happening in the first few lines of your code. You are setting the "on" listener twice (ignoring the resulting snapshot for the first listener), and you're awaiting authentication and setting a database object without using it. I am not absolutely certain about what you're trying to achieve, but would this work? It's a simplified version of what I think you're trying to do with the current code:
console.log("Testing if the array values are here \n" + newarray);
firebase.auth().onAuthStateChanged((user) => {
if (user) {
document.getElementById("team").innerHTML = '';
database = firebase.database();
for (agentId of newarray) {
var dbRef = database.ref().child('Agents').child(newarray[i]);
dbRef.on('value', newAgents);
}
}
})
function newAgents(AgentSnap) {
var container = document.getElementById("team");
var key = AgentSnap.AgentID;
console.log(AgentSnap.key);
var Agent = AgentSnap.val();
var AgentCard = `
<div class= "profilepics" id="${key}">
<figure ><img src=${Agent.profilepicurl}><figcaption>${Agent.Fname}</figcaption></figure>
</div>
`;
container.innerHTML += AgentCard;
}

How to return more than one row from HANA database using XS?

I'm trying to retrieve all data from a db table into json object, like so:
function getTableData()
{
var vals = {};
var data = [];
try {
var dbCon = $.db.getConnection();
var query = 'SELECT * FROM SAPPRD.ZUSERDATATAB';
var pstmt = dbCon.prepareStatement(query);
var rs = {};
rs = pstmt.executeQuery();
while (rs.next()) {
vals.team = rs.getString(1);
vals.fname = rs.getString(3);
vals.lname = rs.getString(2);
data.push(vals);
$.response.status = $.net.http.OK;
}
$.response.setBody(JSON.stringify(data));
// $.response.contentType = contentType;
// $.response.headers.set('Content-Disposition', 'filename=' + filename);
} catch (e) {
$.response.setBody('errors: ' + e.message);
}
}
The query works only partially, because in data I get number of rows x last row content, like so:
[{"team":"I313766","fname":"0","lname":"LEGOWSKI"},
{"team":"I313766","fname":"0","lname":"LEGOWSKI"},
etc. etc.]
How would I make it retrieve all the data instead of one row number of times?
Okay, I got the solution. Moving a single line declaring array vals into the while statement solved the problem - the array vals was initialized as an empty array each time, therefore allowing the proper .push of each row, instead of pushing last row from db table into data multiple times. Thanks to everybody who took time and tried answering.
function getTableData()
{
var data = [];
try {
var dbCon = $.db.getConnection();
var query = 'SELECT * FROM SAPPRD.ZUSERDATATAB';
var pstmt = dbCon.prepareStatement(query);
var rs = pstmt.executeQuery();
while (rs.next()) {
var vals = {}; // this is the moved line of code...
vals.team = rs.getString(1);
vals.fname = rs.getString(3);
vals.lname = rs.getString(2);
data.push(vals);
$.response.status = $.net.http.OK;
}
$.response.setBody(JSON.stringify(data));
// $.response.contentType = contentType;
// $.response.headers.set('Content-Disposition', 'filename=' + filename);
} catch (e) {
$.response.setBody('errors: ' + e.message);
}
}
solution above just in case someone needs it in future.
This is XSJS(server side JS) and not SAPUI5. The read of DB is pretty similar to the JDBC framework in Java to read DB tables and the result set collection will have the data and you iterate over them and move them to a local object.
There is only call to the DB during execute_query and rs.next() is just a loop to read each row.

Categories