Google Apps Script: Use getFolderById for multiple variables depending on value - google-apps-script

I think I have a very simple issue with Google Apps Script, but I already tried to google the solution for 1.5hrs without success. I guess I search for the wrong terms.
Here my code:
function folderLocations(){
var folder = {
Michael: '1bz9wIBRcRN2V-xxxxxxxxxx',
Chris: '1AEKHiI8iZKjHs-xxxxxxxxxx',
Steve: '1TD8iwjcbR7K5dN-xxxxxxxxxx',
};
return folder;
}
function createNewGoogleDocs() {
//ID of Google Docs Template, what sheet to use + save all values as 2D array
const googleDocTemplate = DriveApp.getFileById('xxxxxxxxxx_XznDn-i0WVtIM');
const sheet = SpreadsheetApp
.getActiveSpreadsheet()
.getSheetByName('Current Overview');
const rows = sheet.getDataRange().getValues();
//Start processing each spreadsheet row
rows.forEach(function(row, index){
//Destination folder ID (can differ from each person)
const destinationFolder = DriveApp.getFolderById(folderLocations().Chris);
// Set custom file name and create file
const copy = googleDocTemplate.makeCopy(`${row[15]} - ${row[3]} Quarterly Review` , destinationFolder);
const doc = DocumentApp.openById(copy.getId());
const body = doc.getBody();
// Replace placeholders with real values
body.replaceText('%NAME%', row[3]);
body.replaceText('%QUARTER%', row[15]);
body.replaceText('%ANSWER_1%', row[16]);
body.replaceText('%ANSWER_2%', row[17]);
[...]
doc.saveAndClose();
})
}
All working fine! BUT: What I want is to "dynamically" change the folder, depending on the value of a cell. It's not always "Chris"...:
const destinationFolder = DriveApp.getFolderById(folderLocations().Chris);
E.g.: If row[4] == Michael, then use the folder ID of "Michael". Somehow I can't get it to work to be "dynamically". 😔
I already tried all this, none working:
const destinationFolder = DriveApp.getFolderById(folderLocations().row[4]);
const destinationFolder = DriveApp.getFolderById(folderLocations(row[4]));
const destinationFolder = DriveApp.getFolderById(folderLocations().`${row[4]}`);
const destinationFolder = DriveApp.getFolderById(folderLocations().toString(row[4]));
etc.
👆🏻 I know what I try to do here is embarrassing. But I am normally not a developer and nobody at my company is familiar with Google Apps Script. That's the last bit I am missing, rest I put together myself using Google.
Thank you SOO much! 🙏🏻

You don't even need a function. Just an object is enough:
const folderLocations = {
Michael: '1bz9wIBRcRN2V-xxxxxxxxxx',
Chris: '1AEKHiI8iZKjHs-xxxxxxxxxx',
Steve: '1TD8iwjcbR7K5dN-xxxxxxxxxx',
};
var id = folderLocations['Chris'];
console.log(id); // 1AEKHiI8iZKjHs-xxxxxxxxxx
const destinationFolder = DriveApp.getFolderById(folderLocations[row[4]]);

This did the trick :
function folderLocations(person){
var folder = {
Michael: '1bz9wIBRcRN2V-xxxxxxxxxx',
Chris: '1AEKHiI8iZKjHs-xxxxxxxxxx',
Steve: '1TD8iwjcbR7K5dN-xxxxxxxxxx',
};
return folder[person];
}
...further below:
const destinationFolder = DriveApp.getFolderById(folderLocations(row[4]));

Related

Create PDF from Google Sheet Template

