no item with the given id could be found - google-apps-script

I created a script that pdf's and emails Google Form responses to me (based off of TJHouston's script). It works perfectly but errors if the enduser tries to edit their original response.
The original trigger is onFormSubmit, From Spreadsheet, On Form Submit. This works great.
I created a second trigger which is onFormSubmit, From Spreadsheet, On edit thinking that was the trigger to prompt another creation of the PDF - but when I click the link to edit my google form responses and then resubmit the form, I get the error "No item with the given ID could be found, or you do not have permission to access it." and it references the line
var copyId = DriveApp.getFileById(docTemplate)
I also tried the onFormSubmit, From Spreadsheet, On change trigger and got the same results.
// Get template from Google Docs and name it
var docTemplate;
var docName;
if (NYcollection == "YES") {docTemplate = "1JRDVjOYxeyl1dXIC115u5W2SQTVgbBMPUERBQ9xfgjo";docName = "Form M";}
else if (NYcollection == "NO") {docTemplate = "1YGx8wGZyBvfmcfdkTWGQF8XUGlJF7zaYI9ZVO2lYnto";docName = "Form N";}
// Get document template, copy it as a new temp doc, and save the Doc’s id
var copyId = DriveApp.getFileById(docTemplate)
.makeCopy(NYfacility +' - '+docName+' '+NYdate +NQE4)
.getId();
// Open the temporary document
var copyDoc = DocumentApp.openById(copyId);
// Get the document’s body section
var copyBody = copyDoc.getActiveSection();
// Replace place holder keys,in our google doc template
copyBody.replaceText('keyNYname', NYname);
copyBody.replaceText('keyNYdate', NYdate);
The code goes on to replace all my keys and then email the pdf to me. I'm not understanding why it can get the ID on the initial trigger but not on the edit trigger. Am I using the wrong trigger or should there be additional code in there?
Thanks for any help you can give!!

Have the code check for the absence of either YES or NO, and if the values you want are not there, use the getValue() method of the range class to get the value.
Put this code before the line that assigns the SS ID.
if (NYcollection !== "YES" && NYcollection !== "NO") {
//Get the value
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheetWhereValueIs = ss.getSheetByName('YourSheetNameHere');
var rowOfMyValue = 3;
var columnOfMyValue = 4;
var NYcollection = sheetWhereValueIs.getRange(rowOfMyValue, columnOfMyValue).getValue();
};

Related

Google Apps Script Permissions for a function with inputs

I know this topic has bee covered before sort of, but none of it really makes sense to me. Basically I wrote a function that will generate a PDF from some API data using user inputs. It looks vaguely like this and works when I run it in the script editor.
function myfunction(InputA,InputB,InputC,InputD,InputE) {
......
var sourceSpreadsheet = SpreadsheetApp.getActive();
var parents = DriveApp.getFileById(sourceSpreadsheet.getId()).getParents();
if (parents.hasNext()) {
var folder = parents.next();
}
var response = UrlFetchApp.fetch(url, options)
var blob = response.getAs('application/pdf').setName(InputA + InputB + InputC)
var newFile = folder.createFile(blob);
return newFile
}
The problem is Google permissions. The classic: "You don't have permission to access the drive".
I have tried publishing the script as a private sheets addon and enabled it on my spreadsheet. But that didn't really do anything. And I don't really know why because I authorized the app for all the required scopes when I approved the add on. I can see it in the extensions menu but I am still getting errors when I try to call the function.
The button method of enabling permissions doesn't work for me because I need to run the code several times based on parameters defined in the Sheet. I tried simple triggers since I want the code to run weekly anyways, but found the same problem.
Can someone give me the step by step of how I'm supposed to do this.
Please don't send links to the google documentation because I have read the related pages and still don't know what I'm doing wrong.
I recommend you use an installable onEdit trigger. I have this approach and see if it works for you.
Sample Data:
Assuming url is from Input D column.
Create a column of checkboxes that will trigger the installed trigger. In my case, ticking it will create the file and unticking it will remove the file created.
Ticking rows where an input (at least 1) is missing, will cancel the creation of the file and then untick the checkbox ticked.
Drive folder:
Script:
function createFileTrigger(e) {
var spreadsheet = e.source;
var sheet = spreadsheet.getActiveSheet();
var range = e.range;
var value = e.value;
var row = range.getRow();
var col = range.getColumn();
// proceed if edited cell is Sheet1!H2:H
if(sheet.getSheetName() == 'Sheet1' && col == 8 && row > 1) {
// if checkbox is ticked
if(value == 'TRUE') {
// inputs = [Input A, Input B, Input C, Input D, Input E]
var inputs = range.offset(0, -6, 1, 5).getValues().flat();
var parents = DriveApp.getFileById(spreadsheet.getId()).getParents();
var folder = parents.next();
// set some conditions here to pre-check the inputs
// e.g. if at least 1 input is blank, cancel file creation (return)
if(inputs.filter(String).length < 5) {
// untick the checkbox ticked
range.setValue('FALSE');
// skip creation of file
return;
}
// assuming url is from Input D (removed options as not needed for presentation)
var response = UrlFetchApp.fetch(inputs[3]);
var newFileName = `${inputs[0]} ${inputs[1]} ${inputs[2]}`;
// if file is existing (which should not happen but just in case)
if(folder.getFilesByName(newFileName).hasNext()) {
// do something else that is needed to be done to avoid duplication of file
// e.g. overwrite or skip creating file
console.log(newFileName + ' is already existing in the parent folder');
}
// if not existing
else {
// create the file
var blob = response.getAs('application/pdf').setName(newFileName)
// for presenation purposes, will write the id of the created file
range.offset(0, -1).setValue(folder.createFile(blob).getId());
}
}
// if checkbox is unticked
else {
// do something else that is needed to be done
// e.g. delete the file using the id returned (using Drive Advanced Services)
var fileIdRange = range.offset(0, -1);
Drive.Files.remove(fileIdRange.getValue());
// remove file id content on the cell
fileIdRange.clearContent();
}
}
}
Ticking checkbox (folder):
Ticking checkbox (sheet):
Note:
This can still be improved, but should already be enough for your case.

