ForEach only looping through first item on DataSnapshot - javascript

I´m trying to loop through the content of a DataSnapshot and then depending on a condition do some work FOR EACH one of the elements but currently, the ForEach is only doing the work in the first item. The "serverStatus" sometimes is waiting and sometimes in "onCall". When the first item is "onCall" does not go through the rest of the items as I think is supposed to do. Below a snapchot of where I get the information from:
And here is my function:
exports.manageCallRequests = functions.database.ref('/resquests/{userId}').onCreate((snap, context) => {
const event = snap.val();
console.log("function manageCallRequests is being called")
var rootPath = admin.database().ref();
var userOnCall = context.params.userId;
var serversRef = rootPath.child('servers');
var callRequest = event;
var userTime = callRequest["time"];
var waiting= "waiting";
//We first get all the servers in ascending order depending on the last time they were used
var serversSorted = serversRef.orderByChild('lastTimeUsed')
//Gets the children on the "serversSorted" Query
return serversSorted.once("value").then(allServers =>{
//Checks if there is any child
if(allServers.hasChildren()){
allServers.forEach(async function(server) {
//we extract the value from the server variable, this contains all the information
//about each one of the servers we have
var serverInfo = server.val();
var serverKey = server.key;
var serverNumber = serverInfo["serverNumber"];
var serverStatus = serverInfo["serverStatus"];
console.log("server status "+serverStatus)
if(serverStatus === waiting){
const setCallRequest = await serversRef.child(serverKey).child("current").child("callRequest").set(callRequest);
const removeUserOnCall = await rootPath.child("resquests").child(userOnCall).remove();
const setServerStatus = await serversRef.child(serverKey).child("serverStatus").set("onCall");
}
});
}else{
console.log("No servers available")
}
});
});

I had the same behavior because my cloud function was exited before that all iterations were executed in the forEach loop.I get rid of it using this snippet of code:
for (const doc of querySnapshot.docs) {
// Do wathever you want
// for instance:
await doc.ref.update(newData);
}

I found 2 ways of getting this done. The first one is useful if we have a DataSnapshot without any OrderBy* call, in this case, would be:
var allServers = await serversRef.once("value");
for (let serverKey of Object.keys(allServers.val())){
var server = allServers[serverKey];
//Do some work
}
We need to first get the keys of the object to then be able to extract it from within the for loop, as explained here otherwise we´ll get a "TypeError: 'x' is not iterable"
Now the problem with this particular case is that a have a DataSnapshot that was previously sorted at var serversSorted = serversRef.orderByChild('lastTimeUsed') so when we call Object.keys(allServers.val()) the value returned is no longer sorted and that´s where forEach() comes in handy. It guarantees the children of a DataSnapshot will be iterated in their query order as explained here however for some reasons when doing some async work within the forEach loop this seems not to work, that´s why I had to do this:
var serversSorted = serversRef.orderByChild('lastTimeUsed')
var allServers = await serversSorted.once("value");
//Checks if there is any children
if (allServers.hasChildren()) {
//if there is iterate through the event that was passed in containing all
// the servers
var alreadyOnCall = false;
var arrayOfServers = []
var arrayOfKeys = []
allServers.forEach(function(individualServer){
arrayOfKeys.push(individualServer.key)
arrayOfServers.push(individualServer)
})
for (var serveIndex = 0; serveIndex < arrayOfServers.length;serveIndex++){
var serverObj = arrayOfServers[serveIndex]
var serverObject = serverObj.val()
var serverKey = arrayOfKeys[serveIndex]
var serverStatus = serverObject["serverStatus"];
var serverNumber = serverObject["serverNumber"];
console.log("server info "+serverStatus+" "+serverKey);
if (serverStatus === waiting && alreadyOnCall === false) {
const setCallRequest = await serversRef.child(serverKey).child("current").child("callRequest").set(callRequest);
const removeUserOnCall = await rootPath.child("resquests").child(userOnCall).remove();
const setServerStatus = await serversRef.child(serverKey).child("serverStatus").set("onCall");
alreadyOnCall= true
console.log("Call properly set");
}
}
}

