I am building on a previous project in which I have a Google Form which takes responses in a Google Sheet and uses a template Sheet to populate the Form responses and have that generate a new Sheet document. This is a very dumbed-down version of what I'm trying to execute in reality, but the goals remain the same: I am trying to replace text across multiple tabs in the template Sheet when generating a new one.
Currently, in my Apps Script, I have code which is successfully able to make a copy of the template file and name it accordingly:
//Enter collected info into Requirements Template
const googleSheetTemplate = DriveApp.getFileById('1wqCwMhpuDLReU1hE1CbcDL-Vdw_4zge1xM6oOl34Ohg');
const destinationFolder = DriveApp.getFolderById('1GxNZQmP8mxHBhVl5AMoqBFs8sAIYzcm3');
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Form Responses 2');
const copy = googleSheetTemplate.makeCopy(`${row[3]}, ${row[0]} Vehicle Order` , destinationFolder);
const newSheet = SpreadsheetApp.openById(copy.getId());
const A1 = newSheet.getDataRange();
And the next few lines which are meant to be able to find and replace certain strings within the newly copied Sheet are as follows:
A1.createTextFinder("{{Customer}}").replaceAllWith(row[3]);
A1.createTextFinder("{{Car}}").replaceAllWith(row[1]);
A1.createTextFinder("{{Color}}").replaceAllWith(row[2]);
A1.createTextFinder("{{Delivery}}").replaceAllWith(row[5]);
The issue I am experiencing is that the first tab of the Sheet gets populated, but the second tab does not.
Is there more I must add somewhere in order to get the second tab filled out? Is this even possible in Google Apps Script?
Entire code below:
function myFunction() {
// get the spreadsheet information
const ss = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
//const responseSheet = ss.getSheetByName('Form Responses 2');
const data = ss.getDataRange().getValues();
//console.log(data);
// Loop over the rows
data.forEach((row,i) => {
// Identify whether notification has been sent
if (row[4] === '') {
// Get the Form info
var emailTo = "jeffreyabr#gmail.com"
var subject = 'Car Request';
const Timestamp = row[0];
var Car = row[1];
var Color = row[2];
var requestor = row[3]
var delivery = row[5];
//Form variable declarations
formTime = Timestamp;
formCar = Car;
formColor = Color;
formName = requestor;
formDelivery = delivery;
//Enter collected info into Requirements Template
const googleSheetTemplate = DriveApp.getFileById('1wqCwMhpuDLReU1hE1CbcDL-Vdw_4zge1xM6oOl34Ohg');
const destinationFolder = DriveApp.getFolderById('1GxNZQmP8mxHBhVl5AMoqBFs8sAIYzcm3');
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Form Responses 2');
const copy = googleSheetTemplate.makeCopy(`${row[3]}, ${row[0]} Vehicle Order` , destinationFolder);
const newSheet = SpreadsheetApp.openById(copy.getId());
const A1 = newSheet.getDataRange();
A1.createTextFinder("{{Customer}}").replaceAllWith(row[3]);
A1.createTextFinder("{{Car}}").replaceAllWith(row[1]);
A1.createTextFinder("{{Color}}").replaceAllWith(row[2]);
A1.createTextFinder("{{Delivery}}").replaceAllWith(row[5]);
const orderLink = newSheet.getUrl();
//Add URL to Sheet
sheet.getRange(i + 1, 7).setValue(orderLink)
orderBlob = [];
//Get the blob of order attachment
if(row[6]){
var order1 = row[6].split(', ');
order1.forEach(url => {
var orderFileId = url.replace('https://drive.google.com/open?id=','');
var orderFile = DriveApp.getFileById(orderFileId);
orderBlob.push(orderFile.getBlob());
});
}
let body = '';
// Generate email
var html = HtmlService.createTemplateFromFile("email.html");
var htmlText = html.evaluate().getContent();
// Send email
GmailApp.sendEmail(emailTo, subject, body, {htmlBody: htmlText, attachments: orderBlob})
// Mark as Notified
const g = 'Notification sent';
ss.getRange(i + 1,5).setValue(g);
}
})
}
Answer
You can try a looping method to access all of the sheet tabs inside of your newSheet Spreadsheet file using the getSheets() method.
Just replace part of your script from the creation of newSheet to the last line of A1.createTextFinder with the script below:
[UPDATED]
Sample Script
const newSheet = SpreadsheetApp.openById(copy.getId());
for (currentSheet = 0; currentSheet < newSheet.getSheets().length; currentSheet++) {
const a1 = newSheet.getSheets()[currentSheet].getDataRange();
a1.createTextFinder("{{Customer}}").replaceAllWith(row[3]);
a1.createTextFinder("{{Car}}").replaceAllWith(row[1]);
a1.createTextFinder("{{Color}}").replaceAllWith(row[2]);
a1.createTextFinder("{{Delivery}}").replaceAllWith(row[5]);
}
Sample Test:
Sample copy of the newSheet file w/ 2 sheet tabs
Script test demonstration
The newSheet file that contains 2 sheet tabs:
Sheet 1
Sheet 2
I tried to write the script as shown below. It is necessary to combine the values and background colors in this cell range into a single array. But it does not come as needed.
function newsheet(){
var app = SpreadsheetApp
var ss = app.getActiveSpreadsheet().getSheetByName("AddData");
//get category if checked
var catList = ss.getRange("I5:J31").getValues();
var catClrs = ss.getRange("J5:J31").getBackgrounds();
var c = catList.map((key,index) => ({[key]:catClrs[index]}));
Logger.log(c);
}
The answer comes this way
But I want an outcome like this.
[{Extra,true,#b6d7a8}, {Extra,false,#ffffff}, {Extra,false,#ffffff}, {Ldrain ,false,#ffffff}, {Dish Drain,true,#0c343d}, {U drain,true,#274e13}, {Hard Sholder,true,#00ffff}, {Soft Sholder,true,#cc0000}, {As. wearing,true,#ff9900}, {Tack coat Wearing,true,#fce5cd}, {As.Binder,true,#9900ff}, {Tack Coat binder,true,#46bdc6}, {Prime coat,true,#000000}, {ABC overlay,false,#ffffff}, {Subbase overlay,true,#980000}, {Edgewidning ABC,true,#999999}, {Edgewidning Subbase,false,#ffffff}, {SubGrade Ew,true,#0000ff}, {Embankment 5,true,#9fc5e8}, {Embankment 4,true,#6fa8dc}, {Embankment 3,true,#3d85c6}, {Embankment 2,true,#0b5394}, {Embankment 1,true,#1c4587}, {Subgrade Emb,true,#8e7cc3}, {Soft Ground tratment 1,true,#c27ba0}, {Soft Ground tratment 2,true,#a64d79}, {Clearing & grabbing,true,#f1c232}
I'd do it like this because it will be easier to read the values as you iterate through the array by having property labels for the object
function myobject(){
const ss=SpreadsheetApp.getActive()
var sh = ss.getSheetByName("AddData");
var catList = sh.getRange("I5:J31").getValues();
var catClrs = sh.getRange("J5:J31").getBackgrounds().flat();
let c=[];
catList.forEach((row,i) => {c.push({label:row[0],value:row[1],color:catClrs[i]})});
Logger.log(JSON.stringify(c));
}
With my fake data:
[{"label":3,"value":21,"color":"#ffffff"},{"label":0,"value":17,"color":"#ffffff"},{"label":5,"value":29,"color":"#ffffff"},{"label":29,"value":25,"color":"#ffffff"},{"label":16,"value":24,"color":"#ffffff"},{"label":17,"value":15,"color":"#ffffff"},{"label":22,"value":4,"color":"#ffffff"},{"label":6,"value":1,"color":"#ffffff"},{"label":4,"value":28,"color":"#ffffff"},{"label":14,"value":28,"color":"#ffffff"},{"label":13,"value":1,"color":"#ffffff"},{"label":2,"value":15,"color":"#ffffff"},{"label":23,"value":6,"color":"#ffffff"},{"label":14,"value":24,"color":"#ffffff"},{"label":21,"value":6,"color":"#ffffff"},{"label":12,"value":18,"color":"#ffffff"},{"label":12,"value":7,"color":"#ffffff"},{"label":5,"value":5,"color":"#ffffff"},{"label":2,"value":0,"color":"#ffffff"},{"label":16,"value":16,"color":"#ffffff"},{"label":15,"value":21,"color":"#ffffff"},{"label":0,"value":25,"color":"#ffffff"},{"label":20,"value":6,"color":"#ffffff"},{"label":24,"value":23,"color":"#ffffff"},{"label":22,"value":0,"color":"#ffffff"},{"label":14,"value":15,"color":"#ffffff"},{"label":24,"value":6,"color":"#ffffff"}]
Added flat()
Replaced key by row, index by i and ({[key]:catClrs[index]}) by [row[0],row[1],catClrs[i]]
function newsheet(){
var app = SpreadsheetApp
var ss = app.getActiveSpreadsheet().getSheetByName("AddData");
//get category if checked
var catList = ss.getRange("I5:J31").getValues();
var catClrs = ss.getRange("J5:J31").getBackgrounds().flat();
var c = catList.map((row,i) => [row[0],row[1],catClrs[i]]);
Logger.log(c);
}
The result will be slightly different because {} is used for objects and objects requires a key and a value.
I changed key by row to make the variable name closer to what it's holding as usually a "key" is a string but in this case it's an Array holding the values of two cells in the same row. i is shorter than index.
Nothing new compared to the accepted answer, but just shortening your code and a slighty better performance:
function newsheet(){
const ss = SpreadsheetApp.getActive().getSheetByName("AddData");
const range = ss.getRange("I5:J31");
const [catList,catClrs] = [range.getValues(),range.getBackgrounds()];
const c = catList.map((row,i) => [row[0],row[1],catClrs[0][i]]);
Logger.log(c);
}
If you want to filter on the true values:
function newsheet(){
const ss = SpreadsheetApp.getActive().getSheetByName("AddData");
const range = ss.getRange("I5:J31");
const [catList,catClrs] = [range.getValues(),range.getBackgrounds()];
const c = catList.map((row,i) => [row[0],row[1],catClrs[0][i]]).filter(r=>r[1]);
Logger.log(c);
}
Here is the code that worked correctly. In addition to what is mentioned in the question, the method of filtering is also included.
function newsheet(){
var app = SpreadsheetApp
var ss = app.getActiveSpreadsheet().getSheetByName("AddData");
//get category
var catList = ss.getRange("I5:J31").getValues();
var catClrs = ss.getRange("J5:J31").getBackgrounds().flat();
//merge arrays
var c = catList.map((row,i) => [row[0],row[1],catClrs[i]]);
//filter "checked" boxes values
var fillterC = c.filter(filterLogic);
}
//create filter logic function
var filterLogic =function(item){
if(item[1] === true){
return true;
}else{
return false;
}
}
Thanks to everyone who helped. I learned a lot.
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)
I have an array (below)
var img_name = new Array("images/test.jpg", "images/test.jpg");
var imgTotal = img_name.length;
var rnd_no = Math.floor(imgTotal*Math.random());
var ojimg = img_name[rnd_no];
What I need to do is pass another piece of information and attach that to the body tag. So. if test1.jpg is loaded I need to pass "light" to the body tag and if the other image is selected I need to pass "dark". What this does is alows a user in CMS to select a light or dark theme depending on the image. The image will be output randomly.
You could store objects in your array:
// I'm using an Array literal instead of a Array constructor here, because it's shorter
var img_name = [ {image:"images/test.jpg", style:"light"}, {image:"images/test.jpg", style:"dark"} ];
var imgTotal = img_name.length;
var rnd_no = Math.floor(imgTotal*Math.random());
var ojimg = img_name[rnd_no].image;
var ojstyle = img_name[rnd_no].style;
How about nested arrays:
var img_name = [["images/test1.jpg", "light"], ["images/test2.jpg", "dark"]]];
var imgTotal = img_name.length;
var rnd_no = Math.floor(imgTotal*Math.random());
var ojimg = img_name[rnd_no][0];
var cssclass = img_name[rnd_no][1];
In your array, you can just have some additional string with a seperator.
var img_name = new Array("images/test.jpg:light", "images/test.jpg:dark");
For your example, I have used : as seperator. In the consuming method, you can split the data based on seperator : and use both items accordingly.
You could use a map of image srcs strings to theme names. Then you can use a selected image src to directly access the theme name.
var themeMap = {};
themeMap["testImg1.jpg"] = myDarkTheme; //myDarkTheme can be a full object or just a string
You can then do
getTheme(img_name[rnd_no]);
function getTheme(imgSrcString)
{
return themeMap[imgSrcString];
}