how can i get the last modified row in a sheet to display a value in a form response from another sheet?

I have a sheet named "Credentials" populated with the following data:
And another sheet in the same spreadsheet named "Clients" which is linked to a google form:
My goal here is to show on the confirmation message the data from the "Credentials" sheet which is in the same row as the row that is filled when a form is submitted. E.G: the form data that was stored in row 2 should show "usr a" and "pass a" on the confirmation message
I have this apps script to do so but it always shows the last data from the "Credentials" sheet on the form confirmation message and i can't see why:
function onEdit(e) {
var ss = SpreadsheetApp.getActiveSpreadsheet()
var sheet_clientes = ss.getSheetByName('Clients')
var sheet_demos = ss.getSheetByName('Credentials')
var form = FormApp.openByUrl(ss.getFormUrl())
var demosRange = sheet_demos.getRange('A2:B4')
var demosData = demosRange.getValues()
var lr = sheet_clientes.getLastRow()
var user = demosData[lr][0]
var password = demosData[lr][1]
form.setConfirmationMessage('Congratulations: Your user is:\n' + user + '\n Your password is:\n' + password)
}
You can use the Form Submit Trigger of Google Apps Script to run the script whenever a response was submitted.
But for setConfirmationMessage(message) Bryan P explained that
we can't conditionally set and immediately display a custom
message for the current user's response based on what their answers
were. The confirmation message is "front-loaded" in a sense.
Since we cannot use setConfirmationMessage() for this case, I created an alternative that will send the credentials to the email address the provided in the response.
Try this:
Code:
function onFormSubmit(e) {
var range = e.range;
var sheet = range.getSheet();
var row = range.getRow();
var spreadsheet = sheet.getParent();
var credSheet = spreadsheet.getSheetByName("Credentials");
var creds = credSheet.getRange(row, 1, 1, 2).getValues();
var values = e.namedValues;
var email = values['Email Address'];
var username = creds[0][0];
var password = creds[0][1];
var message = 'Your user is:' + username + '\nYour password is:' + password;
var subject = 'Credentials';
GmailApp.sendEmail(email, subject, message)
}
Trigger Setup:
In your Apps Script, go to the left side menu and click Triggers.
Click Add Trigger.
Copy the setup below.
Click Save
Note: Make sure to copy and save the code provided above before creating the trigger.
Example:
References:
Event Object
Class Range
Class Sheet
Class GmailApp

If a cell in an incoming row from a Google Form has a certain value, then run a script function