Related

JavaScript - Issues recovering a map in an object after being saved in localStorage

I've been dealing with this for some time. I've a list of sections in which the user checks some checkboxes and that is sent to the server via AJAX. However, since the user can return to previous sections, I'm using some objects of mine to store some things the user has done (if he/she already finished working in that section, which checkboxes checked, etc). I'm doing this to not overload the database and only send new requests to store information if the user effectively changes a previous checkbox, not if he just starts clicking "Save" randomly. I'm using objects to see the sections of the page, and storing the previous state of the checkboxes in a Map. Here's my "supervisor":
function Supervisor(id) {
this.id = id;
this.verif = null;
this.selections = new Map();
var children = $("#ContentPlaceHolder1_checkboxes_div_" + id).children().length;
for (var i = 0; i < children; i++) {
if (i % 2 == 0) {
var checkbox = $("#ContentPlaceHolder1_checkboxes_div_" + id).children()[i];
var idCheck = checkbox.id.split("_")[2];
this.selections.set(idCheck, false);
}
}
console.log("Length " + this.selections.size);
this.change = false;
}
The console.log gives me the expected output, so I assume my Map is created and initialized correctly. Since the session of the user can expire before he finishes his work, or he can close his browser by accident, I'm storing this object using local storage, so I can change the page accordingly to what he has done should anything happen. Here are my functions:
function setObj(id, supervisor) {
localStorage.setItem(id, JSON.stringify(supervisor));
}
function getObj(key) {
var supervisor = JSON.parse(localStorage.getItem(key));
return supervisor;
}
So, I'm trying to add to the record whenever an user clicks in a checkbox. And this is where the problem happens. Here's the function:
function checkboxClicked(idCbx) {
var idSection = $("#ContentPlaceHolder1_hdnActualField").val();
var supervisor = getObj(idSection);
console.log(typeof (supervisor)); //Returns object, everythings fine
console.log(typeof (supervisor.change)); //Returns boolean
supervisor.change = true;
var idCheck = idCbx.split("_")[2]; //I just want a part of the name
console.log(typeof(supervisor.selections)); //Prints object
console.log("Length " + supervisor.selections.size); //Undefined!
supervisor.selections.set(idCheck, true); //Error! Note: The true is just for testing purposes
setObj(idSection, supervisor);
}
What am I doing wrong? Thanks!
Please look at this example, I removed the jquery id discovery for clarity. You'll need to adapt this to meet your needs but it should get you mostly there.
const mapToJSON = (map) => [...map];
const mapFromJSON = (json) => new Map(json);
function Supervisor(id) {
this.id = id;
this.verif = null;
this.selections = new Map();
this.change = false;
this.selections.set('blah', 'hello');
}
Supervisor.from = function (data) {
const id = data.id;
const supervisor = new Supervisor(id);
supervisor.verif = data.verif;
supervisor.selections = new Map(data.selections);
return supervisor;
};
Supervisor.prototype.toJSON = function() {
return {
id: this.id,
verif: this.verif,
selections: mapToJSON(this.selections)
}
}
const expected = new Supervisor(1);
console.log(expected);
const json = JSON.stringify(expected);
const actual = Supervisor.from(JSON.parse(json));
console.log(actual);
If you cant use the spread operation in 'mapToJSON' you could loop and push.
const mapToJSON = (map) => {
const result = [];
for (let entry of map.entries()) {
result.push(entry);
}
return result;
}
Really the only thing id change is have the constructor do less, just accept values, assign with minimal fiddling, and have a factory query the dom and populate the constructor with values. Maybe something like fromDOM() or something. This will make Supervisor more flexible and easier to test.
function Supervisor(options) {
this.id = options.id;
this.verif = null;
this.selections = options.selections || new Map();
this.change = false;
}
Supervisor.fromDOM = function(id) {
const selections = new Map();
const children = $("#ContentPlaceHolder1_checkboxes_div_" + id).children();
for (var i = 0; i < children.length; i++) {
if (i % 2 == 0) {
var checkbox = children[i];
var idCheck = checkbox.id.split("_")[2];
selections.set(idCheck, false);
}
}
return new Supervisor({ id: id, selections: selections });
};
console.log(Supervisor.fromDOM(2));
You can keep going and have another method that tries to parse a Supervisor from localStorageand default to the dom based factory if the localStorage one returns null.