I am fairly new to code and App Script, but I've managed to come up with this from research.
Form submitted, Sheet populated, take entry data, copy and append new file, save as pdf, email pdf
I've created examples of what I've been trying to do
Link to form - https://docs.google.com/forms/d/e/1FAIpQLSfjkSBkn3eQ1PbPoq0lmVbm-Dk2u2TP_F_U5lb45SddsTsgsA/viewform?usp=sf_link
link to spreadsheet - https://docs.google.com/spreadsheets/d/1kWQCbNuisZsgWLk3rh6_Iq107HoK7g-qG2Gln5pmYTE/edit?resourcekey#gid=1468928415
link to template - https://docs.google.com/spreadsheets/d/1Ye7DyJQOjA3J_EUOQteWcuASBCfqlA-_lzyNw0REjY8/edit?usp=sharing
However I receive the following error - Exception: Document is missing (perhaps it was deleted, or you don't have read access?)
at Create_PDF(Code:32:34)
at After_Submit(Code:13:21)
App Script Code as follows - If I use a google Doc as a template it works. However I would like to use a spreadsheet as a template, and have the result pdf content fit to page. Please let me know if you need any additional information for this to work.
function After_Submit(e, ){
var range = e.range;
var row = range.getRow(); //get the row of newly added form data
var sheet = range.getSheet(); //get the Sheet
var headers = sheet.getRange(1, 1, 1,5).getValues().flat(); //get the header names from A-O
var data = sheet.getRange(row, 1, 1, headers.length).getValues(); //get the values of newly added form data + formulated values
var values = {}; // create an object
for( var i = 0; i < headers.length; i++ ){
values[headers[i]] = data[0][i]; //add elements to values object and use headers as key
}
Logger.log(values);
const pdfFile = Create_PDF(values);
sendEmail(e.namedValues['Your Email'][0],pdfFile);
}
function sendEmail(email,pdfFile,){
GmailApp.sendEmail(email, "Subject", "Message", {
attachments: [pdfFile],
name: "From Someone"
});
}
function Create_PDF(values,) {
const PDF_folder = DriveApp.getFolderById("1t_BYHO8CqmKxVIucap_LlE0MhslpT7BO");
const TEMP_FOLDER = DriveApp.getFolderById("1TNeI1HaSwsloOI4KnIfybbWR4u753vVd");
const PDF_Template = DriveApp.getFileById('1Ye7DyJQOjA3J_EUOQteWcuASBCfqlA-_lzyNw0REjY8');
const newTempFile = PDF_Template.makeCopy(TEMP_FOLDER);
const OpenDoc = DocumentApp.openById(newTempFile.getId());
const body = OpenDoc.getBody();
for (const key in values) {
body.replaceText("{{"+key+"}}", values[key]);
}
OpenDoc.saveAndClose();
const BLOBPDF = newTempFile.getAs(MimeType.PDF);
const pdfFile = PDF_folder.createFile(BLOBPDF);
console.log("The file has been created ");
return pdfFile;
}
You get the error message with Google Sheets because you are using a Google Doc class to create the PDF, which is not compatible with Google Sheets.
DocumentApp can only be used with Google Docs. I will advise you to change
const OpenDoc = DocumentApp.openById(newTempFile.getId());
for
const openDoc = SpreadsheetApp.openById(newTempFile.getId());
const newOpenDoc = openDoc.getSheetByName("Sheet1");
And depending on the Google Sheet where the "Body" of the information is located. Replace:
const body = OpenDoc.getBody();
for an equivalent like getRange() or any Range class that helps you target the information you need. For example:
// This example is assuming that the information is on the cel A1.
const body = newOpenDoc.getRange(1,1).getValue();
The template for the PDF should be something like this:

List Google drive folder contents to google sheets with only new files

Looking to learn how to improve my use of loops. Currently I need to list the names and URLS from a google drive Folder to a sheet and this is the code that I have:
Existing Code
function wthFolderContents() {
var folder_id = 'myFolderID';
var folders = DriveApp.getFolderById(folder_id)
var contents = folders.getFiles();
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("SheetName");
sheet.clearContents()
sheet.appendRow( ['name', 'link'] );
var file;
var name;
var link;
var row;
while(contents.hasNext()) {
file = contents.next();
name = file.getName();
link = file.getUrl();
sheet.appendRow ( [name, link] );
with this code everytime the script is run the contents are cleared and then relisted. I am looking at a way of doing this dynamically / only update the new files so the script runs more effeciently.
Ive tried the following
New Code
function wthFolderContents2() {
var folder_id = '1vBzucZsb0SMOoHSWGtkUF-5QLQr5Fh1C';
var folders = DriveApp.getFolderById(folder_id)
var contents = folders.getFiles();
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("WHTCert");
var lastRow = sheet.getLastRow()
var existing = sheet.getRange(1,1,lastRow,1).getValues()
for(i=1;i<lastRow;i++) {
var existingFilename = existing [i][0]
Logger.log(existingFilename)
while(contents.hasNext()) {
var file;
var name;
var link;
file = contents.next();
name = file.getName();
link = file.getUrl();
if (!name == existingFilename) {
sheet.appendRow ( [name, link] );
}
}
}
I cant get this to work, not sure what exactly where I have gone wrong. Hope someone can point me int he right direction!
Cheers
I believe your goal is as follows.
You want to reduce the process cost of your script.
Modification points:
In your script, appendRow is used. In this case, the process cost will become high. Ref
The search for files is run in a loop. In this case, the process cost will become high.
In your situation, it seems that you want to retrieve the file list just under the specific folder. In this case, I thought that when Drive API is used, the process cost can be reduced. In this answer, I would like to propose using Drive API in your script. When this is reflected in your script, it becomes as follows.
When Drive API is used, all values can be retrieved. So, I thought that your 1st process might be able to be used.
Modified script:
Before you use this script, please enable Drive API at Advanced Google services.
function wthFolderContents2() {
var folder_id = '1vBzucZsb0SMOoHSWGtkUF-5QLQr5Fh1C';
// Retrieve file list.
var q = `'${folder_id}' in parents and trashed = false and mimeType != '${MimeType.FOLDER}'`;
var fileList = [['name', 'link']];
var pageToken = "";
do {
var obj = Drive.Files.list({ q, maxResults: 1000, pageToken, fields: "nextPageToken,items(id,title)", corpora: "allDrives", supportsAllDrives: true, includeItemsFromAllDrives: true });
if (obj.items.length > 0) {
fileList = [...fileList, ...obj.items.map(({ id, title }) => [title, `https://docs.google.com/presentation/d/${id}/edit?usp=drivesdk`])];
}
pageToken = obj.nextPageToken;
} while (pageToken);
// Put the values to Spreadsheet.
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("WHTCert");
sheet.clearContents();
sheet.getRange(1, 1, fileList.length, fileList[0].length).setValues(fileList);
}
When this script is run, the file list (filename and URL) is retrieved from the specific folder. And, the retrieved values to the "WHTCert" sheet.
Reference:
Files: list
Please convert this 2 script for scan folder only one subfolder and file, show folder name and link.
function wthFolderContents()
function wthFolderContents2()

How do I get the image ID of an image IN THE CELL?

I'm currently working on a code in Google Apps Script that allows a user to fill out a spreadsheet and have the spreadsheet generate printouts for a job board. I'm trying to design this in a way where the user can simply insert a logo image into a row of my Google sheet and have it replace a placeholder in my doc template.
I have found lots of answers about how you can take an image and convert it to a blob and insert it from a url or an ID, however, I can't seem to find a way to get the ID or url from the image in the cell.
Here's my code currently:
//Creates menu option on spreadsheet
function onOpen() {
const ui = SpreadsheetApp.getUi();
const menu = ui.createMenu('AutoFill Docs');
menu.addItem('Create New Docs', 'createNewGoogleDocs');
menu.addToUi();
}
//Defines where to get template and info from
function createNewGoogleDocs() {
const googleDocTemplate = DriveApp.getFileById('14MJNd37pn6D-EmNKCQzXXvxJCcOAoB3KS-TlDgZuWMI');
const destinationFolder = DriveApp.getFolderById('120Sb_CJJlmz5NzJW8W3DB4TNuC4kdD3e');
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('JobBoard');
const rows = sheet.getDataRange().getValues();
rows.forEach(function(row, index) {
if (index === 0) return;
if (row[9]) return;
const copy = googleDocTemplate.makeCopy(`${row[1]}, ${row[0]} Printout`, destinationFolder);
const doc = DocumentApp.openById(copy.getId())
const body = doc.getBody();
const friendlyDate = new Date(row[2]).toLocaleDateString();
//Replacing text
body.replaceText('{{Company}}', row[1]);
body.replaceText('{{jobTitle}}', row[0]);
body.replaceText('{{datePosted}}', friendlyDate);
body.replaceText('{{Description}}', row[3]);
body.replaceText('{{Qualifications}}', row[5]);
body.replaceText('{{Wage}}', row[4]);
body.replaceText('{{Apply}}', row[6]);
//A subfunction to handle replacing the image
function textToImage() {
var replaceTextToImage = function(body, searchText, image, width) {
var next = body.findText(searchText);
if (!next) return;
var r = next.getElement();
r.asText().setText("");
var img = r.getParent().asParagraph().insertInlineImage(0, image);
if (width && typeof width == 100) {
var w = img.getWidth();
var h = img.getHeight();
img.setWidth(width);
img.setHeight(width * h / w);
}
return next;
};
var documentId = doc;
var replaceText = "{{Upload Image}}";
var imageFileId = "### File ID of image ###"; //I don't know how to get this variable
var body = DocumentApp.openById(documentId).getBody();
var image = DriveApp.getFileById(imageFileId).getBlob();
do {
var next = replaceTextToImage(body, replaceText, image, 200);
} while (next);
}
//Close and saves new doc
doc.saveAndClose();
const url = doc.getUrl();
sheet.getRange(index + 1, 10).setValue(url)
})
}
I think what might be messing me up is that I have to loop through all my cells right now so that I can create multiple documents at once (meaning each row will have a different doc and different image ID). I'm just not sure how to work around that.
Here's the template and spreadsheet
https://docs.google.com/spreadsheets/d/1cySHogAxcUgzr0hsJoTyPZakKQkM6uIOtmyPzcMoJUM/edit?usp=sharing
https://docs.google.com/document/d/14MJNd37pn6D-EmNKCQzXXvxJCcOAoB3KS-TlDgZuWMI/edit?usp=sharing
There is a bit of an issue trying to get an image in a specific cell. There's even a Feature Request for that. This year Google released a few classes for image management but there seems to be issues when retrieving those using cellImage class.
I found a related answer (workaround) from user #Tanaike where images are retrieved from Google Sheets, converted to a Blob and inserted into a Google Doc.
Sample code provided was:
const spreadsheetId = "###"; // Google Spreadsheet ID
const res = DocsServiceApp.openBySpreadsheetId(spreadsheetId).getSheetByName("Sheet1").getImages();
console.log(res); // You can check the retrieved images at the log.
if (res.length == 0) return;
const blob = res[0].image.blob; // Here, 1st image of Sheet1 is retrieved. Of course, you can choose the image on the sheet.
let doc = DocumentApp.create("newDocName Goes_Here");
var body = doc.getBody();
var imgPDF = body.appendImage(blob);
Take into consideration that to make the above work you need to:
Install Google Apps Script library. (instructions here)
Enable Drive API.
I tested this and indeed, got the images from the given sheet and inserted them into the Google Doc specified. For some reason, running your code did not show me a newly created file from the template but you can tweak the above accordingly to your case.

Apps Script's spreadsheets "copyTo()" writes the range two times?

Henlo!
I've been stuck on a weird bug in google apps scripts (relative to sheets) and I didn't find any response on internet anywhere.
I have a sheet with filters (~370 lines) and I'm trying to copy its values to another sheet. But when I do that, for some reason, the 370 lines get copied 2 times, and I have absolutely no idea why. Result sheet is 370 lines + one blank line + 370 lines again.
Here's the code I'm using :
var spreadsheet = SpreadsheetApp.openById(spreadsheetID);
var init_sheet = spreadsheet.getSheetByName("default");
var final_sheet = spreadsheet.insertSheet("final");
var soure_range = init_sheet.getRange("A1:K");
var target_range = final_sheet.getRange("N1:X");
soure_range.copyTo(target_range);
if anyone knows why the range gets copied 2 times, I'd be glad to get unstuck :x
EDIT :
As discussed in the comments, this bug seems to appear only when I use a sheets converted from xlsx (the function above works correctly with any other normal sheet).
Here's the convertion function I use :
function convert(xlsxID, name, parentFolder) {
var xlsxBlob = DriveApp.getFileById(xlsxID).getBlob();
var file = {
title: ,
//Which Drive Folder do you want the file to be placed in
parents: [{'id':parentFolder}],
key: 'XXXX',
value: 'XXXX',
visibility: 'PRIVATE'
};
file = Drive.Files.insert(file, xlsxBlob, {
convert: true
});
return file.getId();
}
I was able to replicate the issue. Initially I did not filter the data from the converted xlsx file.
When copying range using copyTo(destination). Use the top-left cell position for your destination range.
Sample Code:
var spreadsheet = SpreadsheetApp.openById(spreadsheetID);
var init_sheet = spreadsheet.getSheetByName("Feuil1");
var final_sheet = spreadsheet.insertSheet("final");
var soure_range = init_sheet.getRange("A1:K");
var target_range = final_sheet.getRange("N1"); //Top-left cell position
soure_range.copyTo(target_range);
Output:
const ss = SpreadsheetApp.openById(spreadsheetID);
const ish = ss.getSheetByName("default");
const fsh = ss.insertSheet("final");
const srg = ish.getRange(1,1,ish.getLastRow(),11);
const vs = srg.getValues();
const trg = fsh.getRange(1,14,vs.length,vs[0].length);
srg.copyTo(trg);

Checking if a filename exists and updating the file it in Google Script

I currently have a script which merges data from a Google Sheet into a Google Doc template. For each row of the worksheet, a new document is created using the title data from the row. The script works fine, but isn't my work. It has been passed onto me and I'm not skilled enough at Google Script to figure out what I'd like to achieve.
Ideally I wanted to know if it was possible to check when the script is run whether the document file already exists. It would do this as each document that is created uses the title data from the worksheet. If the document does exist then the data could be updated in that sheet, rather than creating a new version of it.
The script is the following
function mergeDocSheet() {
const TEMPLATE_ID = '16YfyeDjGDp-88McAtLCQQyZ1xz4QX5z';// Google Doc template ID
const SS_ID = '1C5gtJCSzHMuSz-oVWEItl2EUVRDwF5iH_'; // Google Sheet ID
const SHEET_NAME = "data"; // Google Sheet Tab name
const MAPPED = mappedDocToSheet;
const FILE_NAME = ["Titre de la formation"] // Header IDs from sheet.
docMerge(TEMPLATE_ID,SS_ID,SHEET_NAME,MAPPED, FILE_NAME);
}
function docMerge(templateID,ssID, sheetName, mapped, fileNameData, rowLen = "auto"){
//Get the Spreadsheet and sheet tab
const ss = SpreadsheetApp.openById(ssID);
const sheet = ss.getSheetByName(sheetName);
//Get number of rows to process
rowLen = (rowLen = "auto") ? getRowLen() - 1 : rowLen;
//Gets the range of data in the sheet then grabs the values of the range
const range = sheet.getRange(1,1,rowLen,sheet.getDataRange().getNumColumns());
const matrix = range.getValues();
// Searches the file mapped object and finds the corresponding number returns the column number in an array.
const fileNameRows = getFileNameRows()
//Loops through each row of the sheet grabbing the data from each row and putting it into a new doc.
for(let i = 1; i < rowLen; i++){
let row = matrix[i];
//Get the title for the file.
let fileName = buildFileName(row)
let newDoc = DriveApp.getFileById(templateID).makeCopy(fileName);
updateFileData(row, newDoc.getId());
};
function updateFileData(rowArray, doc){
//Loops through the mapped object.
mapped.forEach(function(element){
let textID = `\{\{${element.doc}\}\}`
DocumentApp.openById(doc).getBody().replaceText(textID,
rowArray[element.col]);
});
};
function buildFileName(rowArry){
let fileNameArray = fileNameRows.map(ele => rowArry[ele]);
return fileNameArray.join("_");
};
function getFileNameRows(){
//Map the column indexes from fileNameData
let fileNameLocs = fileNameData
.flatMap(name => {
return mapped.filter(element => element.sheet === name)
.map(ele => ele.col);
});
return fileNameLocs;
};
function getRowLen(){
return sheet.getDataRange().getNumRows();
};
};
Would it be possible to set up some kind of conditional, perhaps around these lines?
let newDoc = DriveApp.getFileById(templateID).makeCopy(fileName);
updateFileData(row, newDoc.getId());
I'm hoping someone can point me in the right direction with this. Any advice is much appreciated.
You can consider using searchFiles(params) to search for a specific filename with Doc type in your drive based on the search query term guidelines. Once you found all the files having the same filename, you can delete each file using setTrashed(trashed) before creating a new file using the template document
Sample Code:
//Loops through each row of the sheet grabbing the data from each row and putting it into a new doc.
for(let i = 1; i < rowLen; i++){
let row = matrix[i];
//Get the title for the file.
let fileName = buildFileName(row);
//This query parameter will search for an exact match of the filename with Doc file type
let params = "title='"+fileName+"' and mimeType = 'application/vnd.google-apps.document'"
let files = DriveApp.searchFiles(params);
while (files.hasNext()) {
//Filename exist
var file = files.next();
///Delete file
file.setTrashed(true);
}
//Create a new file
let newDoc = DriveApp.getFileById(templateID).makeCopy(fileName);
updateFileData(row, newDoc.getId());
};
In this given sample code, we will loop all files that have the exact filename and delete each file before creating a new one.
Additional References:
Google Drive Mimetypes