nodejs loop in asynch - node.js

Im sending user information to user when he signs in.
var userstuff00 = findSub(user['id']);
userstuff00.then(function(sub){
for(var i in sub){
var userstuff01 = findSub(sub[i.toString()]['id']);
userstuff01.then(function(sub2){
for(var i2 in sub2){
//here i is the last object in sub but i2 is for the first i
console.log(sub[i.toString()]);
console.log(sub2[i2.toString()]);
}
});
}
});
and this is findSub function where usercollection is mongodb table:
function findSub(stuffCode){
var tempMembers = usercollection.find({'stuff ': stuffCode});
return tempMembers;
}
this is user object :
user{
id,
name,
stuff,
subMember[] //list of users where their stuff equals to this users id
}
I want to add every sub2 in that users subMember but i's id is not equal to i2's stuff( I cant add i2 in another i's submember).
how can get submembers for the first i and then find submembers for the second i?
my goal is a list of users that has users as submembers and these users(submembers level 1) have submembers(submembers level 2) and so on (up to level 10) :
Family Tree

This is a typical issue when working with asynchronous code: the synchronous code will finish before any of the asynchronous code. So this loop:
for(var i in sub)
... will finish before any of the then callbacks inside it will be executed. So by the time one of those gets executed, the value of i is already the length of sub.
To be able to use the value of the i at the time you called then (not its callback), there are different solutions. One of them is to use let i instead of var i as it will define a different variable on each iteration of the for loop. Or you can bind the value of i as argument to the then callback:
userstuff01.then(function(i, sub2){
// ^^^ added parameter
for(var i2 in sub2){
//here i is the last object in sub but i2 is for the first i
console.log(sub[i.toString()]);
console.log(sub2[i2.toString()]);
}
}.bind(null, i));
// ^^^^^^^^^^^ bind the parameter value to the current value of i.

Related

Creating a Unique ID for each record on Form Submission

I am new to Google Apps Script and have just begun to understand its working. A team member wrote out a simple simple script for some work i was doing. The script, in essence, triggered when any of a permitted set of users (could vary) submits inputs to a 'Form Responses 1' spreadsheet via a Google Form.
Basically, I have a form that users complete and then submit. Upon submission, the script checks for the active row, The code adds 1 to the number of the cell W2 (which is a 'do not edit' cell, and replaces W2 with the new number, then checks if the Unique ID field on the Active Row is null and then replaces it with a concatenated ID thats alphanumeric. ie, it prefixes a set alphabetical prefix and takes the numerical input from the cell W2 on the same form to create a new Unique ID.
The script was working perfectly until the team member left and I removed her access from the Google sheets with no change to the script at all. I've been scrambling trying to figure out what happened after that, because since access was removed, when I haven't made any changes to my code. I have searched many different places and cannot seem to find what is wrong.
If i post it on a new google sheet, it's working fine .. but not on this sheet which already has around 900 critical entries.
Any guidance is welcome. the Script is as below. HELP!
//Logger.log(SpreadsheetApp.getActiveSpreadsheet().getUrl());
//Logger.log(SpreadsheetApp.getActive().getUrl());
// Get the active sheet
var sheet = SpreadsheetApp.getActiveSheet();
// Get the active row
var row = sheet.getActiveCell().getRowIndex();
// Get the next ID value. NOTE: This cell should be set to the last record counter value
var id = sheet.getRange("X2").getValue()+1;
Logger.log("HKG0"+id);
// Check if ID column is empty
if (sheet.getRange(row, 1).getValue() == "") {
// Set new ID value
sheet.getRange(2, 24).setValue(id);
sheet.getRange(row, 1).setValue("HKG0"+id);
}
}
If your code is running off of a form submit trigger then this should work for you.
function formsubit(e) {
Logger.log(JSON.stringify(e));
var sheet = e.range.getSheet();
var id = sheet.getRange("X2").getValue() + 1;
if (sheet.getRange(e.range.rowStart, 1).getValue() == "") {
sheet.getRange(2, 24).setValue(id);
sheet.getRange(e.range.rowStart, 1).setValue("HKG0" + id);
}
}
The Logger.log will help you to learn more about the event object. You can learn more about event objects here
If you're looking for a unique id for each submission try: const id = new Date(e.values[0]).valueOf(); it's the number of milliseconds since Jan 1, 1970

Update a row in google sheets based on duplicate

I'm designing a script that takes an object (jsonData[data]) and inputs its values into a different sheet based on which product it is.
Currently the script inputs all the data into a new row each time the form reaches a new stage, however the form goes through 4 stages of approval and so I'm finding each submission being entered into 4 different rows. Each submission has an "Id" value within the object which remains the same (but each submission could also be on any row in the sheet as it's used a lot).
I'm checking whether the ID exists in the sheet and using iteration to find the row number:
function updatePlatformBulkInfo(jsonData) {
var sheetUrl = "https://docs.google.com/spreadsheets/d/13U9r9Lu2Fq1WTT8pQ128heCm6_gMmH1R4O6u8e7kvBo/edit#gid=0";
var sheetName = "PlatformBulkSetup";
var doc = SpreadsheetApp.openByUrl(sheetUrl);
var sheet = doc.getSheetByName(sheetName);
var rowList = [];
var formId = jsonData["Id"];
var allSheetData = sheet.getDataRange().getValues();
setLog("AllSheetData = " + allSheetData[1][11]) //Logs to ensure data is collected correctly
var rowEdited = false;
var rowNumber = 0;
//Check whether ID exists in the sheet
for (var i = 0; i < allSheetData.length; i++) {
if(allSheetData[i][11] == formId) {
rowEdited = true;
} else {
rowNumber += 1;
}
}
My issue is with the next part:
//Append row if ID isn't duplicate or update row if duplicate found
if (rowEdited == false) {
for (var data in jsonData) {
rowList.push(jsonData[data])
}
setLog("***Row List = " + rowList + " ***");
setLog("***Current Row Number = " + rowNumber + " ***");
sheet.appendRow(rowList);
} else if(rowEdited == true){
var newRowValue = jsonData[data];
sheet.getRange(rowNumber, 1).setValues(newRowValue);
}
Everything works fine if the duplicate isn't found (the objects values are appended to the sheet). But if a duplicate is found I'm getting the error:
Cannot find method setValues(string)
This looks to me like i'm passing a string instead of an object, but as far as I'm aware I've already converted the JSON string into an object:
var jsonString = e.postData.getDataAsString();
var jsonData = JSON.parse(jsonString);
How can I modify my script to write the updated data to the matched row?
It's unclear based on your code whether or not you will actually write to the correct cell in the case of a duplicate. As presented, it looks as though you loop over the sheet data, incrementing a row number if the duplicate is not found. Then, after completing the loop, you write to the sheet, in the row described by rowNumber, even though your code as written changes rowNumber after finding a duplicate.
To address this, your loop needs to exit upon finding a duplicate:
var duplicateRow = null, checkedCol = /* your column to check */;
for(var r = 0, rows = allSheetData.length; r < rows; ++r) {
if(allSheetData[r][checkedCol] === formId) {
// Convert from 0-base Javascript index to 1-base Range index.
duplicateRow = ++r;
// Stop iterating through allSheetData, since we found the row.
break;
}
}
In both cases (append vs modify), you seem to want the same output. Rather than write the code to build the output twice, do it outside the loop. Note that the order of enumeration specified by the for ... in ... pattern is not dependable, so if you need the elements to appear in a certain order in the output, you should explicitly place them in their desired order.
If a duplicate ID situation is supposed to write different data in different cells, then the following two snippets will need to be adapted to suit. The general idea and instructions still apply.
var dataToWrite = [];
/* add items to `dataToWrite`, making an Object[] */
Then, to determine whether to append or modify, test if duplicateRow is null:
if(dataToWrite.length) {
if(duplicateRow === null) {
sheet.appendRow(dataToWrite);
} else {
// Overwriting a row. Select as many columns as we have data to write.
var toEdit = sheet.getRange(duplicateRow, 1, 1, dataToWrite.length);
// Because setValues requires an Object[][], wrap `dataToWrite` in an array.
// This creates a 1 row x N column array. If the range to overwrite was not a
// single row, a different approach would be needed.
toEdit.setValues( [dataToWrite] );
}
}
Below is the most basic solution. At the end of this post, I'll expand on how this can be improved. I don't know how your data is organized, how exactly you generate new unique ids for your records, etc., but let's assume it looks something like this.
Suppose we need to update the existing record with new data. I assume your JSON contains key-value pairs for each field:
var chris = {
id:2,
name: "Chris",
age: 29,
city: "Amsterdam"
};
Updating a record breaks down into several steps:
1) Creating a row array from your object. Note that the setValues() method accepts a 2D array as an argument, while the appendRow() method of the Sheet class accepts a single-dimension array.
2) Finding the matching id in your table if it exists. The 'for' loop is not very well-suited for this idea unless you put 'break' after the matching id value is found. Otherwise, it will loop over the entire array of values, which is redundant. Similarly, there's no need to retrieve the entire data range as the only thing you need is the "id" column.
IMPORTANT: to get the row number, you must increment the array index of the matching value by 1 as array indices start from 0. Also, if your spreadsheet contains 1 or more header rows (mine does), you must also factor in the offset and increment the value by the number of headers.
3) Based on the matching row number, build the range object for that row and update values. If no matching row is found, call appendRow() method of the Sheet class.
function updateRecord(query) {
rowData = [];
var keys = Object.keys(query);
keys.forEach(function(key){
rowData.push(query[key]);
})
var ss = SpreadsheetApp.getActive();
var sheet = ss.getSheets()[0];
var headers = sheet.getRange(1, 1, 1, sheet.getLastColumn()).getValues()[0];
var idColumn = 1;
var ids = sheet.getRange(2, idColumn, sheet.getLastRow() - 1, 1).getValues();
var i = 0;
var matchedRow;
do {
if (ids[i] == query.id) { matchedRow = i + 2; }
i++;
} while (!matchedRow && i < ids.length);
if (matchedRow) {
var row = sheet.getRange(matchedRow, idColumn, 1, rowData.length);
row.setValues([rowData]);
} else {
sheet.appendRow(rowData);
}
}
NOTE: if your query contains only some fields that need to be updated (say, the 'id' and the 'name' field), the corresponding columns for these fields will be
headers.indexOf(query[key]) + 1;
Possible improvements
If the goal is to use the spreadsheet as a database and define all CRUD (Create, Read, Write, Delete) operations. While the exact steps are beyond the scope of the answer, here's the gist of it.
1) Deploy and publish the spreadsheet-bound script as a web app, with the access set to "anyone, even anonymous".
function doGet(e) {
handleResponse(e);
}
function doPost(e) {
handleRespone(e);
}
function handleResponse(e) {
if (e.contentLength == -1) {
//handle GET request
} else {
//handle POST request
}
}
2) Define the structure of your queries. For example, getting the list of values and finding a value by id can be done via GET requests and passing parameters in the url. Queries that add, remove, or modify data can be sent as payload via POST request. GAS doesn't support other methods besides GET and POST, but you can simulate this by including relevant methods in the body of your query and then selecting corresponding actions inside handleResponse() function.
3) Make requests to the spreadsheet URL via UrlFetchApp. More details on web apps https://developers.google.com/apps-script/guides/web