forEach with single document firebase queries in client side?

I have different document id for every loop and when I query inside the forEach loop query is working but not pushing the obj into the array
function getAllDonations() {
donations = [];
const user_session_data = sessionStorage.getItem('LoginInfo');
const parse_user_login_data = JSON.parse(user_session_data);
let TABLE_NAME = "donation_favourites";
let get_requests_qry = App.db.collection(TABLE_NAME);
get_requests_qry.where('user_id', '==', parse_user_login_data.user_id).get().then(snapshot => {
let changes = snapshot.docChanges();
changes.forEach(change => {
var one_item = change.doc.data();
let TABLE_NAME1 = "donation_requests";
let get_requests_qry1 = App.db.collection(TABLE_NAME1);
get_requests_qry1.doc(one_item.donationId).get().then(snapshot => {
donations.push(snapshot.data())
});
});
console.log("checking the data",donations.length) //this length is not coming
});
}
If you want to read the files in use forloop but it is not recommended for large loop for small loop it is ok
if you want to read files parallel use forEach
You can also do it with async and await instead forLoop
await Promise.all(changes.map(async (change) => {
var one_item = change.doc.data()
let TABLE_NAME1 = "donation_requests";
let get_requests_qry1 = App.db.collection(TABLE_NAME1);
var snapshot1 = await get_requests_qry1.doc(one_item.donationId).get()
donations.push(snapshot1.data())
}));

Update an Object in indexed db by ignoring a value

I have written the below code
updatePublication(projectName, publicationId, publicationObj, callback) {
let self = this;
this.initDatabase(function (db) {
let tx = self.db.transaction(self.PUBLICATIONS, self.READ_WRITE);
let store = tx.objectStore(self.PUBLICATIONS);
let index = store.index(self.PROJECT_NAME);
let request3 = index.openCursor(IDBKeyRange.only(projectName));
console.log("hrere");
request3.onsuccess = function () {
let cursor = request3.result;
if (cursor) {
let updateObject = cursor.value;
if (updateObject.publicationID == publicationId) {
updateObject.publicationObject = publicationObj;
cursor.update(updateObject);
callback(publicationId);
}
cursor.continue();
} else {
callback(publicationId);
}
};
});
}
But this give error:
I checked the cause of error. It is beacuse , publicationObj which is passed has an object named _requestObjectBuilder which is of the type Subscriber.
used somewhere in the code like this:
_requestObjectBuilder = interval(1000).pipe(tap(() => {}));
Is there any way i can modify my updatePublication code to ignore this value?
Does indexed db support a query for ignoring a value and saving the data?
Note: If i set publicationObj._requestObjectBuilder = undefined, the data gets saved to indexedDB. But this breaks the functionality where _requestObjectBuilder is used.
Fixed the issue by cloning the object and setting it to undefined
let clonedObject = Object.assign({}, publicationObject);
clonedObject._requestObjectBuilder = undefined;
Now i am updating the clonedObject

Return list of Objects with Node js

