HOW TO USE: Global Array in Function with Loop/While (JavaScript/GAS) - javascript

I would like to use a global array / variable in my function. The function should be executed as long as IDS are in the array.
In the variable "var files = [...];" there are for example two
IDS, depending on how many files are in the folder.
var files = ['16EdsAx', '16wQxxIc'];
var files = [];
function getListOfId(){
var folderId = "11tjb_odTJ2E_ez";
var filesN = DriveApp.getFolderById(folderId).getFiles();
while (filesN.hasNext()) files.push(filesN.next().getId());
//console.log(files);
}
Don't be intimidated, these two functions only read the DOCs documents
and write them into the corresponding cell.
function getDocItems(docID, identifier){
const body = DocumentApp.openById("13TlciLOZV").getBody(); // >>> The IDS from the array should be used here <<<<
const docText = body.getText();
//Check if search characters are to be included.
let startLen = identifier.start_include ? 0 : identifier.start.length;
let endLen = identifier.end_include ? 0 : identifier.end.length;
//Set up the reference loop
let textStart = 0;
let doc = docText;
let docList = [];
//Loop through text grab the identifier items. Start loop from last set of end identfiers.
while(textStart > -1){
let textStart = doc.indexOf(identifier.start);
if(textStart === -1){
break;
}else{
let textEnd = doc.indexOf(identifier.end) + identifier.end.length;
let word = doc.substring(textStart,textEnd);
doc = doc.substring(textEnd);
docList.push(word.substring(startLen,word.length - endLen));
};
};
//return a unique set of identifiers.
return [...new Set(docList)];
};
//The chewy conversation
function runsies(){
const docID = "13TlciLOZV"; // >>> The IDS from the array should be used here <<<<
const identifier = {
start: `ISIN: `,
start_include: false,
end: `VERRECHNUNGSKONTO`,
end_include: false
};
let results = getDocItems(docID, identifier);
//var commaAdd = results.join("''");
//console.log(results);
const ss = "17a55HCwlO5uF8gkXpG";//The spreadsheet ID
const sheet = "Stock_Data";//The sheet tab name
var activeSheet = SpreadsheetApp.getActiveSheet();
let importToSpredsheet = SpreadsheetApp.openById(ss).getSheetByName(sheet);
const range = activeSheet.getRange(6,1,results.length,1);
range.setValue(results);
};
Here you can find the tutorial where I got this code from. HERE
I always used the exact docs id in the code. But now I would like to use the ids from the array from the getListOfId () function. The information from the files should all be in different cells, ideally all in column A one below the other.
So my questions are:
How can I refer to the IDS in the other two functions?
The function should be repeated until all IDS have been used and all files have been read out and entered in the spreadsheet, but how?

I believe your goal as follows.
You want to retrieve the Google Document IDs from the function of getListOfId.
In this case, the IDs returned from getListOfId are always the file IDs of Google Document.
You want to use the file IDs to docID of let results = getDocItems(docID, identifier); in the function of runsies.
You want to put the values retrieved from the function of getDocItems to the sheet of Stock_Data in the Google Spreadsheet.
Modification points:
In this case, I would like to propose the following flow.
Retrieve the file IDs from getListOfId.
In this modification, the file IDs retrieved from getListOfId are used in runsies.
Put the file IDs to getDocItems using a loop.
Put the result values to the Spreadsheet.
When I saw your script for putting values to the Spreadsheet, the values are put to the active sheet. If you want to put the values to the sheet of Stock_Data in the Google Spreadsheet of const ss = "17a55HCwlO5uF8gkXpG";, it is required to modify the script.
And also, in your script, by const range = activeSheet.getRange(6,1,results.length,1); and range.setValue(results);, the 1st element in the array of results is put the number of times of the length of results from the cell "A6". When you want to put the values from the row 6, it is required to modify the script.
When above points are reflected to your script, it becomes as follows.
Modified script:
getListOfId()
Please set your folder ID.
function getListOfId(){
var folderId = "###"; // Please set your folder ID.
var filesN = DriveApp.getFolderById(folderId).getFiles();
var files = [];
while (filesN.hasNext()) files.push(filesN.next().getId());
return files;
}
runsies()
Please set your Spreadsheet ID.
function runsies(){
const docIDs = getListOfId(); // Here, the file IDs are retrieved from `getListOfId`.
const identifier = {
start: `ISIN: `,
start_include: false,
end: `VERRECHNUNGSKONTO`,
end_include: false
};
if (docIDs.length == 0) return;
const results = docIDs.map(id => getDocItems(id, identifier)); // Here, the retrieved file IDs are used in a loop.
const ss = "###"; // Please set your Spreadsheet ID.
const sheetName = "Stock_Data"; //The sheet tab name
const sheet = SpreadsheetApp.openById(ss).getSheetByName(sheetName);
const range = sheet.getRange(sheet.getRange(6,1).isBlank() ? 6 : sheet.getLastRow() + 1,1,results.length,results[0].length);
range.setValues(results);
}
In this case, when docIDs has not file IDs, the script is stopped.
In this modified script, from your script, the retrieved values results are put from the row 6 on the sheet of Stock_Data in the Google Spreadsheet const ss = "###". When the values has already been existing from the row 6, the values are appended.
getDocItems(docID, identifier)
From:
const body = DocumentApp.openById("13TlciLOZV").getBody();
To
const body = DocumentApp.openById(docID).getBody();
Note:
Please use this modified script with enabling V8 runtime.
If above modification is not the result you expect, can you show the whole script and the detail of your goal? By this, I would like to confirm it.
References:
map()
setValues(values)