remove previously added and() clause from Where com.datastax.driver.core.querybuilder.Select.Where

for (int i=0; i<mycolumns.length; i++)
{
where.and(QueryBuilder.eq(COLNAME, mycolumns[i]));
//how to remove the above and() call
}
In every iteration of the loop, I want to execute the query and then substitute the value in next loop iteration.
I'm not completely clear on what you are trying to accomplish. I am guessing that you are trying to update multiple rows sharing a primary key, updating 1 row at a time?
Unfortunately this isn't possible since when you call where.and you are adding data to the Where object and it is returning you a reference to the same Where object.
In short, Where is not immutable and neither is the Statement it belongs to, so you won't get a new copy every time you call it, rather you get an updated version of the Where object.
What you could do is generate your Statement again (whether it be QueryBuilder.update,delete, or insert) in the loop like:
for (int i=0; i<mycolumns.length; i++) {
Statement stmt = QueryBuilder.update("tableName").where(eq("key", 1)).and(QueryBuilder.eq(COLNAME, mycolumns[i]));
session.execute(stmt);
}

Read callback data in new thread C#

Can someone help me, I have one method that I call to "activate" callback method(which is set to send me changed data every second) and second method(which is "activated" by first) that runs every second and give me data. That is all fine, but I need that second method run in new thread and after couple days of trying and reading I couldn't mange to do that. Can someone please guide me how to do that? here is my code.
int ItemNumItems;
Array ItemClientHandles;
Array ItemServerValues;
Array ItemQualities;
Array ItemTimeStamps;
public void Callback()
{
//Here I add a delegate with method to be called on data change.
Grupa1.DataChange += new DIOPCGroupEvent_DataChangeEventHandler(Grupa1_DataChange);
}
void Grupa1_DataChange(int TransactionID, int NumItems, ref Array ClientHandles, ref Array ItemValues, ref Array Qualities, ref Array TimeStamps)
{
//Here I read my data and pass them to my variables(and this is
//runned every second) and I want this method to run in new thread.
ItemNumItems = NumItems;
ItemClientHandles = ClientHandles;
ItemServerValues = ItemValues;
ItemQualities = Qualities;
ItemTimeStamps = TimeStamps;
}
Something like that maybe:
void Grupa1_DataChange(int TransactionID, int NumItems, ref Array ClientHandles, ref Array ItemValues, ref Array Qualities, ref Array TimeStamps)
{
Array tmpClientHandles = ClientHandles;
Array tmpItemValues = ItemValues;
Array tmpQualities = Qualities;
Array tmpTimeStamps = TimeStamps;
ThreadPool.QueueUserWorkItem(o =>
{
//Here I read my data and pass them to my variables(and this is
//runned every second) and I want this method to run in new thread.
ItemNumItems = NumItems;
ItemClientHandles = tmpClientHandles;
ItemServerValues = tmpItemValues;
ItemQualities = tmpQualities;
ItemTimeStamps = tmpTimeStamps;
});
}
?
But why to you want to run it asynchronously?
From the code you show there is nothing that will gain to being run in parallel.