I recently started development of a Node js application and it uses Selenium in a controller to fetch list of items from a web page and I want to return the fetched list of items as a JSON response.
exports.read_all_products = function (req, res) {
var driver = new webdriver.Builder().forBrowser('phantomjs').build();
driver.get('https://www.test.com/products?PC=' +req.params.category);
driver.wait(until.elementLocated(By.className('product-slide-all')), 20000, 'Could not locate the element within the time specified');
driver.findElements(By.className("product-slide-all")).then(function (elements) {
var arr = [];
elements.forEach(function (element) {
element.getAttribute("innerHTML").then(function (html) {
const dom = new JSDOM(html);
var obj = new Object();
obj.product_name = dom.window.document.querySelector(".product-name").textContent;
obj.product_code = dom.window.document.querySelector(".product-code").textContent;
obj.price = dom.window.document.querySelector(".product-price").textContent;
arr.push(obj);
});
});
res.json(arr);
});
}
Issue is I am always getting an empty JSON response even though items were added to the array. I want to know the proper way of handling this scenario.
Thanks.
It looks like the issue is because Selenium is running an async process, thus the response immediately returns because there is nothing blocking it.
findElements returns a Promise which you need to return the response from.
Take a look at How do I return the response from an asynchronous call?
Finally I was able to get it work with the help of webdriver.promise.map.
Moved web driver HTML extraction to separate function.
var findItems = function (category) {
var driver = new webdriver.Builder().forBrowser('phantomjs').build();
var map = webdriver.promise.map;
driver.get('https://www.test.com?PC=' + category);
driver.wait(until.elementLocated(By.className('product-slide-all')), 30000, 'Could not locate the element within the time specified');
var elems = driver.findElements(By.className("product-slide-all"));
return map(elems, elem => elem.getAttribute("innerHTML")).then(titles => {
return titles;
});
}
then call it from response handling function like bellow,
exports.read_all_products = function (req, res) {
findItems(req.params.category).then(function (html) {
var value;
var arr = [];
Object.keys(html).forEach(function (key) {
value = html[key];
const dom = new JSDOM(value);
var obj = new Object();
obj.product_name = dom.window.document.querySelector(".product-name").textContent;
obj.product_code = dom.window.document.querySelector(".product-code").textContent;
obj.price = dom.window.document.querySelector(".product-price").textContent;
arr.push(obj);
});
res.json(arr);
})
};
it's described in this stack overflow answers.

return from JS function