Related

Split multiple JSON string into strucutred table using Google App Script

I am trying to split a data set with an ID and JSON string into a structured table.
The difficult part is I need it to be dynamic, the JSON string varies often and I want headings to be determined by the unique values in the input column at that time. I need the script to be able to create headings if the string changes without needed to recode the script.
We have about 150 different JSON strings we are hoping to use this script on, without recoding it for each one. Each string has lots of data points.
I have a script working but it splits them one by one, need to build something that will do bulk in one go, by looping through all outputs in B and creating a column for each unique field in all the strings, then populating them.
The script works if I paste the additional info straight in, however I am having trouble reading from the sheet
var inputsheet = SpreadsheetApp.getActive().getSheetByName("Input");
var outputsheet = SpreadsheetApp.getActive().getSheetByName("Current Output");
var additionalinfo = inputsheet.getRange(1,1).getValue()
Logger.log(additionalinfo)
var rows = [],
data;
for (i = 0; i < additionalinfo.length; i++) {
for (j in additionalinfo[i]) {
dataq = additionalinfo[i][j];
Logger.log(dataq);
rows.push([j, dataq]);
}
dataRange = outputsheet.getRange(1, 1, rows.length, 2);
dataRange.setValues(rows);
}
}
Here is a link to the sample data. Note that in Sample 1 & 2 there are different headings, we need the script to identify this and create headings for both
https://docs.google.com/spreadsheets/d/1BMiVuAgDbibLw6yUG3IZ9iw4MZTaVVegkw_k3ItQ4mU/edit#gid=0
Try this script that produces dynamic headers based on the json that has been read. It collects all json data, get its keys, and remove the duplicates.
Script:
function JSON_SPLITTER() {
var spreadsheet = SpreadsheetApp.getActive();
var inputsheet = spreadsheet .getSheetByName("Input");
var outputsheet = spreadsheet .getSheetByName("Current Output");
var additionalinfo = inputsheet.getDataRange().getValues();
var keys = [];
// prepare the additionalInfo data to be parsed for later
var data = additionalinfo.slice(1).map(row => {
// collect all keys in an array
if (JSON.parse(row[1]).additionalInfo) {
keys.push(Object.keys(JSON.parse(row[1]).additionalInfo));
return JSON.parse(row[1]).additionalInfo;
}
else {
keys.push(Object.keys(JSON.parse(row[1])));
return JSON.parse(row[1]);
}
});
// unique values of keys, modified to form header
var headers = [...new Set(keys.flat())]
// Add A1 as the header for the ids
headers.unshift(additionalinfo[0][0]);
// set A1 and keys as headers
var output = [headers]
// build output array
additionalinfo.slice(1).forEach((row, index) => {
var outputRow = [];
headers.forEach(column => {
if(column == 'Contract Oid')
outputRow.push(row[0]);
else
outputRow.push(data[index][column]);
});
output.push(outputRow)
});
outputsheet.getRange(1, 1, output.length, output[0].length).setValues(output);
}
Output:
Update:
Modified script for no-additionalInfo key objects.