How to maintain counters with LinqToObjects?

I have the following c# code:
private XElement BuildXmlBlob(string id, Part part, out int counter)
{
// return some unique xml particular to the parameters passed
// remember to increment the counter also before returning.
}
Which is called by:
var counter = 0;
result.AddRange(from rec in listOfRecordings
from par in rec.Parts
let id = GetId("mods", rec.CKey + par.UniqueId)
select BuildXmlBlob(id, par, counter));
Above code samples are symbolic of what I am trying to achieve.
According to the Eric Lippert, the out keyword and linq does not mix. OK fair enough but can someone help me refactor the above so it does work? A colleague at work mentioned accumulator and aggregate functions but I am novice to Linq and my google searches were bearing any real fruit so I thought I would ask here :).
To Clarify:
I am counting the number of parts I might have which could be any number of them each time the code is called. So every time the BuildXmlBlob() method is called, the resulting xml produced will have a unique element in there denoting the 'partNumber'.
So if the counter is currently on 7, that means we are processing 7th part so far!! That means XML returned from BuildXmlBlob() will have the counter value embedded in there somewhere. That's why I need it somehow to be passed and incremented every time the BuildXmlBlob() is called per run through.
If you want to keep this purely in LINQ and you need to maintain a running count for use within your queries, the cleanest way to do so would be to make use of the Select() overloads that includes the index in the query to get the current index.
In this case, it would be cleaner to do a query which collects the inputs first, then use the overload to do the projection.
var inputs =
from recording in listOfRecordings
from part in recording.Parts
select new
{
Id = GetId("mods", recording.CKey + part.UniqueId),
Part = part,
};
result.AddRange(inputs.Select((x, i) => BuildXmlBlob(x.Id, x.Part, i)));
Then you wouldn't need to use the out/ref parameter.
XElement BuildXmlBlob(string id, Part part, int counter)
{
// implementation
}
Below is what I managed to figure out on my own:.
result.AddRange(listOfRecordings.SelectMany(rec => rec.Parts, (rec, par) => new {rec, par})
.Select(#t => new
{
#t,
Id = GetStructMapItemId("mods", #t.rec.CKey + #t.par.UniqueId)
})
.Select((#t, i) => BuildPartsDmdSec(#t.Id, #t.#t.par, i)));
I used resharper to convert it into a method chain which constructed the basics for what I needed and then i simply tacked on the select statement right at the end.

Resources