How to make For loop wait for Firebase Database response? - javascript

I have a function that receives an object array. The array looks like this:
var receivedObjects = [{nuih329hs: 100}, {8suwhd73h: 500}, {2dsd73ud: 50}, {9u238ds: 200}];
Each key is an ID, and each value is a number.
The objects are in a set order and what I want to do is iterate through the objects and define each value as a variable, which I'll then use when creating a html row.
The problem I'm having is, I have to make a call to a Firebase database inside this iteration code, the result is that while a row is successfully being created for each object value, the value entered into each row is always the same (the last/most recent value in the object array), so in the above case the number 200 is appearing in all four rows.
I think this might be happening because maybe all the iterations are completing before the first Firebase call is even completed, meaning the variable currentValue (which I'm entering into the rows) is set at the last value in the array before the first Firebase call is made.
Note: There is another array (called listOfIds) which contains Firebase IDs, I'm able to successfully use each ID from this array as a variable to use in the Firebase call, var currentID is that variable.
This is the function:
function populateTable(receivedObjects){
var receivedObjectsLength = receivedObjects.length;
// Note: an object array called listOfIds exists here (containing Firebase IDs to be used for querying Firebase), it's referenced below.
for (var i = 0; i < receivedObjectsLength; i++) {
// listOfIds is an Object array
var currentID = Object.keys(listOfIds[i]);
var term = (Object.keys(receivedObjects[i])[0]);
var currentValue = receivedObjects[i][term];
//The following line is showing the correct ID and Value for each iteration:
console.log("The current ID is: "+currentID+" and the current value is: "+currentValue);
firebase.database().ref('/users/' + currentID).once('value').then(function(child) {
// This is where the rows are created, ``currentValue`` is used here, but it's appearing as the same value in every row.
// The next line is showing the correct current ID, but it's showing the currentValue as "200" in every row.
console.log("The current ID is: "+currentID+" and the current value is: "+currentValue);
});
}
}
I'm very confused, because I thought the Javascript iteration code would wait until the data gets returned from the Firebase call, but it seems this isn't the case? How can I change my code so that the line console.log("The current ID is: "+currentID+" and the current value is: "+currentValue); inside the Firebase call success function will show the correct currentValue?

Instead of making the loop wait, you can use a function to put currentID and currentValue into their own scope so that they don't get overwritten with each iteration:
function getUser(currentID, currentValue) {
return firebase.database().ref('/users/' + currentID).once('value').then(function(child) {
console.log("The current ID is: "+currentID+" and the current value is: "+currentValue);
});
}
In your loop:
for (var i = 0; i < receivedObjectsLength; i++) {
...
getUser(currentID, currentValue)
}

Related

chrome.storage.local.get an object and iterating through obj keys

I'm having trouble using the chrome.storage.local.get feature. I have all working, except I don't understand how to handle the .get function.
I want to copy an object by clicking a button, from one chrome tab to another one. I have a loop to get my Object keys, and I can save it successfuly in chrome local storage by:
chrome.storage.local.set({
'copiedObj': obj
});
but, when trying to use the .get function, I can't specify the key number I want. I need the key number because the keys and their values changes every time because i'm copying data from different pages.
$('#targetInfo').click(function(){
console.log('context 2 received');
var storage = chrome.storage.local.get('copiedObj', function (items) {
console.log(items);
});
});
this gets me the full object keys and values, but when trying to change it to get the first key (changing the console.log inside the script to items[0], I get an undefined error message in the console.
I want it to iterate through all the keys, and assign key to a variable, value to a variable and execute a function. For each key by it's time. I've written a detailed explanation inside the code:
//Get the DOM input field
var specific_name = document.getElementById('new_specific_name');
var specific_value = document.getElementById('new_specific_value');
//Start loop, from 0 to the last object keys
for (i=0;i<items.length;i++) {
//Set specific_name value to the first key inside the object
specific_name.value = XXX[i];
//Set specific_value value to the matching key value inside the object
specific_value.value = ZZZ[i];
//Execute this function
add_new_specific();
}
(minimal solution, pending clarification) As mentioned before,
var storage = chrome.storage.local.get('copiedObj', function (items) {
console.log(items);
});
should be
var storage = chrome.storage.local.get('copiedObj', function (result) {
console.log(result.items);
console.log(result.items[0]); // if items is an array...
});
To loop through an object, see https://stackoverflow.com/a/18202926/1587329. You need to do this all inside the anonymous function in .get.

Why are my defined strings printing to the console as undefined?

I've written a very simple application that pulls all the tracks off of a spotify playlist. First, it gets the track JSON objects using an AJAX request and puts them in an array called tracks[].
Next, I iterate through this array, and in each iteration I pull out the strings for the name of the song and the artist for each track. I put these in a concentrated string. This string is then stored in a new array called complete. This is done for each track.
Essentially, complete is an array that looks something like
[
"Sympathy for The Devil, The Rolling Stones",
"Come Together, The Beatles",
...
]
At this point I have a strange issue. According to chrome's debugger, the array complete has all of the correct values inside of it. However, when I attempt to console.log any element of complete I end up with the value 'undefined'. Stranger yet, if I console.log the expression I use to get the two strings and stitch them it prints the correct value.
Why does this happen and how can I fix it so it can console.log the correct values
Here is the code that iterates through stored JSON objects, stores them in new array, and prints:
function print() {
for(var x = 0; x < 4; x++) {
for(var n = 0; n < 100; n++) {
try {
//this logs correct
console.log(tracks[x].items[n].track.name + " " + tracks[x].items[n].track.artists[0].name);
//these have correct value
var deb = (tracks[x].items[n].track.name + " " + tracks[x].items[n].track.artists[0].name);
complete[z] = deb;
z++;
console.log(deb); //throws no error, prints fine
console.log(complete[z]); //prints undefined
} catch(err) {
//no exception ever thrown
}
}
}
}
I don't do too much JS, sorry if I'm missing something obvious.
Right after assigning complete[z], you are incrementing z. When printing, you are not accessing complete[z] anymore, but complete[z+1] which is not yet defined. Try moving the incrementation after the console.log command.
Well, in your example above, you've incremented z (which I assume was defined earlier somewhere?) after setting the value on your array. So when setting the value on the array, you've set it to complete[5] (or whatever) but you're logging complete[6] which is most likely outside the range of your array.

Display different text everytime you call onclick function

I have this code where I am trying to pull users from my firebase database:
function pullFromDB(){
usersRef.on('value', function(snapshot) {
function next_user(){
for (user in snapshot.val().users){
document.getElementById("users").innerHTML = user
}
}
So far it will return just one of the users as I believe it must be going through the whole users array and then putting the last name into the "users" element.
How can I iterate to the next item in the array and display a different name every time I click a button?
I have no idea what's return from DB but first try the console.log() to see what's the type of result. This will give you the answer if you are working on array or other data type.
Then if it's an array, your code:
for (user in snapshot.val().users){
document.getElementById("users").innerHTML = user
}
It iterates the whole array and .innerHTML puts user string as the last iteration. If you want to change it each time you click a button you need a variable that will contain inteager of index that's currently aplied to id="users".
Then for each click you need to increase the variable by one.
Here is an example of working with arrays in JS. http://www.w3schools.com/js/tryit.asp?filename=tryjs_array_element
Your are iterating through the whole array and only the last one is possible shown. You just need to assign an index and use it.
I don't know your remaining code, but by the given information, this should to the trick:
i = 0;
function pullFromDB(){
usersRef.on('value', function(snapshot) {
lastnames = Object.keys(snapshot.val().users); // get all keys, in your case the last names
function next_user(){
user = lastnames[i];
document.getElementById("users").innerHTML = user;
i++; // increase after click
// when you want to access more information of the user, you can do it like this (as an example)
// user_id = snapshot.val().users[user].id;
}

Saving an array globally

What I'm trying to achieve
I have a spreadsheet with 2 sheets A & B.
A has 2 columns - Name, Amount (Master List)
B has 4 columns - Name, Amount, X, Y (Transaction List)
Name column of Sheet B references Name column of Sheet A for data. Whenever a name is selected, I want to populate Amount column in B with Amount in column of sheet A as a placeholder which users can override. For this, I plan to load the Sheet A data in an array (available Globally) so that in onEdit(e) I can refer that array instead of accessing Sheet B.
But the options I could find - CacheService and PropertyService - save only string values. But I want to have:
var myGlobalArray = [];
function on init(){
//iterate and fill the array such that it has following output
//myGlobalArray[Name1] = 1
//myGlobalArray[Name2] = 2
//myGlobalArray[Name3] = 3
}
function onEdit(e){
//if selected value is name1, populate myGolbalArray[Name1] value in Amount
}
Question
Where & how to define myGlobalArray?
I tried to use cache service with JSON.Stringify and JSON.parse but the array is empty in onEdit.
Each call to your script creates a new instance of your script with its own unique globals. Every time you call a script you will actually find a global "this" for that specific instance. You are correct to look at PropertyService as a persistent way to save data.
Right off I See that your globalArray is not set up right:
var myGlobalArray = [];
needs to be
var myGlobalArray = {};
myGlobalArray['name1'] = 1
myGlobalArray['name2'] = 2
myGlobalArray['name3'] = 3
//myGlobalArray = {name3=3.0, name1=1.0, name2=2.0}
var stringArray = JSON.stringify(myGlobalArray)
//{"name1":1,"name2":2,"name3":3};
Now that can be saved to and read from the property store.
PropertiesService.getScriptProperties().setProperty("NameArray", stringArray);
stringArray = PropertiesService.getScriptProperties().getProperty("NameArray");
myGlobalArray = JSON.parse(stringArray);
Logger.log(myGlobalArray['name1']) // returns 1
It's true that CacheService and PropertyService save only string values, but you can store any scalar data by using the JSON utilities JSON.stringify() and JSON.parse().
// Save an array in cache service
CacheService.getPublicCache()
.put("myGlobalArray", JSON.stringify(myGlobalArray));
// Retrieve an array from property service
var myGlobalArray = JSON.parse( CacheService.getPublicCache()
.get("myGlobalArray") );
// Save an array in property service
PropertiesService.getDocumentProperties()
.setProperty("myGlobalArray", JSON.stringify(myGlobalArray));
// Retrieve an array from property service
var myGlobalArray = JSON.parse( PropertiesService.getDocumentProperties()
.getProperty("myGlobalArray") );
When a variable is called "Global", we are referring to its scope, saying that it is available to all code within the same module. (You can read more about scope in What is the scope of variables in JavaScript?)
But since you're looking at CacheService and PropertyService, you already know that scope is only part of the problem. Each time that onEdit() is invoked, it will be running in a new execution instance on one of Google's servers. A value that had been in a global variable in a previous instance will not be available to this new instance. Therefore, we need to populate our "global variable" in each new invocation of our script.
An elegant way to reference global variables is as names properties of the special this object. For example, every function in our script can refer to this.myGlobalArray.1
You can adapt the getRssFeed() example from the Class Cache documentation into get_myGlobalArray(), say. Then your onEdit() trigger needs only to call that first to make sure that this.myGlobalArray contains the relevant array data.
function onEdit(e){
get_myGlobalArray();
//if selected value is name1, populate myGlobalArray[Name1] value in Amount
...
sheet.getRange(e.range.getRow(),2).setValue(myGlobalArray[e.value]);
}
/**
* Ensure the global variable "myGlobalArray" is defined and contains the
* values of column A in SheetA as an array.
*/
function get_myGlobalArray() {
if (typeof this.myGlobalArray == 'undefined') {
// Global variable doesn't exist, so need to populate it
// First, check for cached value
var cache = CacheService.getPublicCache();
var cached = cache.get("myGlobalArray");
if (cached) {
// We have a cached value, so parse it and store in global
this.myGlobalArray = JSON.parse(cached);
}
else {
// No value in the cache, so load it from spreadsheet
var data = SpreadsheetApp.getActive().getSheetByName("Sheet A").getDataRange().getValues();
this.myGlobalArray = {};
for (var row=0; row<data.length; row++) {
this.myGlobalArray[data[row][0]] = data[row][6];
}
// Stringify and store the global into the cache
cache.put("myGlobalArray", JSON.stringify(this.myGlobalArray));
}
}
}
Edit: Associative Array
In the comment within onEdit(), it's indicated:
//if selected value is name1, populate myGolbalArray[Name1] value in Amount
This implies that myGlobalArray is an associative array, where the index is a non-integer value. This requirement is now reflected in the way that this.myGlobalArray gets populated when read from the spreadsheet.
for (var row=0; row<data.length; row++) {
this.myGlobalArray[data[row][0]] = data[row][6];
// ^^^^^^^^^^^^ ^^^^^^^^^^^^
// Name ---------------/ /
// Amount ------------------------/
}
Much has been written about the different flavours of Javascript arrays, for instance Javascript Associative Arrays Demystified.
1 Actually, only functions with global scope would understand this to mean "global to the script". Functions that are contained inside objects would interpret this to mean their host object only. But that's a story for another day.

Store function result to variable

How can I store the result of a function to a variable?
In navigating an object that contains an array, I am looking for one value, value1, once that is found I want to get the value of one of its properties, property1.
The code I am using below is an example and is incorrect.
function loadSets(id){
clworks.containers.find(function(i){
return i.get('property1')===id;
});
}
My intent is to navigate the object below:
clworks.containers
containers[0].property0.id
containers[1].property1.id
I am trying to determine how to find which item in the array has a property value equal to the id used in the function and then store it as a variable.
Simply:
var myVar = loadSets(id);
EDIT
Ok, as much as I understand your question now, your situation is the following:
You have an array containing objects called containers;
You want to iterate through this array, looking for the property id of the property property1 which equals the one specified in the function called loadSets(id);
Once found, store the object with the requested id in a variable.
Am I right?
If so, this should solve your problem:
// This function iterates through your array and returns the object
// with the property id of property1 matching the argument id
function loadSets( id ) {
for(i=0; i < containers.length; i++) {
if( containers[i].property1.id === id )
return containers[i];
}
return false;
}
After this you just need to do what I said in the first answer to your question, triggering it however you want. I put up a quick JSBin for you. Try to put 10, or 20, in the input field and then hitting the find button; it will return the object you are looking for. Try putting any other number, it will return a Not found.
Currently your function, loadSets, isn't actually returning anything and so you cannot store a result from it.
Try:
function loadSets(id){
return Base.containers.find(function(i){
return i.get('entityid')===id;
});
}
And to get a result into a variable:
var result = loadSets(id);

Categories

Resources