I am trying to fetch data from Firebase to display a chart. The promises seems to work fine when I individually enter the url instead through the for loop. When I use the for loop, the ref is not updating and week[i] is undefined. Once the xaxis array is filled, I will be calling another function to display a chart. I am very new to asynchronous callbacks and any direction/hint will be greatly appreciated. I am wondering why week[i] is undefined within the callback?
EDIT: Looks like the for loop runs fully first and then firebase is loaded. So, value of i will be 6 by the time firebase is loaded and ready to go.
var xaxis = [];
var week = [582016, 592016, 5102016, 5112016, 5122016, 5132016, 5142016];
for (var i = 0; i < 6; i++) {
var total_day_calories = 0;
var ref = new Firebase("https://xxxxxxxxxx.firebaseio.com/"+ week[i] );
ref.once('value').then(function(snapshot){
snapshot.forEach(function(childSnapshot) {
var childData = childSnapshot.val().total_calories;
total_day_calories = total_day_calories + childData;
});
xaxis.push(total_day_calories);
console.log(xaxis);
}, function(errorObject) {
console.log("The read failed: " + errorObject.code);
});
}
Related
I have two functions that I am trying to run when I load the page. dataRetrieve() gets the data from a firebase collection. populate() is supposed to populate a boxlist with the entries retrieved from dataRetrieve(). The main problem is that it lists the array as empty when I run populate() after dataRetrieve() no matter what I try. The last thing I tried was this:
async function dataRetrieve(){
const getAdmins = firebase.functions().httpsCallable('getAdmins');
// Passing params to data object in Cloud functinon
getAdmins({}).then((results) => {
admins = results;
console.log("admins retrieved");
console.log(admins);
}).then(() => {
populate();
});
}
async function populate(){
let list = document.getElementById("user-list");
//loop through users in out Users object and add them to the list
for (var i = 0; i < admins.length; i++) {
let newItem = document.createElement('option');
newItem.innerHTML = admins[i].first + " " +admins[i].last;
newItem.id = admins[i].uid;
if (i == 0) {
newItem.className = "active";
}
console.log(newItem.innerHTML + " " + newItem.id)
list.appendChild(newItem);
}
updateResponse(list.firstChild);
list.size = admins.length;
console.log(document.getElementById("user-list").size)
//collect all the list items
let listItems = list.querySelectorAll('option');
//loop through the list itmes and add a click listener to each that toggles the 'active' state
for (var i = 0; i < listItems.length; i ++) {
listItems[i].addEventListener('click', function(e) {
if (!e.target.classList.contains('active')) {
for (var i = 0; i < listItems.length; i ++) {
listItems[i].classList.remove('active');
}
e.target.classList.add('active');
updateResponse(e.target);
}
})
}
}
also, admins is a global variable listed at the start of the script:
var admins = [];
I am trying to run all this onload so I can instantly generate the list
I thought that .next would cause it to wait to get the values before running, but even making results a parameter and transferring it directly into the function that way gives an undefined array. I don't understand why the function insists on calling on old data. Pls help.
I'm not sure what updateResponse function does. If it's not returning a promise then I'd make the populate function synchronous first. Also do you really need to use admins array somewhere else apart from populate function that it is a global variable? If not then I'd just pass it as a parameter.
async function dataRetrieve() {
const getAdmins = firebase.functions().httpsCallable('getAdmins');
// Passing params to data object in Cloud function
const results = await getAdmins({})
console.log("admins retrieved");
console.log(results);
// Passing results in populate function
populate(results.data)
// If your function returns an array, pass the array itself
}
function populate(admins) {
let list = document.getElementById("user-list");
//loop through users in out Users object and add them to the list
// Using a for-of loop instead so no need to worry about checking the length here
for (const admin of admins) {
let newItem = document.createElement('option');
newItem.innerHTML = admin.first + " " + admin.last;
newItem.id = admin.uid;
//if (i == 0) {
// newItem.className = "active";
//}
console.log(newItem.innerHTML + " " + newItem.id)
list.appendChild(newItem);
}
updateResponse(list.firstChild);
// rest of the logic
}
I guess you know how to check when the page loads. call the retrieve function when the page is loaded. Then you should call the populate function at the end of the retrieve function. this makes sure that the populate function is called after you get all the data
I am working on a Javascript project and am trying to read all the keys in my firebase database and inserting each and every entry into its own HTML heading. Although I have run into a problem where it is not working. What should I do? Here is the code.
function comment(){
x = document.getElementById("comment").value;
writeUserData(x);
}
message_id = 0;
function writeUserData(words) {
database.ref('comments/' + String(message_id)).set({
comment: words,
});
message_id ++;
}
function readComments(){
var children;
database.ref("comments/").on("value", function(snapshot) {
children = snapshot.numChildren();
})
for (i = 0; i < children; i ++){
database.ref('comments/' + String(i)).on("value", function(snapshot) {
snapshot.forEach(function(childSnapshot) {
var data = childSnapshot.val();
//create html elements
new_comment = document.createElement("H");
new_comment.innerHTML = data;
document.getElementById("comment_div").appendChild(new_comment);
document.getElementById("comment_div").appendChild(document.createElement('br'));
});
});
}
}
Also, I am new to databases. So if there is perhaps a better way to write this code, please let me know. Thanks!
Data is loaded from Firebase (and most modern cloud APIs) asynchronously. While that is happening, your main code continues to execute.
By the time your for (i = 0; i < children; i ++){ now runs, the children = snapshot.numChildren() hasn't executed yet, so children has no value and the loop never enters.
The solution is always the same: any code that needs the data to the database, needs to be inside the callback that gets executed when the data has loaded.
So the simple solution is to move that code into the callback:
database.ref("comments/").on("value", function(snapshot) {
children = snapshot.numChildren();
for (i = 0; i < children; i ++){
database.ref('comments/' + String(i)).on("value", function(snapshot) {
snapshot.forEach(function(childSnapshot) {
var data = childSnapshot.val();
//create html elements
new_comment = document.createElement("H");
new_comment.innerHTML = data;
document.getElementById("comment_div").appendChild(new_comment);
document.getElementById("comment_div").appendChild(document.createElement('br'));
});
});
}
})
But I don't think you even need the nested on() call here, as all data is already loaded as part of database.ref("comments/").on("value". All you need to do is loop over it in the callback, which should look something like:
database.ref("comments/").on("value", function(commentsSnapshot) {
commentsSnapshot.forEach(function(snapshot) {
snapshot.forEach(function(childSnapshot) {
var data = childSnapshot.val();
//create html elements
new_comment = document.createElement("H");
new_comment.innerHTML = data;
document.getElementById("comment_div").appendChild(new_comment);
document.getElementById("comment_div").appendChild(document.createElement('br'));
});
});
})
When using the Bing Maps Api and doing geocoding I am trying to store the latitude and longitude in arrays from the callback. Mostly this works, except for one usually. There always seems to be a duplicate latitude and longitude in entitiesToVisit, but not testLocations when CalculateOptimizedDirections is called.
for(var i = 0; i < toVisit.length; i++){
if(toVisit[i].checked){
var count = parseInt(toVisit[i].id.toString().split(":")[0]);
var tempEntity = entitiesToPickFrom[count];
console.log(entitiesToPickFrom[count]);
tempEntity.compositeAddress = document.getElementById("d"+toVisit[i].id.toString().split(":")[1]).innerHTML.split(">")[1].split("<")[0];
config.searchManager.geocode({
where: tempEntity.compositeAddress,
count: 1,
callback: function (result, pinData) {
var topResult = result.results && result.results[0];
if (topResult) {
pinData.latitude = topResult.location.latitude;
pinData.longitude = topResult.location.longitude;
entitiesToPickFrom[count].latitude = topResult.location.latitude;
entitiesToPickFrom[count].longitude = topResult.location.longitude;
//entitiesToVisit.push(pinData);
//setTimeout(10,function (){console.log("Pin Data");});
//console.log(entitiesToVisit);
document.getElementById("BingMap").style.display = "block";
var wizardDiv = document.getElementById("AddressSelectioWizard");
wizardDiv.style.display = "none";
//possible issue
testLocations.push(new Microsoft.Maps.Location(pinData.latitude,pinData.longitude));
entitiesToVisit.push(pinData);
//testLocations.push(new Microsoft.Maps.Directions.Waypoint(tempEntity.latitude,tempEntity.longitude));
if(entitiesToVisit.length >= checkedCheckers){
CalculateOptimizedDirections();
}
}
else{
//console.log("Nothing gotten");
console.log(result);
//console.log(tempEntity.compositeAddress);
}
},
errorCallback: function (error){console.log(error)},
userData: tempEntity
});
}
}
I've noticed that when I set a timeout to just print text to the console in the middle of the callback, everything works perfectly. This seems to be a bad solution though, is there a better way around it?
The pinData object you are passing into the entitiesToVisit array is the tempEntity value you are passing into the userData option of the geocode call. The issue is likely related to this code:
var count = parseInt(toVisit[i].id.toString().split(":")[0]);
var tempEntity = entitiesToPickFrom[count];
I've been using google's charts API and have reached a dead end. I use the API to query a spreadsheet and return some data. For visualizations I'm using Razorflow - a JS dashboard framework - not Google Charts. Getting the data is pretty straight forward using code like this (this code should work - spreadsheet is public):
function initialize() {
// The URL of the spreadsheet to source data from.
var myKey = "12E2fE8GWuPvXJoiRZgCZUCFhRKlW69uJAm7fch71jhA"
var query = new google.visualization.Query("https://docs.google.com/spreadsheets/d/" + myKey + "/gviz/tq?sheet=Sheet1");
query.setQuery("SELECT A,B,C WHERE A>=1 LIMIT 1");
query.send(function processResponse(response) {
var KPIData = response.getDataTable();
var KPIName = [];
myNumberOfDataColumns = KPIData.getNumberOfColumns(0) - 1;
for (var h = 0; h <= myNumberOfDataColumns ; h++) {
KPIName[h] = KPIData.getColumnLabel(h);
};
});
};
google.charts.setOnLoadCallback(initialize);
The above will create an array holding the column labels for column A,B and C.
Once the data is fetched I want to use the data for my charts. Problem is, I need to have the data ready before I create the charts. One way I have done this, is creating the chart before calling google.charts.setOnLoadCallback(initialize) and then populate the charts with data from inside the callback. Like this:
//create dashboard
StandaloneDashboard(function (db) {
//create chart - or in this case a KPI
var firstKPI = new KPIComponent();
//add the empty component
db.addComponent(firstKPI);
//lock the component and wait for data
firstKPI.lock();
function initializeAndPopulateChart() {
// The URL of the spreadsheet to source data from.
var myKey = "12E2fE8GWuPvXJoiRZgCZUCFhRKlW69uJAm7fch71jhA"
var query = new google.visualization.Query("https://docs.google.com/spreadsheets/d/" + myKey + "/gviz/tq?sheet=Sheet1");
query.setQuery("SELECT A,B,C WHERE A>=1 LIMIT 1");
query.send(function processResponse(response) {
var KPIData = response.getDataTable();
var KPIName = [];
myNumberOfDataColumns = KPIData.getNumberOfColumns(0) - 1;
for (var h = 0; h <= myNumberOfDataColumns ; h++) {
KPIName[h] = KPIData.getColumnLabel(h);
};
//use label for column A as header
firstKPI.setCaption(KPIName[0]);
//Set a value - this would be from the query too
firstKPI.setValue(12);
//unlock the chart
firstKPI.unlock();
});
};
google.charts.setOnLoadCallback(initializeAndPopulateChart);
});
It works but, I would like to separate the chart functions from the data loading. I guess the best solution is to create a promise. That way I could do something like this:
//create dashboard
StandaloneDashboard(function (db) {
function loadData() {
return new Promise (function (resolve,reject){
//get the data, eg. google.charts.setOnLoadCallback(initialize);
})
}
loadData().then(function () {
var firstKPI = new KPIComponent();
firstKPI.setCaption(KPIName[0]);
firstKPI.setValue(12);
db.addComponent(firstKPI);
})
});
As should be quite obvious, I do not fully understand how to use promises. The above does not work but. I have tried lots of different ways but, I do not seem to get any closer to a solution. Am I on the right track in using promises? If so, how should i go about this?
Inside a promise you need to call resolve or reject function when async job is done.
function loadData() {
return new Promise (function (resolve,reject){
query.send(function() {
//...
err ? reject(err) : resolve(someData);
});
})
}
And then you can do
loadData().then(function (someData) {
//here you can get async data
}).catch(function(err){
//here you can get an error
});
});
new Promise(resolve => {
google.charts.setOnLoadCallback(resolve);
}).then(getValues);
I am attempting to pull information from the League of Legends API.
To simplify what I am doing, I am attempting to pull information about a user and their previous matches. The problem that I run into is that when I parse a JSON request, it returns a champion ID rather than their name (Ex: 412 rather than "Thresh").
The only solution I can see for this would be to make another JSON request and parse that data for the champion name. Currently what I have looks like this.
$.getJSON(championMasteryPHP, function (json) {
for (var i = 0; i < 20; i++) {
var champID = json[i].championId;
var championInfo = "http://example.com/champInfo.php?summonerid=" + champID;
$.getJSON(championInfo, function (json2) {
var champName = json2.name;
});
$('#champ').append("<li>"+champID+" - "+champName+"</li>")
}
});
I'm unable to access the champName variable due to it being nested within the second JSON function.
Is there a better way to do this?
$.getJSON(championMasteryPHP, function (json) {
for (var i = 0; i < 20; i++) {
var champID = json[i].championId;
var championInfo = "http://example.com/champInfo.php?summonerid=" + champID;
$.getJSON(championInfo, function (json2) {
var champName = json2.name;
$('#champ').append("<li>"+champID+" - "+champName+"</li>")
});
}
});
Just put it inside the second json request since you need to wait till that request is done anyway.
You should put the append statement in the callback because getJSON is an asynchronous method (does mean the Request is running in the background, and calls your function back when it got a response), so you should wait for the response first then you can append it to #champ :
$.getJSON(championMasteryPHP, function (json) {
for (var i = 0; i < 20; i++) {
var champID = json[i].championId;
var championInfo = "http://example.com/champInfo.php?summonerid=" + champID;
$.getJSON(championInfo, function (json2) {
var champName = json.name;
$('#champ').append("<li>"+champID+" - "+champName+"</li>")
});
}
});
Hope this helps.