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'));
});
});
})
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'm using AngularJS to build a site where one of the functions is to present Billboard(it's a music chart) listings for a specified date.
I want to present the songs in order, together with an image of the song.
First I'm calling this API:
http://billboard.modulo.site/
where I give a date and get a response of the top 10 songs for that date and data about each song.
The response from the Billboard API also includes a spotify id and I want to use that ID and call the Spotify Web API to get an image of that song, to complement the information I present about each song.
This is how it looks like in my controller:
var spotifyID = [];
$scope.spotifyImg = [];
musicService.getBillboard($scope.date).then(function(data){ //Response is top 10 songs for given date
$scope.status = "";
$scope.songlist = data;
for (var i = 0; i < data.length; i++) {
spotifyID[i] = data[i].spotify_id; //data[i].spotify_id returns the ID of the track, as given by the billboard API
}
$scope.getImages();
});
$scope.getImages = function() {
for (var i = 0; i < spotifyID.length; i++) {
if(spotifyID[i] !== null) {
musicService.getSpotify(spotifyID[i]).then(function(data){
$scope.spotifyImg[i] = data.album.images[0].url; //returns the appropriate image from the Spotify Web API
});
}
}
console.log($scope.spotifyImg);
}
And in my view it would look something like this:
<div ng-repeat = "song in songlist">
<div>{{ song.rank }}</div>
<div>
<img ng-src=" {{ spotifyImg[$index] }}"/>
</div>
</div>
However, it does not work.
When I'm checking the $scope.spotifyImg array in the console, it is of length 11 and only has one element in index 10 and that is the image of the last song(that is the 10th song).
I'm a bit confused as to why the $scope.spotifyImg array only contains one element in index 10. Also why is the array of length 11 when the spotifyID is of length 10?
Any ideas of how I could solve this?
The problem is that getSpotify is run asynchronous, when the responses to these calls come in, i is probably set to spotifyID.length - 1 which means that all callback functions set the $scope.spotifyImg[spotifyID.length - 1] element.
Try this:
$scope.spotifyImg = [];
musicService.getBillboard($scope.date).then(function(data){
$scope.status = "";
$scope.songlist = data;
for (var i = 0; i < data.length; i++) {
$scope.getImage(data[i].spotify_id, i);
}
});
$scope.getImage = function(id, index) {
musicService.getSpotify(id).then(function(data){
$scope.spotifyImg[index] = data.album.images[0].url;
});
}
create separate function and put the content of for loop inside that function and call that function inside the loop
$scope.getImages = function() {
for (var i = 0; i < spotifyID.length; i++) {
if (spotifyID[i] !== null) {
sampleFunc(i);
}
}
}
function sampleFunc(i) {
musicService.getSpotify(spotifyID[i]).then(function(data) {
$scope.spotifyImg[i] = data.album.images[0].url; //returns the appropriate image from the Spotify Web API
});
}
I think reason you get only last index of an array is when you are calling promise inside loop, loop does't wait until the promise returns. it just keep executing.At the time promise returns loop is executed and it;s getting last index of the array. That's why you need to separately call the promise from for loop
You can use IIFE
(function(i){
musicService.getSpotify(spotifyID[i]).then(function (data) {
$scope.spotifyImg[i] = data.album.images[0].url;
});
})(i)
So,your getImages function should be like this.
$scope.getImages = function () {
for (var i = 0; i < spotifyID.length; i++) {
if (spotifyID[i] !== null) {
(function(i){
musicService.getSpotify(spotifyID[i]).then(function (data) {
$scope.spotifyImg[i] = data.album.images[0].url;
});
})(i)
}
}
}
Try using this code
Js code
var spotifyID = [];
$scope.spotifyImg = [];
musicService.getBillboard($scope.date).then(function(data) { //Response is top 10 songs for given date
$scope.status = "";
$scope.songlist = data;
for (var i = 0; i < data.length; i++) {
spotifyID[i] = data[i].spotify_id; //data[i].spotify_id returns the ID of the track, as given by the billboard API
}
$scope.getImages(0);
});
$scope.getImages = function(index) {
if (index == spotifyID.length) {
return;
}
musicService.getSpotify(spotifyID[index]).then(function(data) {
$scope.spotifyImg[index] = data.album.images[0].url; //returns the appropriate image from the Spotify Web API
// call recursive here
$scope.getImages(index++);
});
}
}
call your getimages function recursively so that will add you images in array.
I have an CSV parsing function in JavaScript which gets data (movie names) from CSV and gets data using Ajax call in loop.
movies = new Array();
for (var i = 1; i < allData.length; i++) {
var mName = allData[i][0];
var mPath = allData[i][1];
// console.log(decodeURIComponent(mName));
$.get(apiCall, function showData(data) {
if (data) {
mData = data.results;
if (mData.length > 1) {
var urlData = new URLSearchParams(this.url);
var movie_name = urlData.get('query');
movies.push(movie_name);
}
}
})
}
If data got more then one record for any movie it will save it as a conflict in array.
Problem is, I can access movies array inside inner if (but it is in iteration so I can't use that) and at loop end it is not accessible. How can I access that?
You should not make api calls inside a for loop. Instead do this,
movies = new Array();
function makeApiCallForEntireArray(index, arr, cb){
if(arr.length == index){
cb(true);
return;
}
$.get(apiCall, function showData(data) {
if (data) {
mData = data.results;
if (mData.length > 1) {
var urlData = new URLSearchParams(this.url);
var movie_name = urlData.get('query');
movies.push(movie_name);
}
}
makeApiCallForEntireArray(index+1, arr, cb);
})
}
makeApiCallForEntireArray(0, allData, function(){
//api calls finished
//movie accesssible here with all the data
});
You will not be able to access the content added in movies array at the end of the loop because ajax requests are still in progress. You need to do this some other way so that you can be sure that its end of asynch ajax calls.
Im going to use the answer of #Jaromanda X in my question here Can't get the summation in for loop
Promise.all(allData.map(function(d) {
return $.get(apiCall, function showData(data){
return data.results;
});
})).then(function(res) {
//push your movies here...the result of your apiCall is inside the res variable
});
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);
});
}
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.