Google Sheets Apps Script Matching Source Destination Sheets having same name tabs

I've a need to copy values between tabs of source & destination sheets. The tabs are same name H1, H2, H3 in both sheets. Copying is between tabs of same name i.e., H1->H1, H2->H2, etc. The script is attached below. Every time I've to run the script, I'm changing the sheet name manually. How to do it in one go?
function updateSourceToTarget(sourceID,sourceName,targetID,targetname){
var source = SpreadsheetApp.openById("Source").getSheetByName("H1");
var destination = SpreadsheetApp.openById("Dest").getSheetByName("H1");
var sourcelastRow = source.getLastRow();
var sourcelastCol = source.getLastColumn();
var sourcedata = source.getRange(1,1,sourcelastRow,sourcelastCol).getValues();
destination.getRange(1,1,sourcelastRow,sourcelastCol).setValues(sourcedata);
}
Explanation:
Iterate over an array of the sheet names ["H1","H2","H3"] with a forEach loop.
Put all the sheet names in that array, and the script will take care of the task for every set of source and destination sheet.
Solution:
function updateSourceToTarget(sourceID,sourceName,targetID,targetname){
const sheetNames = ["H1","H2","H3"];
sheetNames.forEach(h=>{
let source = SpreadsheetApp.openById("Source").getSheetByName(h);
let destination = SpreadsheetApp.openById("Dest").getSheetByName(h);
let sourcelastRow = source.getLastRow();
let sourcelastCol = source.getLastColumn();
let sourcedata = source.getRange(1,1,sourcelastRow,sourcelastCol).getValues();
destination.getRange(1,1,sourcelastRow,sourcelastCol).setValues(sourcedata);
});
}
and I guess this is the final version that uses the function parameters:
function updateSourceToTarget(sourceID,targetID){
const sheetNames = ["H1","H2","H3"];
sheetNames.forEach(h=>{
let source = SpreadsheetApp.openById(sourceID).getSheetByName(h);
let destination = SpreadsheetApp.openById(targetID).getSheetByName(h);
let sourcelastRow = source.getLastRow();
let sourcelastCol = source.getLastColumn();
let sourcedata = source.getRange(1,1,sourcelastRow,sourcelastCol).getValues();
destination.getRange(1,1,sourcelastRow,sourcelastCol).setValues(sourcedata);
});
}

Automate Hyperlink Creations

I'm trying to automate hyperlink creations on my GSheet.
Here's my script:
function ticketURLGenerator() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("Data");
var range = sheet.getRange("C2:C");
var ticketID = range.getValue();
Logger.log(ticketID);
for(i = 0; i < ticketID.length; i++){
if(ticketID.length === 0){
ticketID.setValue('')
} else if(ticketID.length > 4){
ticketID.setValue('=HYPERLINK' + '("https://mylink.com/'+ticketID+'";'+ticketID+')');
}
}
}
It does nothing but when I change ticketID.setValue by sheet.getRange("C2:C").setValue it put the whole range in the url. We can see with Logger.log(ticketID) that the whole range is selected.
So according to this result, i'm missing how to get the value of each cell individualy in the range and then check if they are long enought to create an individual url. Do I need to use something like range[i] somewhere? I'm lost.
I believe your goal as follows.
You want to retrieve the values from the cells "C2:C".
When the length of value is more than 4, you want to create a formula of HYPERLINK.
When the length of value is less than 4, you don't want to put the formula.
You want to put the formulas to the cells "C2:C".
Modification points:
When range of var range = sheet.getRange("C2:C") is used, the value of var ticketID = range.getValue() is the value of cell "C2". When you want to retrieve values from the cells "C2:C", please use getValues instead of getValue.
In this case, the retrieved value is 2 dimensional array.
When range.getValue() is the string value, ticketID of var ticketID = range.getValue() is also the string. So I think that when ticketID.setValue('##') is run, an error occurs.
In your script, setValue is used in a loop. In this case, the process cost will become high.
And, when sheet.getRange("C2:C" + sheet.getLastRow()) is used instead of sheet.getRange("C2:C"), the process cost will become low a little.
When above points are reflected to your script, it becomes as follows.
Modified script:
function ticketURLGenerator() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("Data");
var range = sheet.getRange("C2:C" + sheet.getLastRow());
var ticketIDs = range.getValues();
var values = ticketIDs.map(([c]) => [c.toString().length > 4 ? `=HYPERLINK("https://mylink.com/${c}";"${c}")` : c]);
range.setValues(values);
}
In this modification, the values are retrieved from the cells of "C2:C" + sheet.getLastRow(), and an array including the formulas and values is created, and then, the array is put to the cells.
And I used the template literal for creating the formula.
Note:
In this case, please use this script with enabling V8 runtime.
References:
getLastRow()
getValues()
map()
Template literals
You just need to apply the HYPERLINK operation to the tickets that their length is more than 4. To achieve that, you can use map() to iterate over all the elements in your list.
Solution:
function ticketURLGenerator() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const sheet = ss.getSheetByName("Data");
const ticketR = sheet.getRange("C2:C"+sheet.getLastRow());
const ticketIDs = ticketR.getDisplayValues().flat();
const hLinks = ticketIDs.map(ti=>{
if(ti.length>4) {
return [`=HYPERLINK("https://mylink.com/${ti}"; ${ti})`]}
else {return [ti]}
})
ticketR.setValues(hLinks);
}