basic JS question, please go easy on me I'm a newb :)
I pass 2 variables to the findRelatedRecords function which queries other related tables and assembles an Array of Objects, called data. Since findRelatedRecords has so many inner functions, I'm having a hard time getting the data Array out of the function.
As it currently is, I call showWin inside findRelatedRecords, but I'd like to change it so that I can get data Array directly out of findRelatedRecords, and not jump to showWin
function findRelatedRecords(features,evtObj){
//first relationship query to find related branches
var selFeat = features
var featObjId = selFeat[0].attributes.OBJECTID_1
var relatedBranch = new esri.tasks.RelationshipQuery();
relatedBranch.outFields = ["*"];
relatedBranch.relationshipId = 1; //fac -to- Branch
relatedBranch.objectIds = [featObjId];
facSel.queryRelatedFeatures(relatedBranch, function(relatedBranches) {
var branchFound = false;
if(relatedBranches.hasOwnProperty(featObjId) == true){
branchFound = true;
var branchSet = relatedBranches[featObjId]
var cmdBranch = dojo.map(branchSet.features, function(feature){
return feature.attributes;
})
}
//regardless of whether a branch is found or not, we have to run the cmdMain relationship query
//the parent is still fac, no advantage of the parent being branch since cmcMain query has to be run regardless
//fac - branch - cmdMain - cmdSub <--sometimes
//fac - cmdMain - cmdSub <-- sometimes
//second relationship query to find related cmdMains
var relatedQuery = new esri.tasks.RelationshipQuery();
relatedQuery.outFields = ["*"];
relatedQuery.relationshipId = 0; //fac -to- cmdMain
relatedQuery.objectIds = [featObjId];
//rather then listen for "OnSelectionComplete" we are using the queryRelatedFeatures callback function
facSel.queryRelatedFeatures(relatedQuery, function(relatedRecords) {
var data = []
//if any cmdMain records were found, relatedRecords object will have a property = to the OBJECTID of the clicked feature
//i.e. if cmdMain records are found, true will be returned; and continue with finding cmdSub records
if(relatedRecords.hasOwnProperty(featObjId) == true){
var fset = relatedRecords[featObjId]
var cmdMain = dojo.map(fset.features, function(feature) {
return feature.attributes;
})
//we need to fill an array with the objectids of the returned cmdMain records
//the length of this list == total number of mainCmd records returned for the clicked facility
objs = []
for (var k in cmdMain){
var o = cmdMain[k];
objs.push(o.OBJECTID)
}
//third relationship query to find records related to cmdMain (cmdSub)
var subQuery = new esri.tasks.RelationshipQuery();
subQuery.outFields = ["*"];
subQuery.relationshipId = 2;
subQuery.objectIds = [objs]
subTbl.queryRelatedFeatures(subQuery, function (subRecords){
//subRecords is an object where each property is the objectid of a cmdMain record
//if a cmdRecord objectid is present in subRecords property, cmdMain has sub records
//we no longer need these objectids, so we'll remove them and put the array into cmdsub
var cmdSub = []
for (id in subRecords){
dojo.forEach(subRecords[id].features, function(rec){
cmdSub.push(rec.attributes)
})
}
var j = cmdSub.length;
var p;
var sub_key;
var obj;
if (branchFound == true){
var p1 = "branch";
obj1 = {};
obj1[p1] = [cmdBranch[0].Branches]
data.push(obj1)
}
for (var i=0, iLen = cmdMain.length; i<iLen; i++) {
p = cmdMain[i].ASGMT_Name
obj = {};
obj[p] = [];
sub_key = cmdMain[i].sub_key;
for (var j=0, jLen=cmdSub.length; j<jLen; j++) {
if (cmdSub[j].sub_key == sub_key) {
obj[p].push(cmdSub[j].Long_Name);
}
}
data.push(obj);
}
showWin(data,evtObj) <---this would go away
})
}
//no returned cmdRecords; cmdData not available
else{
p = "No Data Available"
obj = {}
obj[p] = []
data.push(obj)
}
showWin(data,evtObj) <--this would go away
})
})
}
I'd like to have access to data array simply by calling
function findRelatedRecords(feature,evt){
//code pasted above
}
function newfunct(){
var newData = findRelatedRecords(feature,evt)
console.log(newData)
}
is this possible?
thanks!
Edit
Little more explanation.....
I'm connecting an Object event Listener to a Function like so:
function b (input){
dojo.connect(obj, "onQueryRelatedFeaturesComplete", getData);
obj.queryRelatedFeatures(input);
console.log(arr) //<----this doesn't work
}
function getData(relatedFeatData){
var arr = [];
//populate arr
return arr;
}
So when obj.QueryRelatedFeatures() is complete, getData fires; this part works fine, but how to I access arr from function b ?
Post Edit Update:
Due to the way that this event is being hooked up you can't simple return data from it. Returning will just let Dojo call to the next method that is hooked up to onSelectionComplete.
When init runs it is long before findRelatedRecords will ever be executed/fired by the onSelectionComplete event of the well, which is why you were seeing undefined/null values. The only way to work with this sort of system is to either 1) call off to a method like you're already doing or 2) fire off a custom event/message (technically it's still just calling off to a method).
If you want to make this method easier to work with you should refactor/extract snippets of it to make it a smaller function but contained in many functions. Also, changing it to have only one exit point at the end of the findRelatedRecords method will help. The function defined inside of subTbl.queryRelatedFeatures() would be a great place to start.
Sorry, you're kind of limited by what Dojo gives you in this case.
Pre Edit Answer:
Just return your data out of it. Everywhere where there is a showWin call just use this return.
return {
data: data,
evtObj: evtObj
}
Then your newfunct would look like this.
function newfunct(){
var newData = findRelatedRecords(feature,evt);
console.log(newData);
console.log(newData.data);
console.log(newData.evtObj);
}
If you only need that "data" object, then change your return to just return data;.
Also, start using semicolons to terminate statements.

Categories

Resources