I want to take new entries in a Google Sheet (coming in from a Google Form), and run a certain trigger on them depending on the response in column B. I have written out the code (which sends the information to a Google Doc template), and established a trigger, but it doesn't differentiate the data in column B.
Here's a very generic version of what I'm working on: Form, Workbook, and Doc.
And here's the code I've got:
function Bob(e) {
var timestamp = e.values[0];
var name = e.values[1];
var awesome = e.values[2];
var file = DriveApp.getFileById('1BxK26Yim0-nAVQW5FcQ59q3Uxe2qm9EhClLE1thSdMU');
var folder = DriveApp.getFolderById('149gm5Voa3tthmv66cF5kJmf3bf2zj-VA')
var copy = file.makeCopy(name, folder);
var doc = DocumentApp.openById(copy.getId());
var body = doc.getBody();
body.replaceText('{{Timestamp}}', timestamp);
body.replaceText('{{Name}}', name);
body.replaceText('{{Awesome}}', awesome);
doc.saveAndClose();
}
So essentially, if column B says Bob, then the trigger will kick in and the function will operate. If it says Joe, then a separate trigger would run a different function (that I have yet to set up, but assume it'd be little more than a copy-paste job). My guess is that it'd just take a few lines in the form of an if-statement before my function runs, but I'm very new at this.
Thanks
Add a condition by using an if statement, i.e.,
function Bob(e){
if(e.values[1] === 'Bob'){
// Add here what you want you function does when the corresponding cell of Column B is equal to "Bob"
}
}

Create a Google Doc for Changed Row on Sheet When Editing Form Responses

Here's a link to the build project: https://drive.google.com/open?id=1IbmEWv_y60n-IefIrjmTcND-E6Rv35vb
Goal
Create a document when the form is submitted initially. Then, create a new document for each affected row on the sheet "Form Responses 1" when using the form to edit responses.
Current Results
Documents are created for all rows on the sheet every time the script runs. The individual documents include the correct data, but documents are also created for rows that didn't change values.
Problem
I'm inexperienced with scripts and cannot find a way to create a document only if values within the row have changed.
I tried a method other than the script below using "On form submit" as a trigger, but editing responses resulted in creating documents that ignored any response that was left unedited. In other words, if the form collected data for Q1 and Q2, the first document created would include everything entered on the form, as expected. If I edited the form response for Q1, however, the newly created document ignored Q2 entirely and only displayed Q1.
I also tried using "On edit" as a trigger. That didn't work at all when using the form. It required editing the sheet "Form Responses 1" directly, so it didn't meet my needs.
Help?
Current Trigger and Script
Trigger: On form submit
function setUp() {
//IDs to get and open
var tid = "1Us7qyNjFTeTrrA2Xt_Yigks1Yn-n0KGre9rUn3UV5yc";
var fid = "1HVy7J7EsgKfX-BjgzOGgXlcmYDuLs5C_";
var sid = "1FrJpfE9L2ZRE76ABDWA0gfYaZd6RFkvGQkSipHFCZCU";
var sname = "Form Responses 1";
//Get the template doc
var template = DriveApp.getFileById(tid);
//Get the output folder
var folder = DriveApp.getFolderById(fid);
//Open the form response worksheet
var ws = SpreadsheetApp.openById(sid).getSheetByName(sname);
//Get the worksheet range
//starting row #, starting column #, last row, # of columns
var range = ws.getRange(2,1,ws.getLastRow()-1,7).getValues();
range.forEach(function(r){
//Name the data
var ts = r[0];
var email = r[1];
var q1 = r[2];
var q2 = r[3];
var q3 = r[4];
var q4 = r[5];
var q5 = r[6];
//Format the timestamp
var date = Utilities.formatDate(new Date(ts), "GMT-5", "yyyy-MM-dd");
createDoc(template,folder,date,email,q1,q2,q3,q4,q5);
});
}
function createDoc(template,folder,date,email,q1,q2,q3,q4,q5) {
//Copy the template, name the doc, and put it in the folder
var copy = template.makeCopy(q1 + ' - ' + date, folder);
//Open the new document
var doc = DocumentApp.openById(copy.getId());
//Get and update the body of the document
var body = doc.getBody();
body.replaceText('##Date##', date);
body.replaceText('##Email##', email);
body.replaceText('##Question1##', q1);
body.replaceText('##Question2##', q2);
body.replaceText('##Question3##', q3);
body.replaceText('##Question4##', q4);
body.replaceText('##Question5##', q5);
//Save and close the document to persist our changes
doc.saveAndClose();
}
You were in the right way when you were using the onFormSubmit(e) and yes, the e object gives you the edited cells, but not the other values, what I did to solve that issue was to use the range that brings the e object to locate the edited row and then find it using e.range.getRow(), the rest would be done as you were doing it.
// Create a new document with the submitted data in the sheet
function createDoc(data){
//Get the output folder
var folder = DriveApp.getFolderById("your folder ID");
//Get the template doc
var template = DriveApp.getFileById("your file ID");
//Copy the template, name the doc, and put it in the folder
var copy = template.makeCopy(data[0][2] + ' - ' + data[0][0], folder);
//Open the new document
var doc = DocumentApp.openById(copy.getId());
//Get and update the body of the document
var body = doc.getBody();
body.replaceText('##Date##', data[0][0]);
body.replaceText('##Email##', data[0][1]);
body.replaceText('##Question1##', data[0][2]);
body.replaceText('##Question2##', data[0][3]);
body.replaceText('##Question3##', data[0][4]);
body.replaceText('##Question4##', data[0][5]);
body.replaceText('##Question5##', data[0][6]);
//Save and close the document to persist our changes
doc.saveAndClose();
}
// This will be trigger every time the form is submitted
function onFormSubmit(e) {
// Get active sheet in the active spreadsheet
var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var numberCols = 7;
// Get values in the row that was edited
var editedRow = sheet.getRange(e.range.getRow(), 1, 1, numberCols).getValues();
// Create a new doc
createDoc(editedRow);
}
As you can see, I made several changes to your code, you can use it like that or adapt it in the way you would want to. Just keep in mind in the future you could check the values inside the data array to verify if they are empty or not.
Docs
For more info about you can check these and be careful about the Instabble Triggers restriccions:
onFormSubmit().
Event Objects - Form Submit.
Class Range.
Installable Triggers.
I was just trying to provide a way to store your last configuration so that you could determinine whether a change has been made between triggers so that you would know whether or not to generate a new document.
SpreadsheetApp.getActive().getSheetByName('ConfigSheet').appendRow([tid,fid,date,email,q1,q2,q3,q4,q5]);
I'd do it this way because getting the last configure is as simple as
var sh=SpreadsheetApp.getActive().getSheetByName('ConfigSheet');
sh.getRange(sh.getLastRow(),1,1,sh.getLastColumn()).getValues()[0];`
and now you have all of your latest configuration setting in a flat array. But I don't know what sort of changes merits creating a new document.

TypeError: Cannot find function getCell in object Sheet

I am completely new to coding anything related to Google Apps Script and JavaScript in general.
I've adapted a script to my needs, but I am getting the following error when I run it:
TypeError: Cannot find function getCell in object Sheet
Essentially, I am trying to get the value in cell D4 (4,4) and pass that value to the variable emailTo. I'm obviously not doing it correctly. The rest of the script should work fine. Any guidance is appreciated.
// Sends PDF receipt
// Based on script by ixhd at https://gist.github.com/ixhd/3660885
// Load a menu item called "Receipt" with a submenu item called "E-mail Receipt"
// Running this, sends the currently open sheet, as a PDF attachment
function onOpen() {
var submenu = [{name:"E-mail Receipt", functionName:"exportSomeSheets"}];
SpreadsheetApp.getActiveSpreadsheet().addMenu('Receipt', submenu);
}
function exportSomeSheets() {
// Set the Active Spreadsheet so we don't forget
var originalSpreadsheet = SpreadsheetApp.getActive();
// Set the message to attach to the email.
var message = "Thank you for attending ! Please find your receipt attached.";
// Construct the Subject Line
var subject = "Receipt";
// THIS IS WHERE THE PROBLEM IS
// Pull e-mail address from D4 to send receipt to
var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var emailTo = sheet.getCell(4, 4).getValue();
// Create a new Spreadsheet and copy the current sheet into it.
var newSpreadsheet = SpreadsheetApp.create("Spreadsheet to export");
var projectname = SpreadsheetApp.getActiveSpreadsheet();
sheet = originalSpreadsheet.getActiveSheet();
sheet.copyTo(newSpreadsheet);
// Find and delete the default "Sheet 1"
newSpreadsheet.getSheetByName('Sheet1').activate();
newSpreadsheet.deleteActiveSheet();
// Make the PDF called "Receipt.pdf"
var pdf = DocsList.getFileById(newSpreadsheet.getId()).getAs('application/pdf').getBytes();
var attach = {fileName:'Receipt.pdf',content:pdf, mimeType:'application/pdf'};
// Send the constructed email
MailApp.sendEmail(emailTo, subject, message, {attachments:[attach]});
// Delete the wasted sheet
DocsList.getFileById(newSpreadsheet.getId()).setTrashed(true);
}
The issue is that getCell() is a method of Range, not Sheet. Get a Range from the Sheet, then use getCell() on the Range object