How to push array position in the array faster

I have a google spreadsheet data in form of array. Now I want to push the array position I used for loop which is working fine on small data but when the data length is increased it result in delay.
Is there a faster way to push the array position in the array.
Here is the code which I am currently using:-
var ss = SpreadsheetApp.openById('19zxxxxxxxxxxxxxxxxxxxxxxxxOI');
var sheet1 = ss.getSheetByName('Sheet2');
var data = sheet1.getRange("A:H").getValues();
var email = Session.getActiveUser().getEmail();
for(var i = 0; i < data.length; i++)
{
data.unshift(i+1);
} // this for loop takes too much time.
data = data.filter(function(item){return item[7] == email});
var x = data.map(function(val){
return val.slice(0, -7);
})
Logger.log(x)
return x;
}
I believe your goal as follows.
If data.unshift(i+1) is data[i].unshift(i+1) as TheMaster's comment, you want to retrieve the values of the column "A" when the value of column "G" is the same with email. At that time, you want to add the row number to the 1st index of the row value.
From your script, I understood like this.
You want to reduce the process cost of this situation.
For this problem, how about this solution?
Pattern 1:
In this pattern, your script is modified. In this case, the result values are retrieve by one loop.
Sample script:
function myFunction() {
var ss = SpreadsheetApp.openById('19zxxxxxxxxxxxxxxxxxxxxxxxxOI');
var sheet1 = ss.getSheetByName('Sheet2');
var data = sheet1.getRange("A1:H" + sheet1.getLastRow()).getValues(); // Modified
var email = Session.getActiveUser().getEmail();
const res = data.reduce((ar, [a,,,,,,g], i) => { // Modified
if (g == email) ar.push([i + 1, a]);
return ar;
}, []);
Logger.log(res)
return res;
}
Pattern 2:
In this pattern, as other method, TextFinder and Sheets API are used. In this case, the size of base data by searching email with TextFinder can be reduced. And each values are retrieved by one API call using Sheets API.
Sample script:
Before you use this script, please enable Sheets API at Advanced Google services.
function myFunction() {
const spreadsheetId = '19zxxxxxxxxxxxxxxxxxxxxxxxxOI';
const ss = SpreadsheetApp.openById(spreadsheetId);
const sheet = ss.getSheetByName('Sheet2');
const email = Session.getActiveUser().getEmail();
// 1. Retrieve the ranges of rows by searching "email" at the column "G".
const ranges = sheet.getRange("G1:G" + sheet.getLastRow()).createTextFinder(email).findAll();
// 2. Create an object for using with Sheets API.
const reqs = ranges.reduce((o, e) => {
const row = e.getRow();
o.rows.push(row);
o.ranges.push("A" + row);
return o;
}, {rows: [], ranges: []});
// 3. Retrieve values and add the row number.
const res = Sheets.Spreadsheets.Values.batchGet(spreadsheetId, {ranges: reqs.ranges})
.valueRanges
.map((e, i) => ([reqs.rows[i], e.values[0][0]]));
Logger.log(res)
return res;
}
If email is included other string, please use matchEntireCell(true) to TextFinder.
References:
reduce()
Advanced Google services
Method: spreadsheets.values.batchGet

Paste values from one Google Sheet to another and remove duplicates based on ID column

