Run a Google Apps Script from another file within the same project - google-apps-script

I would like to run the script "xGen_FilterRemove" from my "Generic Macros.gs" file in my "Test" Script located in "Macro.gs" as per below, but am know sure how to correctly reference it?
function Test() {
var aCurrentBook = SpreadsheetApp.getActive();
'Generic Macros.gs'.xGen_FilterRemove()
}
};

Just run it like this
function Test() {
var aCurrentBook = SpreadsheetApp.getActive();
xGen_FilterRemove()
}
All of the functions in a project are accessible universally no matter what file they are in. That's why all functions must have a unique name. The files are provided to make it easier to organize them. You can have as many functions in a file as you wish

Related

Giving access to a Google Spreadsheet and its accompanying Apps Script

I have a spreadsheet I'm using to manage a bunch of content, with a script I've written that adds an "Export" button to the menu. When the button is clicked, the script gets all the appropriate data and formats it all in a specific way. The formatted version is saved to my Google Drive with a timestamp but a download link is also provided. I'll include a simplified version of the script below in case modifications are required.
I rarely ever use Google's Apps Scripts so I'm rather unfamiliar with the ins and outs of it. I only know the basics (how to write a script that can run when something is done from the spreadsheet's page).
I'm aware I can invite a user to my spreadsheet (or just make it public) but that doesn't seem to bring the script along with it. The script and all the formatting that's being done is the main part of what the person I'm inviting needs. I'm aware that for file.getDownloadUrl() to work (assuming the file is still saving on my Drive), I'd need to give the individual access to that folder as well which isn't a problem.
The question is, how do I give them access to the script so they get the Export menu item? Am I not able to? Am I basically limited to creating a button with the export function bound to it?
function onOpen() {
var ss = SpreadsheetApp.getActiveSpreadsheet()
var csvMenuEntries = [
{
name: "Export as CSV",
functionName: "csvExport"
},
{
name: "Export for wiki",
functionName: "wikiExport"
}
]
ss.addMenu("Export", csvMenuEntries)
}
function prepare(type) {
const ss = SpreadsheetApp.getActiveSpreadsheet()
const ssName = ss.getName()
const sheet = ss.getSheets()[0]
const sheetName = sheet.getSheetName()
const folderName = ssName + ' exports'
let folder
try {
folder = DriveApp.getFoldersByName(folderName).next()
} catch (err) {
folder = DriveApp.createFolder(folderName)
}
let fileName
if (type) {
const extension = type === 'csv' ? 'csv' : 'txt'
fileName = ssName + '_' + sheetName + `_${type}_` + new Date().getTime() + `.${extension}`
}
return { ss, ssName, sheet, sheetName, folder, fileName }
}
function download(file) {
const downloadURL = file.getDownloadUrl().slice(0, -8)
showUrl(downloadURL)
}
function showUrl(downloadURL) {
var link = HtmlService.createHtmlOutput(`Click here to download`)
SpreadsheetApp.getUi().showModalDialog(link, 'Your file is ready!')
}
function csvExport() {
const { ss, sheet, folder, fileName } = prepare('csv')
const csvSettings = getCsvSettings(ss)
const csvFile = convertRangeToCsv(sheet, csvSettings) // not going to share this. It's simple but irrelevant
const file = folder.createFile(fileName, csvFile)
download(file)
}
function wikiExport() {
const { sheet, folder, fileName } = prepare('wiki')
const wikiFile = convertRangeToWikiFormat(sheet) // not going to share this. It's simple but irrelevant
const file = folder.createFile(fileName, wikiFile)
download(file)
}
A container-bound script has the same access as its parent spreadsheet, so if you're sharing the spreadsheet you're also sharing the script (though if they have only view access they have to create their own copy to see it):
All container-bound scripts use the same owner, viewer, and editor access list defined for the container file.
With that in mind, there are a few limitations when using scripts. First, they will not trigger for anonymous users (i.e., users that are not signed in), even if the sheet is editable to the public. You'll notice that if you try to open the script editor as anonymous, you will be asked to sign in. There's also a feature request to allow this on Google's issue tracker here.
Secondly, even if the users are signed in, there are other restrictions for Apps Script's triggers:
onOpen(e) runs when a user opens a spreadsheet, document, presentation, or form that the user has permission to edit.
Users need permission to edit the file for the onOpen() trigger to run. If they have viewer or commenter access the menu won't show up. In fact, you'll find that most script functions won't work if the users have only viewer access since they need editor access for most interactions with the sheet.
So if you want this menu to show up you'll need to give your users explicit editor access. If you really must keep your sheet as view-only or want to interact with anonymous users you can consider building a Web App instead and have the users get the download link from there. The web app has ways to communicate with the back-end or the Sheet so you should be able to reproduce your current code that way as well.
References:
Web Apps
Communicating with server functions
Triggers

how to make sure the code is rendered to googlesheet before export pdf file?

I created a template sheet in google sheet. I need to generate multiple pdf files with variable data based on the template.
For example, I put "Michael" into the specific cell of the template using getRanage.Setvalu function and export the pdf file. What I need is that it would generate a pdf file with respectively variable data. However, the problem is that it is a template pdf file without the data of "Michael". When I check the template sheet, it does show the "Michael" on the template sheet.
My code is something like below.
getRange("A1").SetValue(); genaratePdf();
I tried to use the timeout function, it does work. However, this is not what I want and is inefficient.
My question is that if there is a way to make sure that "Michael" is rendered to the sheet before running the export pdf function?
try SpreadsheetApp.flush() before running the pdf code.
If the function getRange().setValue() has been executed successfully, the cell should be updated by the value entered in setValue, keep in mind that the value can be numeric, string, boolean or date. Unless the content of those cells is being modified by another process, such as Formulas or GAS script, you would not need to implement any additional check.
In any case, you can control if that cell was set properly before running the generatePdf function.
Code.gs
function main() {
var dataARR = ["Michael", "Anna", "Fish"]
var ss_id = "1CmlUOdz_8LVV00WvCU9aqIJBUf7bpNBiqk7Clwy3eHc"
var folder_id = "1roIvZbLxhX9PKQesj0ItINfBNHia_Kv2"
for (let data of dataARR) {
if (editSpreadSheet(data, ss_id) === data) generatePdf(folder_id, ss_id, data)
}}
function editSpreadSheet(data, id) {
let ss = SpreadsheetApp.openById(id).
ss.getRange('A1').setValue(data)
return ss.getRange('A1').getValue()
}
function generatePDF(folder_id, ss_id, data) {
var blob_pdf = DriveApp.getFileById(ss_id).getAs('application/pdf')
DriveApp.getFolderById(folder_id).createFile(blob_pdf).setName(data)
}
Documentation
SpreadSheetApp
Drive App

Google Apps Script trigger - run whenever a new file is added to a folder

I want to execute a google apps script whenever a new file is added to a specific folder.
Currently I'm using a run-every-x-minutes clock trigger, but I only need to run the script whenever I add a file to a folder. Is there a way to do this?
The same as this question - which is now almost 3 years old. The comment below the question states that:
There's not a trigger for that, if that's what you're hoping. How are
things getting into the folder, and do you have any control over that?
– Jesse Scherer Apr 8 '18 at 3:02
I wonder if this comment is still valid, and if it is, then if there's a workaround.
Issue:
Unfortunately, the comment you read is still true. Here is a list of all the available triggers and a trigger for a new file added to a folder is not one of them.
Workaround / Explanation:
I can offer you a workaround which is usually used by developers when they built their add-ons. You can take advantage of the PropertiesService class. The logic is quite simple.
You will store key-value pairs scoped to the script:
In your case, the key will be the folder id, and the value will be the number of files under this folder.
You will setup a time-driven trigger to execute mainFunction for example every one minute.
The script will count the current number of files within the selected folder. The function responsible for that is countFiles.
The checkProperty function is responsible for checking if the current number of files under this folder matches the old number of files. If there is a match, meaning no files were added, then checkProperty returns false, otherwise return true and update the property for the current folder ID, so when the script runs after 1 minute, it will compare with the fresh value.
If checkProperty returns true, then execute the desired code.
Code snippet:
Set up a time-driven trigger for mainFunction. Whatever code you put inside the brackets of the if(runCode) statement will be executed if the number of files under the folderID has changed.
function mainFunction(){
const folderID = 'folderID'; //provide here the ID of the folder
const newCounter = countFiles(folderID);
const runCode = checkProperty(folderID, newCounter);
if(runCode){
// here execute your main code
//
console.log("I am executed!");
//
}
}
And here are the helper functions which need to be in the same project (you can put them in the same script or different scripts but in the same "script editor").
function countFiles(folderID) {
const theFolder = DriveApp.getFolderById(folderID);
const files = theFolder.getFiles();
let count = 0;
while (files.hasNext()) {
let file = files.next();
count++;
};
return count;
}
function checkProperty(folderID, newC){
const scriptProperties = PropertiesService.getScriptProperties();
const oldCounter = scriptProperties.getProperty(folderID);
const newCounter = newC.toString();
if(oldCounter){
if(oldCounter==newCounter){
return false;
}
else{
scriptProperties.setProperty(folderID, newCounter);
return true;
}
}
else{
scriptProperties.setProperty(folderID, newCounter);
return true;
}
}

Copying multiple files in Apps Script

I have a script of which finds all files by name then copies and renames them, I want it to make a copy of multiple files (all of which have the same name) then rename them to the same original name. Currently, it mostly functions as it finds the files by name but it only copies the first file and ignores the rest. My current script is as follows
function copyDocs() {
var file = DriveApp.getFilesByName('PlaceHolder').next();
file.makeCopy('PlaceHolder');
}
I myself am not the most educated on this type of language or any coding language, to be honest, so I'm sorry if this is an easy solution or rookie mistake.
DriveApp.getFilesByName(...) returns a FileIterator. You need to iterate over all of the found files.
function copyDocs()
{
var files = DriveApp.getFilesByName('PlaceHolder');
while(files.hasNext())
{
files.next().makeCopy("PlaceHolder");
}
}
My issue was solved with help from #Cooper and #IMTheNachoMan. I simply needed to use the while(files.hasNext) iterator before placing makeCopy("PlaceHolder"). The end script is as follows
function copyDocs() {
var files = DriveApp.getFilesByName('PlaceHolder');
while (files.hasNext()) {
files.next().makeCopy('PlaceHolder');
}
}

google script: File class is not defined

I'm trying to make my google docs script create a backup copy of the file each time I open it.
To make a copy I write
var name = File.getName();
var filecopy = File.makeCopy(name + " backup");
But it won't recognize the File class. Although it knows DocsList. How do I make it work or make a copy of the file another way?
GAS permits to call class methods or instance only native classes (Object, String, etc), own classes or Google Services (DocList, SpreadsheetApp, etc). Other classes like File, Folder, Spreadsheet, Range, etc are accessible and instanceable only via calling the services functions, for example, DocsList.getFileById("..."); returns the File class instance.
The following function copies a file having the srcFileID ID to a new file with the name stored in the dstFileName parameter.
function testCopy(srcFileID, dstFileName) {
var srcFile = DocsList.getFileById(srcFileID);
srcFile.makeCopy(dstFileName);
}
You cannot use the File class that way. Use something on these lines
var file = DocsList.getFileById(ID) ; // you can use DocsList.find or DocsList.create
var filecopy = file.makeCopy();