I have a similar situation to the one described on this question: two worksheets, with input data coming into the Feed sheet using the importxml function and a Data sheet where new rows get copied thanks to a script set to run daily.
However, the current script is creating daily duplicates. As such, I would like to adapt the answer provided on the question above so that the script checks the IDs on column F and only copies the rows with new IDs.
How should I update the section below that creates a hash to one that looks for the IDs on column F instead? Also my rows are consistent, so is it correct to assume I can just remove the relevant code lines towards the end?
The sample Google Sheet is available here.
function appendUniqueRows() {
var ss = SpreadsheetApp.getActive();
var sourceSheet = ss.getSheetByName('Get Data');
var destSheet = ss.getSheetByName('Final Data');
var sourceData = sourceSheet.getDataRange().getValues();
var destData = destSheet.getDataRange().getValues();
// Check whether destination sheet is empty
if (destData.length === 1 && "" === destData[0].join('')) {
// Empty, so ignore the phantom row
destData = [];
}
// Generate hash for comparisons
var destHash = {};
destData.forEach(function(row) {
destHash[row.join('')] = true; // could be anything
});
// Concatentate source rows to dest rows if they satisfy a uniqueness filter
var mergedData = destData.concat(sourceData.filter(function (row) {
var hashedRow = row.join('');
if (!destHash.hasOwnProperty(hashedRow)) {
// This row is unique
destHash[hashedRow] = true; // Add to hash for future comparisons
return true; // filter -> true
}
return false; // not unique, filter -> false
}));
// Check whether two data sets were the same width
var sourceWidth = (sourceData.length > 0) ? sourceData[0].length : 0;
var destWidth = (destData.length > 0) ? destData[0].length : 0;
if (sourceWidth !== destWidth) {
// Pad out all columns for the new row
var mergedWidth = Math.max(sourceWidth,destWidth);
for (var row=0; row<mergedData.length; row++) {
for (var col=mergedData[row].length; col<mergedWidth; col++)
mergedData[row].push('');
}
}
// Write merged data to destination sheet
destSheet.getRange(1, 1, mergedData.length, mergedData[0].length)
.setValues(mergedData);
}
I'm a novice in this world of Google Apps scripts, so do please let me know if I'm missing any crucial information. Thanks in advance for the help.
You want to copy the values from "Feed" sheet to "Data" sheet.
When the values are copied, you want to copy only new values which are not included in "Data" sheet.
You want to choose the new values using the values of column "F".
If my understanding for your question is correct, how about this modification? In this modification, I modified the script in your shared spreadsheet.
Modification points:
In your script, all values of "Feed" sheet are copied to "Data" sheet. So in order to choose only new values, I used the following flow.
Retrieve the values from column "F". This is used for choosing the new values.
Retrieve the new values using the values from column "F".
Put the new values to "Data" sheet.
The script which reflected above flow is as follows.
Modified script:
From:
This is your script in the shared spreadsheet. Please modify this script to below one.
function Copy() {
var sss = SpreadsheetApp.openById('#####'); // this is your Spreadsheet key
var ss = sss.getSheetByName('Feed'); // this is the name of your source Sheet tab
var range = ss.getRange('A3:H52'); //assign the range you want to copy
var data = range.getValues();
var tss = SpreadsheetApp.openById('#####'); //replace with destination ID
var ts = tss.getSheetByName('Data'); //replace with destination Sheet tab name
ts.getRange(ts.getLastRow()+1, 1,50,8).setValues(data);// 49 value refers to number of rows, 8 to columns
}
To:
function Copy() {
var sss = SpreadsheetApp.openById('#####'); // this is your Spreadsheet key
var ss = sss.getSheetByName('Feed'); // this is the name of your source Sheet tab
var range = ss.getRange('A3:H52'); //assign the range you want to copy
var data = range.getValues();
var tss = SpreadsheetApp.openById('#####'); //replace with destination ID
var ts = tss.getSheetByName('Data'); //replace with destination Sheet tab name
// Below script was added.
var values = ts.getRange("F3:F").getValues().filter(String);
var copiedValues = data.filter(function(e) {return !values.some(function(f){return f[0] == e[5]}) && e.filter(String).length > 0});
ts.getRange(ts.getLastRow() + 1, 1, copiedValues.length, copiedValues[0].length).setValues(copiedValues);
}

Categories

Resources